From 699a681aa985aee148733da093d28b7cd33c286a Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 8 Dec 2015 23:42:57 +0200 Subject: [PATCH 01/58] update syntax and build commands to es2015 --- .babelrc | 4 + .gitignore | 10 + .istanbul.yml | 2 + .travis.yml | 3 + Makefile | 12 +- build.js | 302 +++++++++++++++++++++++++++++++ {lib => lib-coffee}/index.coffee | 0 lib/index.js | 277 ++++++++++++++++++++++++++++ package.json | 32 +++- test/mocha.opts | 2 +- 10 files changed, 628 insertions(+), 16 deletions(-) create mode 100644 .babelrc create mode 100644 .istanbul.yml create mode 100644 build.js rename {lib => lib-coffee}/index.coffee (100%) create mode 100644 lib/index.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..7683066 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015", "stage-0"], + "plugins": [] +} diff --git a/.gitignore b/.gitignore index d851475..0338f48 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,13 @@ node_modules coverage src +logs +*.log +npm-debug.log* +pids +*.pid +*.seed +lib-cov +.grunt +.lock-wscript +build/Release diff --git a/.istanbul.yml b/.istanbul.yml new file mode 100644 index 0000000..7178a27 --- /dev/null +++ b/.istanbul.yml @@ -0,0 +1,2 @@ +instrumentation: + root: lib diff --git a/.travis.yml b/.travis.yml index f5b97b7..80838f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: node_js node_js: + - "0.12" + - "0.11" - "0.10" + - "iojs" after_script: - npm run coveralls sudo: false diff --git a/Makefile b/Makefile index 1716e30..bb3176f 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ build: - cp -R lib src - ./node_modules/.bin/coffee -c lib - find lib -iname "*.coffee" -exec rm '{}' ';' + npm run build unbuild: - rm -rf lib - mv src lib + npm run unbuild coveralls: - NODE_ENV=test istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage + npm run coveralls + +release: + npm run release diff --git a/build.js b/build.js new file mode 100644 index 0000000..fc8a2d4 --- /dev/null +++ b/build.js @@ -0,0 +1,302 @@ +'use strict'; + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); +//import contentful from 'contentful'; + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _when = require('when'); + +var _when2 = _interopRequireDefault(_when); + +var _string = require('string'); + +var _string2 = _interopRequireDefault(_string); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _pluralize = require('pluralize'); + +var _pluralize2 = _interopRequireDefault(_pluralize); + +var _rootsUtil = require('roots-util'); + +var _rootsUtil2 = _interopRequireDefault(_rootsUtil); + +var _querystring = require('querystring'); + +var _querystring2 = _interopRequireDefault(_querystring); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var errors = { + no_token: 'Missing required options for roots-contentful. Please ensure `access_token` and `space_id` are present.', + no_type_id: 'One or more of your content types is missing an `id` value', + sys_conflict: 'One of your content types has `sys` as a field. This is reserved for storing Contentful system metadata, please rename this field to a different value.' +}; + +var hosts = { + develop: 'preview.contentful.com', + production: 'cdn.contenful.com' +}; + +/* TODO: change to `export default` when tests are rewritten in ES6/7 */ +module.exports = function (opts) { + if (!opts.access_token && opts.space_id) { + throw new Error(errors.no_token); + } + var client = contenful.createClient({ + host: hosts[process.env.CONTENTFUL_ENV] || (opts.preview ? hosts.develop : null) || hosts.production, + accessToken: opts.access_token, + space: opts.space_id + }); + return (function () { + function RootsContentful(roots) { + _classCallCheck(this, RootsContentful); + + this.util = new _rootsUtil2.default(roots); + this.roots.config.locals = this.roots.config.locals || {}; + this.roots.config.locals.contentful = this.roots.config.locals.contentful || {}; + this.roots.config.locals.asset = asset_view_helper; + } + + _createClass(RootsContentful, [{ + key: 'setup', + value: function setup() { + return configure_content(opts.content_types).with(this).then(get_all_content).tap(set_urls).then(transform_entries).then(sort_entries).tap(set_locals).tap(compile_entries).tap(write_entries); + } + }]); + + return RootsContentful; + })(); +}; + +/* + * Configures content types set in app.coffee. Sets default values if + * optional config options are missing. + * @param {Array} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of configured content types + */ +function configure_content(types) { + if (_lodash2.default.isPlainObject(types)) { + types = reconfigure_alt_type_config(types); + } + return _when2.default.map(types, function (t) { + if (!t.id) { + return _when2.default.reject(errors.no_type_id); + } + t.filters = t.filters || {}; + if (!t.name || t.template && !t.path) { + return (0, _when2.default)(client.contentType(t.id).then(function (res) { + t.name = t.name || (0, _pluralize2.default)((0, _string2.default)(res.name).toLowerCase().underscore().s); + if (t.template) { + t.path = t.path || function (e) { + return t.name + '/' + (0, _string2.default)(e[res.displayField]).slugify().s; + }; + } + return t; + })); + } + return _when2.default.resolve(t); + }); +} + +/* + * Reconfigures content types set in app.coffee using an object instead of + * an array. The keys of the object set as the `name` option in the config + * @param {Object} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of content types + */ +function reconfigure_alt_type_config(types) { + return _lodash2.default.reduce(types, function (res, type, k) { + type.name = k; + res.push(type); + return res; + }, []); +} + +/* + * Fetches data from Contentful for content types, and formats the raw data + * @param {Array} types - configured content_type objects + * @return {Promise} - returns formatted locals object with all content + */ +function get_all_content(types) { + return _when2.default.map(types, function (t) { + return fetch_content(t).then(format_content).then(function (c) { + return t.content = c; + }).yield(t); + }); +} + +/* + * Fetch entries for a single content type object + * @param {Object} type - content type object + * @return {Promise} - returns response from Contentful API + */ +function fetch_content(type) { + return (0, _when2.default)(client.entries(_lodash2.default.merge(type.filters, { + content_type: type.id, + includes: 10 + }))); +} + +/* + * Formats raw response from Contentful + * @param {Object} content - entries API response for a content type + * @return {Promise} - returns formatted content type entries object + */ +function format_content(content) { + return _when2.default.map(content, format_entry); +} + +/* + * Formats a single entry object from Contentful API response + * @param {Object} e - single entry object from API response + * @return {Promise} - returns formatted entry object + */ +function format_entry(e) { + if (_lodash2.default.has(e.fields, 'sys')) { + return _when2.default.reject(errors.sys_conflict); + } + return _lodash2.default.assign(_lodash2.default.omit(e, 'fields'), e.fields); +} + +/* + * Sets `_url` and `_urls` properties on content with single entry views + * `_url` takes the value `null` if the content type's custom path function + * returns multiple paths + * @param {Array} types - content type objects + * return {Promise} - promise when urls are set + */ +function set_urls(types) { + return _when2.default.map(types, function (t) { + if (t.template) { + return _when2.default.map(t.content, function (entry) { + var paths = t.path(entry); + if (_lodash2.default.isString(paths)) { + paths = [paths]; + } + entry._urls = paths.map(function (p) { + return '/' + p + '.html'; + }); + return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; + }); + } + }); +} + +/* + * Builds locals object from types objects with content + * @param {Array} types - populated content type objects + * @return {Promise} - promise for when complete + */ +function set_locals(types) { + var _this = this; + + return _when2.default.map(types, function (t) { + return _this.config.locals.contentful[t.name] = t.content; + }); +} + +/* + * Transforms every type with content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function transform_entries(types) { + return _when2.default.map(types, function (t) { + if (t.transform) { + return _when2.default.map(t.content, function (entry) { + return (0, _when2.default)(entry, t.transform); + }); + } + return _when2.default.resolve(t); + }); +} + +/* + * Sort every type content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function sort_entries(types) { + return _when2.default.map(types, function (t) { + if (t.sort) { + // in order to sort promises we have to resolve them first. + return _when2.default.all(t.content).then(function (data) { + return t.content = data.sort(t.sort); + }); + } + return _when2.default.resolve(t); + }); +} + +/* + * Compiles single entry views for content types + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function compile_entries(types) { + var _this2 = this; + + return _when2.default.map(types, function (t) { + if (!t.template) { + return _when2.default.resolve(); + } + return _when2.default.map(t.content, function (entry) { + var template = _path2.default.join(_this2.roots.root, t.template); + var compiler = _lodash2.default.find(_this2.roots.config.compilers, function (c) { + return _lodash2.default.contains(c.extensions, _path2.default.extname(template).substring(1)); + }); + return _when2.default.map(entry._urls, function (url) { + _this2.roots.config.locals.entry = _lodash2.default.assign({}, entry, { _url: url }); + return compiler.renderFile(template, _this2.roots.config.locals).then(function (res) { + _this2.roots.config.locals.entry = null; + return _this2.util.write(url, res.result); + }); + }); + }); + }); +} + +/* + * Writes all data for type with content as json + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function write_entries(types) { + var _this3 = this; + + return _when2.default.map(types, function (t) { + if (!t.write) { + return _when2.default.resolve(); + } + return _this3.util.write(t.write, JSON.stringify(t.content)); + }); +} + +/* + * View helper for accessing the actual url from a Contentful asset + * and appends any query string params + * @param {Object} asset - Asset object returned from Contentful API + * @param {Object} opts - Query string params to append to the URL + * @return {String} - URL string for the asset + */ +function asset_view_helper() { + var asset = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var params = arguments[1]; + + asset.fields = asset.fields || {}; + asset.fields.file = assets.fields.file || {}; + var url = asset.fields.file.url; + if (params) { + return url + '?' + _querystring2.default.stringify(params); + } + return url; +} diff --git a/lib/index.coffee b/lib-coffee/index.coffee similarity index 100% rename from lib/index.coffee rename to lib-coffee/index.coffee diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..380f05e --- /dev/null +++ b/lib/index.js @@ -0,0 +1,277 @@ + +import _ from 'lodash'; +import W from 'when'; +import S from 'string'; +import path from 'path'; +import contentful from 'contentful'; +import pluralize from 'pluralize'; +import RootsUtil from 'roots-util'; +import querystring from 'querystring'; + +const errors = { + no_token: 'Missing required options for roots-contentful. Please ensure `access_token` and `space_id` are present.', + no_type_id: 'One or more of your content types is missing an `id` value', + sys_conflict: 'One of your content types has `sys` as a field. This is reserved for storing Contentful system metadata, please rename this field to a different value.' +}; + +const hosts = { + develop: 'preview.contentful.com', + production: 'cdn.contentful.com' +}; + +/* TODO: change to `export default` when tests are rewritten in ES6/7 */ +module.exports = function(opts) { + if (!opts.access_token && opts.space_id) { + throw new Error(errors.no_token); + } + let client = contentful.createClient({ + host: + hosts[process.env.CONTENTFUL_ENV] || + (opts.preview ? hosts.develop : null) || + hosts.production, + accessToken: opts.access_token, + space: opts.space_id + }); + return class RootsContentful { + constructor(roots) { + this.util = new RootsUtil(roots); + this.roots.config.locals = this.roots.config.locals || {}; + this.roots.config.locals.contentful = this.roots.config.locals.contentful || {}; + this.roots.config.locals.asset = asset_view_helper; + } + setup() { + return configure_content(opts.content_types).with(this) + .then(get_all_content) + .tap(set_urls) + .then(transform_entries) + .then(sort_entries) + .tap(set_locals) + .tap(compile_entries) + .tap(write_entries) + } + } +} + +/* + * Configures content types set in app.coffee. Sets default values if + * optional config options are missing. + * @param {Array} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of configured content types + */ +function configure_content(types) { + if (_.isPlainObject(types)) { + types = reconfigure_alt_type_config(types); + } + return W.map(types, t => { + if (!t.id) { + return W.reject(errors.no_type_id); + } + t.filters = t.filters || {}; + if (!t.name || (t.template && !t.path)) { + return W( + client.contentType(t.id) + .then(res => { + t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s); + if (t.template) { + t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`); + } + return t + }) + ) + } + return W.resolve(t); + }); +} + +/* + * Reconfigures content types set in app.coffee using an object instead of + * an array. The keys of the object set as the `name` option in the config + * @param {Object} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of content types + */ +function reconfigure_alt_type_config(types) { + return _.reduce( + types, + ((res, type, k) => { + type.name = k; + res.push(type); + return res; + }), + [] + ); +} + +/* + * Fetches data from Contentful for content types, and formats the raw data + * @param {Array} types - configured content_type objects + * @return {Promise} - returns formatted locals object with all content + */ +function get_all_content(types) { + return W.map(types, t => { + return fetch_content(t) + .then(format_content) + .then(c => t.content = c) + .yield(t) + }); +} + +/* + * Fetch entries for a single content type object + * @param {Object} type - content type object + * @return {Promise} - returns response from Contentful API + */ +function fetch_content(type) { + return W(client.entries( + _.merge( + type.filters, + { + content_type: type.id, + includes: 10 + } + ) + )); +} + +/* + * Formats raw response from Contentful + * @param {Object} content - entries API response for a content type + * @return {Promise} - returns formatted content type entries object + */ +function format_content(content) { + return W.map(content, format_entry); +} + +/* + * Formats a single entry object from Contentful API response + * @param {Object} e - single entry object from API response + * @return {Promise} - returns formatted entry object + */ +function format_entry(e) { + if (_.has(e.fields, 'sys')) { + return W.reject(errors.sys_conflict); + } + return _.assign(_.omit(e, 'fields'), e.fields); +} + +/* + * Sets `_url` and `_urls` properties on content with single entry views + * `_url` takes the value `null` if the content type's custom path function + * returns multiple paths + * @param {Array} types - content type objects + * return {Promise} - promise when urls are set + */ +function set_urls(types) { + return W.map(types, t => { + if (t.template) { + return W.map(t.content, entry => { + let paths = t.path(entry); + if (_.isString(paths)) { + paths = [paths]; + } + entry._urls = paths.map(p => `/${p}.html`); + return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; + }); + } + }) +} + +/* + * Builds locals object from types objects with content + * @param {Array} types - populated content type objects + * @return {Promise} - promise for when complete + */ +function set_locals(types) { + return W.map(types, t => { + return this.config.locals.contentful[t.name] = t.content + }); +} + +/* + * Transforms every type with content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function transform_entries(types) { + return W.map(types, t => { + if (t.transform) { + return W.map(t.content, entry => { + return W(entry, t.transform); + }); + } + return W.resolve(t); + }); +} + +/* + * Sort every type content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function sort_entries(types) { + return W.map(types, t => { + if (t.sort) { + // in order to sort promises we have to resolve them first. + return W.all(t.content) + .then(data => t.content = data.sort(t.sort)); + } + return W.resolve(t); + }); +} + +/* + * Compiles single entry views for content types + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function compile_entries(types) { + return W.map(types, t => { + if (!t.template) { + return W.resolve(); + } + return W.map(t.content, entry => { + let template = path.join(this.roots.root, t.template); + let compiler = _.find(this.roots.config.compilers, c => { + return _.contains(c.extensions, path.extname(template).substring(1)); + }); + return W.map(entry._urls, url => { + this.roots.config.locals.entry = _.assign({}, entry, { _url: url }); + return compiler.renderFile(template, this.roots.config.locals) + .then(res => { + this.roots.config.locals.entry = null; + return this.util.write(url, res.result); + }); + }); + }); + }); +} + +/* + * Writes all data for type with content as json + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function write_entries(types) { + return W.map(types, t => { + if (!t.write) { + return W.resolve(); + } + return this.util.write(t.write, JSON.stringify(t.content)); + }); +} + +/* + * View helper for accessing the actual url from a Contentful asset + * and appends any query string params + * @param {Object} asset - Asset object returned from Contentful API + * @param {Object} opts - Query string params to append to the URL + * @return {String} - URL string for the asset + */ +function asset_view_helper(asset = {}, params) { + asset.fields = asset.fields || {}; + asset.fields.file = assets.fields.file || {}; + let url = asset.fields.file.url; + if (params) { + return `${url}?${querystring.stringify(params)}`; + } + return url; +} diff --git a/package.json b/package.json index 4466728..da975ea 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { - "name": "roots-contentful", - "description": "An extension for using roots with the API-driven Contentful CMS", + "name": "roots-contentful-es2016", + "description": "An extension for using roots with the API-driven Contentful CMS, written in ES2016", "version": "0.0.8", - "author": "joshrowley", + "author": "declandewet", + "contributors": {}, "bugs": { - "url": "https://github.com/carrot/roots-contentful/issues" + "url": "https://github.com/declandewet/roots-contentful-es2016/issues" }, "dependencies": { "contentful": "^1.1.5", @@ -15,6 +16,10 @@ "when": "3.7.x" }, "devDependencies": { + "babel-cli": "^6.3.15", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13", + "babel-register": "^6.3.13", "chai": "3.4.1", "chai-as-promised": "5.1.0", "coffee-script": "1.10.0", @@ -25,9 +30,10 @@ "roots": "3.1.0" }, "directories": { + "lib": "lib", "test": "test" }, - "homepage": "https://github.com/carrot/roots-contentful", + "homepage": "https://github.com/declandewet/roots-contentful-es2016", "keywords": [ "roots-extension" ], @@ -35,11 +41,19 @@ "main": "lib", "repository": { "type": "git", - "url": "https://github.com/carrot/roots-contentful.git" + "url": "https://github.com/declandewet/roots-contentful-es2016.git" }, "scripts": { - "coverage": "make build; NODE_ENV=test istanbul cover ./node_modules/mocha/bin/_mocha; make unbuild; open coverage/lcov-report/index.html;", - "coveralls": "make build; make coveralls; make unbuild;", - "test": "mocha" + "prebuild": "mv lib src", + "build": "babel src -d lib", + "preunbuild": "rm -rf lib", + "unbuild": "mv src lib", + "prerelease": "npm run build", + "release": "npm publish", + "postrelease": "npm run unbuild", + "test": "mocha", + "coverage": "babel-node ./node_modules/.bin/isparta cover _mocha", + "precoveralls": "npm run coverage", + "coveralls": "coveralls < coverage/lcov.info" } } diff --git a/test/mocha.opts b/test/mocha.opts index abe3301..1666ffb 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,4 +1,4 @@ --reporter spec ---compilers coffee:coffee-script/register +--compilers js:babel-register,coffee:coffee-script/register --require test/support/helpers --timeout 30000 From f166670a156260dcbb9f035b4707c2576b2c0949 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 9 Dec 2015 00:49:17 +0200 Subject: [PATCH 02/58] broken syntax fixes --- lib/index.js | 447 ++++++++++++++++++++++++++------------------------- 1 file changed, 226 insertions(+), 221 deletions(-) diff --git a/lib/index.js b/lib/index.js index 380f05e..72fdf31 100644 --- a/lib/index.js +++ b/lib/index.js @@ -21,9 +21,11 @@ const hosts = { /* TODO: change to `export default` when tests are rewritten in ES6/7 */ module.exports = function(opts) { - if (!opts.access_token && opts.space_id) { + + if (!(opts.access_token && opts.space_id)) { throw new Error(errors.no_token); } + let client = contentful.createClient({ host: hosts[process.env.CONTENTFUL_ENV] || @@ -32,246 +34,249 @@ module.exports = function(opts) { accessToken: opts.access_token, space: opts.space_id }); - return class RootsContentful { - constructor(roots) { - this.util = new RootsUtil(roots); - this.roots.config.locals = this.roots.config.locals || {}; - this.roots.config.locals.contentful = this.roots.config.locals.contentful || {}; - this.roots.config.locals.asset = asset_view_helper; - } - setup() { - return configure_content(opts.content_types).with(this) - .then(get_all_content) - .tap(set_urls) - .then(transform_entries) - .then(sort_entries) - .tap(set_locals) - .tap(compile_entries) - .tap(write_entries) + + /* + * Configures content types set in app.coffee. Sets default values if + * optional config options are missing. + * @param {Array} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of configured content types + */ + function configure_content(types) { + if (_.isPlainObject(types)) { + types = reconfigure_alt_type_config(types); } + return W.map(types, t => { + if (!t.id) { + return W.reject(errors.no_type_id); + } + t.filters = t.filters || {}; + if (!t.name || (t.template && !t.path)) { + return W( + client.contentType(t.id) + .then(res => { + t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s); + if (t.template) { + t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`); + } + return t + }) + ) + } + return W.resolve(t); + }); } -} -/* - * Configures content types set in app.coffee. Sets default values if - * optional config options are missing. - * @param {Array} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of configured content types - */ -function configure_content(types) { - if (_.isPlainObject(types)) { - types = reconfigure_alt_type_config(types); + /* + * Reconfigures content types set in app.coffee using an object instead of + * an array. The keys of the object set as the `name` option in the config + * @param {Object} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of content types + */ + function reconfigure_alt_type_config(types) { + return _.reduce( + types, + ((res, type, k) => { + type.name = k; + res.push(type); + return res; + }), + [] + ); } - return W.map(types, t => { - if (!t.id) { - return W.reject(errors.no_type_id); - } - t.filters = t.filters || {}; - if (!t.name || (t.template && !t.path)) { - return W( - client.contentType(t.id) - .then(res => { - t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s); - if (t.template) { - t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`); - } - return t - }) + + /* + * Fetches data from Contentful for content types, and formats the raw data + * @param {Array} types - configured content_type objects + * @return {Promise} - returns formatted locals object with all content + */ + function get_all_content(types) { + return W.map(types, t => { + return fetch_content(t) + .then(format_content) + .then(c => t.content = c) + .yield(t) + }); + } + + /* + * Fetch entries for a single content type object + * @param {Object} type - content type object + * @return {Promise} - returns response from Contentful API + */ + function fetch_content(type) { + return W(client.entries( + _.merge( + type.filters, + { + content_type: type.id, + includes: 10 + } ) - } - return W.resolve(t); - }); -} + )); + } -/* - * Reconfigures content types set in app.coffee using an object instead of - * an array. The keys of the object set as the `name` option in the config - * @param {Object} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of content types - */ -function reconfigure_alt_type_config(types) { - return _.reduce( - types, - ((res, type, k) => { - type.name = k; - res.push(type); - return res; - }), - [] - ); -} + /* + * Formats raw response from Contentful + * @param {Object} content - entries API response for a content type + * @return {Promise} - returns formatted content type entries object + */ + function format_content(content) { + return W.map(content, format_entry); + } -/* - * Fetches data from Contentful for content types, and formats the raw data - * @param {Array} types - configured content_type objects - * @return {Promise} - returns formatted locals object with all content - */ -function get_all_content(types) { - return W.map(types, t => { - return fetch_content(t) - .then(format_content) - .then(c => t.content = c) - .yield(t) - }); -} + /* + * Formats a single entry object from Contentful API response + * @param {Object} e - single entry object from API response + * @return {Promise} - returns formatted entry object + */ + function format_entry(e) { + if (_.has(e.fields, 'sys')) { + return W.reject(errors.sys_conflict); + } + return _.assign(_.omit(e, 'fields'), e.fields); + } -/* - * Fetch entries for a single content type object - * @param {Object} type - content type object - * @return {Promise} - returns response from Contentful API - */ -function fetch_content(type) { - return W(client.entries( - _.merge( - type.filters, - { - content_type: type.id, - includes: 10 + /* + * Sets `_url` and `_urls` properties on content with single entry views + * `_url` takes the value `null` if the content type's custom path function + * returns multiple paths + * @param {Array} types - content type objects + * return {Promise} - promise when urls are set + */ + function set_urls(types) { + return W.map(types, t => { + if (t.template) { + return W.map(t.content, entry => { + let paths = t.path(entry); + if (_.isString(paths)) { + paths = [paths]; + } + entry._urls = paths.map(p => `/${p}.html`); + return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; + }); } - ) - )); -} - -/* - * Formats raw response from Contentful - * @param {Object} content - entries API response for a content type - * @return {Promise} - returns formatted content type entries object - */ -function format_content(content) { - return W.map(content, format_entry); -} + }) + } -/* - * Formats a single entry object from Contentful API response - * @param {Object} e - single entry object from API response - * @return {Promise} - returns formatted entry object - */ -function format_entry(e) { - if (_.has(e.fields, 'sys')) { - return W.reject(errors.sys_conflict); + /* + * Builds locals object from types objects with content + * @param {Array} types - populated content type objects + * @return {Promise} - promise for when complete + */ + function set_locals(types) { + return W.map(types, t => { + console.log(t) + return this.roots.config.locals.contentful[t.name] = t.content + }); } - return _.assign(_.omit(e, 'fields'), e.fields); -} -/* - * Sets `_url` and `_urls` properties on content with single entry views - * `_url` takes the value `null` if the content type's custom path function - * returns multiple paths - * @param {Array} types - content type objects - * return {Promise} - promise when urls are set - */ -function set_urls(types) { - return W.map(types, t => { - if (t.template) { - return W.map(t.content, entry => { - let paths = t.path(entry); - if (_.isString(paths)) { - paths = [paths]; - } - entry._urls = paths.map(p => `/${p}.html`); - return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; - }); - } - }) -} + /* + * Transforms every type with content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ + function transform_entries(types) { + return W.map(types, t => { + if (t.transform) { + return W.map(t.content, entry => { + return W(entry, t.transform); + }); + } + return W.resolve(t); + }); + } -/* - * Builds locals object from types objects with content - * @param {Array} types - populated content type objects - * @return {Promise} - promise for when complete - */ -function set_locals(types) { - return W.map(types, t => { - return this.config.locals.contentful[t.name] = t.content - }); -} + /* + * Sort every type content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ + function sort_entries(types) { + return W.map(types, t => { + if (t.sort) { + // in order to sort promises we have to resolve them first. + return W.all(t.content) + .then(data => t.content = data.sort(t.sort)); + } + return W.resolve(t); + }); + } -/* - * Transforms every type with content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function transform_entries(types) { - return W.map(types, t => { - if (t.transform) { + /* + * Compiles single entry views for content types + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ + function compile_entries(types) { + return W.map(types, t => { + if (!t.template) { + return W.resolve(); + } return W.map(t.content, entry => { - return W(entry, t.transform); + let template = path.join(this.roots.root, t.template); + let compiler = _.find(this.roots.config.compilers, c => { + return _.contains(c.extensions, path.extname(template).substring(1)); + }); + return W.map(entry._urls, url => { + this.roots.config.locals.entry = _.assign({}, entry, { _url: url }); + return compiler.renderFile(template, this.roots.config.locals) + .then(res => { + this.roots.config.locals.entry = null; + return this.util.write(url, res.result); + }); + }); }); - } - return W.resolve(t); - }); -} - -/* - * Sort every type content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function sort_entries(types) { - return W.map(types, t => { - if (t.sort) { - // in order to sort promises we have to resolve them first. - return W.all(t.content) - .then(data => t.content = data.sort(t.sort)); - } - return W.resolve(t); - }); -} + }); + } -/* - * Compiles single entry views for content types - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function compile_entries(types) { - return W.map(types, t => { - if (!t.template) { - return W.resolve(); - } - return W.map(t.content, entry => { - let template = path.join(this.roots.root, t.template); - let compiler = _.find(this.roots.config.compilers, c => { - return _.contains(c.extensions, path.extname(template).substring(1)); - }); - return W.map(entry._urls, url => { - this.roots.config.locals.entry = _.assign({}, entry, { _url: url }); - return compiler.renderFile(template, this.roots.config.locals) - .then(res => { - this.roots.config.locals.entry = null; - return this.util.write(url, res.result); - }); - }); + /* + * Writes all data for type with content as json + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ + function write_entries(types) { + return W.map(types, t => { + if (!t.write) { + return W.resolve(); + } + return this.util.write(t.write, JSON.stringify(t.content)); }); - }); -} + } -/* - * Writes all data for type with content as json - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function write_entries(types) { - return W.map(types, t => { - if (!t.write) { - return W.resolve(); + /* + * View helper for accessing the actual url from a Contentful asset + * and appends any query string params + * @param {Object} asset - Asset object returned from Contentful API + * @param {Object} opts - Query string params to append to the URL + * @return {String} - URL string for the asset + */ + function asset_view_helper(asset = {}, params) { + asset.fields = asset.fields || {}; + asset.fields.file = asset.fields.file || {}; + let url = asset.fields.file.url; + if (params) { + return `${url}?${querystring.stringify(params)}`; } - return this.util.write(t.write, JSON.stringify(t.content)); - }); -} + return url; + } -/* - * View helper for accessing the actual url from a Contentful asset - * and appends any query string params - * @param {Object} asset - Asset object returned from Contentful API - * @param {Object} opts - Query string params to append to the URL - * @return {String} - URL string for the asset - */ -function asset_view_helper(asset = {}, params) { - asset.fields = asset.fields || {}; - asset.fields.file = assets.fields.file || {}; - let url = asset.fields.file.url; - if (params) { - return `${url}?${querystring.stringify(params)}`; + return class RootsContentful { + constructor(roots) { + this.roots = roots; + this.util = new RootsUtil(this.roots); + this.roots.config.locals = this.roots.config.locals || {}; + this.roots.config.locals.contentful = this.roots.config.locals.contentful || {}; + this.roots.config.locals.asset = asset_view_helper; + } + setup() { + return configure_content(opts.content_types).with(this) + .then(get_all_content) + .tap(set_urls) + .then(transform_entries) + .then(sort_entries) + .tap(set_locals) + .tap(compile_entries) + .tap(write_entries); + } } - return url; } From 7a82fd804477ddc5cff9542520e33d170bd85dcb Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 9 Dec 2015 16:37:57 +0200 Subject: [PATCH 03/58] get sort tests passing --- lib/index.js | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/index.js b/lib/index.js index 72fdf31..67c25d7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -73,15 +73,11 @@ module.exports = function(opts) { * @return {Promise} - returns an array of content types */ function reconfigure_alt_type_config(types) { - return _.reduce( - types, - ((res, type, k) => { - type.name = k; - res.push(type); - return res; - }), - [] - ); + return _.reduce(types, (res, type, k) => { + type.name = k; + res.push(type); + return res; + }, []); } /* @@ -105,13 +101,10 @@ module.exports = function(opts) { */ function fetch_content(type) { return W(client.entries( - _.merge( - type.filters, - { - content_type: type.id, - includes: 10 - } - ) + _.merge(type.filters, { + content_type: type.id, + include: 10 + }) )); } @@ -165,7 +158,6 @@ module.exports = function(opts) { */ function set_locals(types) { return W.map(types, t => { - console.log(t) return this.roots.config.locals.contentful[t.name] = t.content }); } @@ -178,7 +170,7 @@ module.exports = function(opts) { function transform_entries(types) { return W.map(types, t => { if (t.transform) { - return W.map(t.content, entry => { + W.map(t.content, entry => { return W(entry, t.transform); }); } @@ -195,7 +187,7 @@ module.exports = function(opts) { return W.map(types, t => { if (t.sort) { // in order to sort promises we have to resolve them first. - return W.all(t.content) + W.all(t.content) .then(data => t.content = data.sort(t.sort)); } return W.resolve(t); From 5ae47c1ab356a99aab770996fba7be438c7ea58d Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 9 Dec 2015 17:18:43 +0200 Subject: [PATCH 04/58] remove unneccessary files, update README, LICENSE and contributors, complete coverage setup --- build.js | 302 ---------------------------------------- lib-coffee/index.coffee | 228 ------------------------------ license.md | 2 +- package.json | 39 +++++- readme.md | 14 +- 5 files changed, 47 insertions(+), 538 deletions(-) delete mode 100644 build.js delete mode 100644 lib-coffee/index.coffee diff --git a/build.js b/build.js deleted file mode 100644 index fc8a2d4..0000000 --- a/build.js +++ /dev/null @@ -1,302 +0,0 @@ -'use strict'; - -var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); -//import contentful from 'contentful'; - -var _lodash = require('lodash'); - -var _lodash2 = _interopRequireDefault(_lodash); - -var _when = require('when'); - -var _when2 = _interopRequireDefault(_when); - -var _string = require('string'); - -var _string2 = _interopRequireDefault(_string); - -var _path = require('path'); - -var _path2 = _interopRequireDefault(_path); - -var _pluralize = require('pluralize'); - -var _pluralize2 = _interopRequireDefault(_pluralize); - -var _rootsUtil = require('roots-util'); - -var _rootsUtil2 = _interopRequireDefault(_rootsUtil); - -var _querystring = require('querystring'); - -var _querystring2 = _interopRequireDefault(_querystring); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var errors = { - no_token: 'Missing required options for roots-contentful. Please ensure `access_token` and `space_id` are present.', - no_type_id: 'One or more of your content types is missing an `id` value', - sys_conflict: 'One of your content types has `sys` as a field. This is reserved for storing Contentful system metadata, please rename this field to a different value.' -}; - -var hosts = { - develop: 'preview.contentful.com', - production: 'cdn.contenful.com' -}; - -/* TODO: change to `export default` when tests are rewritten in ES6/7 */ -module.exports = function (opts) { - if (!opts.access_token && opts.space_id) { - throw new Error(errors.no_token); - } - var client = contenful.createClient({ - host: hosts[process.env.CONTENTFUL_ENV] || (opts.preview ? hosts.develop : null) || hosts.production, - accessToken: opts.access_token, - space: opts.space_id - }); - return (function () { - function RootsContentful(roots) { - _classCallCheck(this, RootsContentful); - - this.util = new _rootsUtil2.default(roots); - this.roots.config.locals = this.roots.config.locals || {}; - this.roots.config.locals.contentful = this.roots.config.locals.contentful || {}; - this.roots.config.locals.asset = asset_view_helper; - } - - _createClass(RootsContentful, [{ - key: 'setup', - value: function setup() { - return configure_content(opts.content_types).with(this).then(get_all_content).tap(set_urls).then(transform_entries).then(sort_entries).tap(set_locals).tap(compile_entries).tap(write_entries); - } - }]); - - return RootsContentful; - })(); -}; - -/* - * Configures content types set in app.coffee. Sets default values if - * optional config options are missing. - * @param {Array} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of configured content types - */ -function configure_content(types) { - if (_lodash2.default.isPlainObject(types)) { - types = reconfigure_alt_type_config(types); - } - return _when2.default.map(types, function (t) { - if (!t.id) { - return _when2.default.reject(errors.no_type_id); - } - t.filters = t.filters || {}; - if (!t.name || t.template && !t.path) { - return (0, _when2.default)(client.contentType(t.id).then(function (res) { - t.name = t.name || (0, _pluralize2.default)((0, _string2.default)(res.name).toLowerCase().underscore().s); - if (t.template) { - t.path = t.path || function (e) { - return t.name + '/' + (0, _string2.default)(e[res.displayField]).slugify().s; - }; - } - return t; - })); - } - return _when2.default.resolve(t); - }); -} - -/* - * Reconfigures content types set in app.coffee using an object instead of - * an array. The keys of the object set as the `name` option in the config - * @param {Object} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of content types - */ -function reconfigure_alt_type_config(types) { - return _lodash2.default.reduce(types, function (res, type, k) { - type.name = k; - res.push(type); - return res; - }, []); -} - -/* - * Fetches data from Contentful for content types, and formats the raw data - * @param {Array} types - configured content_type objects - * @return {Promise} - returns formatted locals object with all content - */ -function get_all_content(types) { - return _when2.default.map(types, function (t) { - return fetch_content(t).then(format_content).then(function (c) { - return t.content = c; - }).yield(t); - }); -} - -/* - * Fetch entries for a single content type object - * @param {Object} type - content type object - * @return {Promise} - returns response from Contentful API - */ -function fetch_content(type) { - return (0, _when2.default)(client.entries(_lodash2.default.merge(type.filters, { - content_type: type.id, - includes: 10 - }))); -} - -/* - * Formats raw response from Contentful - * @param {Object} content - entries API response for a content type - * @return {Promise} - returns formatted content type entries object - */ -function format_content(content) { - return _when2.default.map(content, format_entry); -} - -/* - * Formats a single entry object from Contentful API response - * @param {Object} e - single entry object from API response - * @return {Promise} - returns formatted entry object - */ -function format_entry(e) { - if (_lodash2.default.has(e.fields, 'sys')) { - return _when2.default.reject(errors.sys_conflict); - } - return _lodash2.default.assign(_lodash2.default.omit(e, 'fields'), e.fields); -} - -/* - * Sets `_url` and `_urls` properties on content with single entry views - * `_url` takes the value `null` if the content type's custom path function - * returns multiple paths - * @param {Array} types - content type objects - * return {Promise} - promise when urls are set - */ -function set_urls(types) { - return _when2.default.map(types, function (t) { - if (t.template) { - return _when2.default.map(t.content, function (entry) { - var paths = t.path(entry); - if (_lodash2.default.isString(paths)) { - paths = [paths]; - } - entry._urls = paths.map(function (p) { - return '/' + p + '.html'; - }); - return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; - }); - } - }); -} - -/* - * Builds locals object from types objects with content - * @param {Array} types - populated content type objects - * @return {Promise} - promise for when complete - */ -function set_locals(types) { - var _this = this; - - return _when2.default.map(types, function (t) { - return _this.config.locals.contentful[t.name] = t.content; - }); -} - -/* - * Transforms every type with content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function transform_entries(types) { - return _when2.default.map(types, function (t) { - if (t.transform) { - return _when2.default.map(t.content, function (entry) { - return (0, _when2.default)(entry, t.transform); - }); - } - return _when2.default.resolve(t); - }); -} - -/* - * Sort every type content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function sort_entries(types) { - return _when2.default.map(types, function (t) { - if (t.sort) { - // in order to sort promises we have to resolve them first. - return _when2.default.all(t.content).then(function (data) { - return t.content = data.sort(t.sort); - }); - } - return _when2.default.resolve(t); - }); -} - -/* - * Compiles single entry views for content types - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function compile_entries(types) { - var _this2 = this; - - return _when2.default.map(types, function (t) { - if (!t.template) { - return _when2.default.resolve(); - } - return _when2.default.map(t.content, function (entry) { - var template = _path2.default.join(_this2.roots.root, t.template); - var compiler = _lodash2.default.find(_this2.roots.config.compilers, function (c) { - return _lodash2.default.contains(c.extensions, _path2.default.extname(template).substring(1)); - }); - return _when2.default.map(entry._urls, function (url) { - _this2.roots.config.locals.entry = _lodash2.default.assign({}, entry, { _url: url }); - return compiler.renderFile(template, _this2.roots.config.locals).then(function (res) { - _this2.roots.config.locals.entry = null; - return _this2.util.write(url, res.result); - }); - }); - }); - }); -} - -/* - * Writes all data for type with content as json - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ -function write_entries(types) { - var _this3 = this; - - return _when2.default.map(types, function (t) { - if (!t.write) { - return _when2.default.resolve(); - } - return _this3.util.write(t.write, JSON.stringify(t.content)); - }); -} - -/* - * View helper for accessing the actual url from a Contentful asset - * and appends any query string params - * @param {Object} asset - Asset object returned from Contentful API - * @param {Object} opts - Query string params to append to the URL - * @return {String} - URL string for the asset - */ -function asset_view_helper() { - var asset = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var params = arguments[1]; - - asset.fields = asset.fields || {}; - asset.fields.file = assets.fields.file || {}; - var url = asset.fields.file.url; - if (params) { - return url + '?' + _querystring2.default.stringify(params); - } - return url; -} diff --git a/lib-coffee/index.coffee b/lib-coffee/index.coffee deleted file mode 100644 index c4a713f..0000000 --- a/lib-coffee/index.coffee +++ /dev/null @@ -1,228 +0,0 @@ -_ = require 'lodash' -W = require 'when' -S = require 'string' -path = require 'path' -contentful = require 'contentful' -pluralize = require 'pluralize' -RootsUtil = require 'roots-util' -querystring = require 'querystring' - -errors = - no_token: 'Missing required options for roots-contentful. Please ensure - `access_token` and `space_id` are present.' - no_type_id: 'One or more of your content types is missing an `id` value' - sys_conflict: 'One of your content types has `sys` as a field. This is - reserved for storing Contentful system metadata, please rename this field to - a different value.' - -hosts = - develop: 'preview.contentful.com' - production: 'cdn.contentful.com' - -module.exports = (opts) -> - # throw error if missing required config - if not (opts.access_token && opts.space_id) - throw new Error errors.no_token - - # setup contentful api client - client = contentful.createClient - host: - hosts[process.env.CONTENTFUL_ENV] || - (hosts.develop if opts.preview) || - hosts.production - accessToken: opts.access_token - space: opts.space_id - - class RootsContentful - constructor: (@roots) -> - @util = new RootsUtil(@roots) - @roots.config.locals ?= {} - @roots.config.locals.contentful ?= {} - @roots.config.locals.asset = asset_view_helper - - setup: -> - configure_content(opts.content_types).with(@) - .then(get_all_content) - .tap(set_urls) - .then(transform_entries) - .then(sort_entries) - .tap(set_locals) - .tap(compile_entries) - .tap(write_entries) - - ###* - * Configures content types set in app.coffee. Sets default values if - * optional config options are missing. - * @param {Array} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of configured content types - ### - - configure_content = (types) -> - if _.isPlainObject(types) then types = reconfigure_alt_type_config(types) - W.map types, (t) -> - if not t.id then return W.reject(errors.no_type_id) - t.filters ?= {} - if (not t.name || (t.template && not t.path)) - return W client.contentType(t.id).then (res) -> - t.name ?= pluralize(S(res.name).toLowerCase().underscore().s) - if t.template - t.path ?= (e) -> "#{t.name}/#{S(e[res.displayField]).slugify().s}" - return t - return W.resolve(t) - - ###* - * Reconfigures content types set in app.coffee using an object instead of - * an array. The keys of the object set as the `name` option in the config - * @param {Object} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of content types - ### - - reconfigure_alt_type_config = (types) -> - _.reduce types, (res, type, k) -> - type.name = k - res.push(type) - res - , [] - - ###* - * Fetches data from Contentful for content types, and formats the raw data - * @param {Array} types - configured content_type objects - * @return {Promise} - returns formatted locals object with all content - ### - - get_all_content = (types) -> - W.map types, (t) -> - fetch_content(t) - .then(format_content) - .then((c) -> t.content = c) - .yield(t) - - ###* - * Fetch entries for a single content type object - * @param {Object} type - content type object - * @return {Promise} - returns response from Contentful API - ### - - fetch_content = (type) -> - W( - client.entries( - _.merge(type.filters, content_type: type.id, include: 10) - ) - ) - - ###* - * Formats raw response from Contentful - * @param {Object} content - entries API response for a content type - * @return {Promise} - returns formatted content type entries object - ### - - format_content = (content) -> W.map(content, format_entry) - - ###* - * Formats a single entry object from Contentful API response - * @param {Object} e - single entry object from API response - * @return {Promise} - returns formatted entry object - ### - - format_entry = (e) -> - if _.has(e.fields, 'sys') then return W.reject(errors.sys_conflict) - _.assign(_.omit(e, 'fields'), e.fields) - - ###* - * Sets `_url` and `_urls` properties on content with single entry views - * `_url` takes the value `null` if the content type's custom path function - * returns multiple paths - * @param {Array} types - content type objects - * return {Promise} - promise when urls are set - ### - - set_urls = (types) -> - W.map types, (t) -> - if t.template then W.map t.content, (entry) -> - paths = t.path(entry) - paths = [paths] if _.isString(paths) - entry._urls = ("/#{p}.html" for p in paths) - entry._url = if entry._urls.length is 1 then entry._urls[0] else null - - ###* - * Builds locals object from types objects with content - * @param {Array} types - populated content type objects - * @return {Promise} - promise for when complete - ### - - set_locals = (types) -> - W.map types, (t) => - @roots.config.locals.contentful[t.name] = t.content - - ###* - * Transforms every type with content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - ### - - transform_entries = (types) -> - W.map types, (t) => - if t.transform - W.map t.content, (entry) => - W(entry, t.transform) - W.resolve(t) - - ###* - * Sort every type content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - ### - - sort_entries = (types) -> - W.map types, (t) => - if t.sort - # Unfortunately, in order to sort promises we have to resolve them first. - W.all(t.content).then (data) => - t.content = data.sort(t.sort) - W.resolve(t) - - ###* - * Compiles single entry views for content types - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - ### - - compile_entries = (types) -> - W.map types, (t) => - if not t.template then return W.resolve() - W.map t.content, (entry) => - template = path.join(@roots.root, t.template) - compiler = _.find @roots.config.compilers, (c) -> - _.contains(c.extensions, path.extname(template).substring(1)) - W.map entry._urls, (url) => - @roots.config.locals.entry = _.assign({}, entry, { _url: url }) - compiler.renderFile(template, @roots.config.locals) - .then((res) => - @roots.config.locals.entry = null - @util.write(url, res.result) - ) - - ###* - * Writes all data for type with content as json - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - ### - - write_entries = (types) -> - W.map types, (t) => - if not t.write then return W.resolve() - @util.write(t.write, JSON.stringify(t.content)) - - ###* - * View helper for accessing the actual url from a Contentful asset - * and appends any query string params - * @param {Object} asset - Asset object returned from Contentful API - * @param {Object} opts - Query string params to append to the URL - * @return {String} - URL string for the asset - ### - - asset_view_helper = (asset = {}, params) -> - asset.fields ?= {} - asset.fields.file ?= {} - url = asset.fields.file.url - if params then "#{url}?#{querystring.stringify(params)}" else url diff --git a/license.md b/license.md index 8ea2c7d..16caa00 100644 --- a/license.md +++ b/license.md @@ -1,7 +1,7 @@ License (MIT) ------------- -Copyright (c) 2014 Josh Rowley, Carrot Creative +Copyright (c) 2015 Carrot Creative Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/package.json b/package.json index da975ea..0327085 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,42 @@ "name": "roots-contentful-es2016", "description": "An extension for using roots with the API-driven Contentful CMS, written in ES2016", "version": "0.0.8", - "author": "declandewet", - "contributors": {}, + "author": "Carrot Creative", + "contributors": [ + { + "name": "joshrowley" + }, + { + "name": "slang800" + }, + { + "name": "axelboc" + }, + { + "name": "hhsnopek" + }, + { + "name": "kylemac" + }, + { + "name": "jenius" + }, + { + "name": "teresus" + }, + { + "name": "nporteschaikin" + }, + { + "name": "pxlpnk" + }, + { + "name": "t0pth4t" + }, + { + "name": "declandewet" + } + ], "bugs": { "url": "https://github.com/declandewet/roots-contentful-es2016/issues" }, @@ -24,6 +58,7 @@ "chai-as-promised": "5.1.0", "coffee-script": "1.10.0", "coveralls": "2.x", + "isparta": "^4.0.0", "istanbul": "0.4.1", "mocha": "2.3.4", "mockery": "1.4.x", diff --git a/readme.md b/readme.md index 471eed4..b814ea1 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,14 @@ # Roots Contentful -[![npm](http://img.shields.io/npm/v/roots-contentful.svg?style=flat)](https://badge.fury.io/js/roots-contentful) [![tests](http://img.shields.io/travis/carrot/roots-contentful/master.svg?style=flat)](https://travis-ci.org/carrot/roots-contentful) [![dependencies](http://img.shields.io/gemnasium/carrot/roots-contentful.svg?style=flat)](https://gemnasium.com/carrot/roots-contentful) -[![devDependencies](https://img.shields.io/david/dev/carrot/roots-contentful.svg)](https://gemnasium.com/carrot/roots-contentful) -[![Coverage Status](https://img.shields.io/coveralls/carrot/roots-contentful.svg)](https://coveralls.io/r/carrot/roots-contentful?branch=master) - -An extension for using [roots](https://github.com/jenius/roots) with the Contentful CMS API. +Info | Badges +-----|------- +Version | [![github release](https://img.shields.io/github/release/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://github.com/declandewet/roots-contentful-es2016/releases/latest) [![npm version](https://img.shields.io/npm/v/roots-contentful-es2016.svg?style=flat-square)](http://npmjs.org/package/roots-contentful-es2016) +License | [![npm license](https://img.shields.io/npm/l/roots-contentful-es2016.svg?style=flat-square)](https://github.com/declandewet/roots-contentful-es2016/blob/master/license.md) +Popularity | [![npm downloads](https://img.shields.io/npm/dm/roots-contentful-es2016.svg?style=flat-square)](http://npm-stat.com/charts.html?package=roots-contentful-es2016) +Testing | [![build status](https://img.shields.io/travis/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://travis-ci.org/declandewet/roots-contentful-es2016) [![test coverage](https://img.shields.io/coveralls/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://coveralls.io/github/declandewet/roots-contentful-es2016) +Quality | [![dependency status](https://img.shields.io/david/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://david-dm.org/declandewet/roots-contentful-es2016) [![dev dependency status](https://img.shields.io/david/dev/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://david-dm.org/declandewet/roots-contentful-es2016#info=devDependencies) + +An extension for using [roots](https://github.com/jenius/roots) with the Contentful CMS API, written in ES2016. > **Note:** This project is in early development, and versioning is a little different. [Read this](http://markup.im/#q4_cRZ1Q) for more details. From b58d57fa1bf88212bfd8f70be1bec7fdbd16c650 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 9 Dec 2015 17:37:01 +0200 Subject: [PATCH 05/58] update contributing.md --- Makefile | 4 ++-- contributing.md | 46 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index bb3176f..c83aa03 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ build: unbuild: npm run unbuild -coveralls: - npm run coveralls +coverage: + npm run coverage release: npm run release diff --git a/contributing.md b/contributing.md index 51a9282..2971797 100644 --- a/contributing.md +++ b/contributing.md @@ -14,11 +14,51 @@ If you are opening an issue about a bug, make sure that you include clear steps - Run `npm install` - Put in work -### Testing +### Build Commands -This project is constantly evolving, and to ensure that things are secure and working for everyone, we need to have tests. If you are adding a new feature, please make sure to add a test for it. The test suite for this project uses [mocha](http://visionmedia.github.io/mocha/) and [should](https://github.com/visionmedia/should.js/)/ +> **Note:** if your environment does not support `make` utilities, +> replace `make` with `npm run` when you type a build command. -To run the test suite, make sure you have installed mocha (`npm install mocha -g`), then you can use the `npm test` or simply `mocha` command to run the tests. +#### Testing + +This project is constantly evolving, and to ensure that things are secure and working for everyone, we need to have tests. If you are adding a new feature, please make sure to add a test for it. The test suite for this project uses [mocha](http://visionmedia.github.io/mocha/) and [chai](http://chaijs.com/) + +To run the tests: + +``` +$ npm test +``` + +To create a coverage report: + +``` +$ make coverage +``` + +#### Building + +Building the project involves compiling the ES2016 syntax down to +regular ES5 using [Babel](http://babeljs.io). This command will move +the contents of `lib/` into `src/` and then compiles `src/` into `lib/`. + +``` +$ make build +``` + +The nature of the above command requires cleanup if you want the original +source code back in it's original place. To do that, use the following: + +``` +$ make unbuild +``` + +#### Publishing to NPM + +This command will run `make build`, then `npm publish`, then `make unbuild`. + +``` +$ make release +``` ### Code Style From 2b204cf855bf2484eb5581db10f18c76e000207e Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 9 Dec 2015 17:50:29 +0200 Subject: [PATCH 06/58] 0.0.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0327085..bc32e4d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "roots-contentful-es2016", "description": "An extension for using roots with the API-driven Contentful CMS, written in ES2016", - "version": "0.0.8", + "version": "0.0.9", "author": "Carrot Creative", "contributors": [ { From 749aad82fdf06682e38a6cffdbe3cc3cdfb00088 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 10 Dec 2015 07:28:18 +0200 Subject: [PATCH 07/58] update test to minimum required es6 syntax for easier lib refactoring --- lib/index.js | 1 - package.json | 1 + test/mocha.opts | 3 +- test/support/helpers.js | 17 -- test/test.coffee | 314 ---------------------- test/test.js | 573 ++++++++++++++++++++++++++++++++++++++++ wallaby.js | 38 +++ 7 files changed, 613 insertions(+), 334 deletions(-) delete mode 100644 test/support/helpers.js delete mode 100644 test/test.coffee create mode 100644 test/test.js create mode 100644 wallaby.js diff --git a/lib/index.js b/lib/index.js index 67c25d7..4379d33 100644 --- a/lib/index.js +++ b/lib/index.js @@ -19,7 +19,6 @@ const hosts = { production: 'cdn.contentful.com' }; -/* TODO: change to `export default` when tests are rewritten in ES6/7 */ module.exports = function(opts) { if (!(opts.access_token && opts.space_id)) { diff --git a/package.json b/package.json index bc32e4d..1513323 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "when": "3.7.x" }, "devDependencies": { + "ava": "^0.7.0", "babel-cli": "^6.3.15", "babel-preset-es2015": "^6.3.13", "babel-preset-stage-0": "^6.3.13", diff --git a/test/mocha.opts b/test/mocha.opts index 1666ffb..56c13ab 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,4 +1,3 @@ --reporter spec ---compilers js:babel-register,coffee:coffee-script/register ---require test/support/helpers +--compilers js:babel-register,coffee:coffee-script/register --timeout 30000 diff --git a/test/support/helpers.js b/test/support/helpers.js deleted file mode 100644 index 6ca23af..0000000 --- a/test/support/helpers.js +++ /dev/null @@ -1,17 +0,0 @@ -var chai = require('chai'), - chai_promise = require('chai-as-promised'), - mockery = require('mockery'), - path = require('path'), - _path = path.join(__dirname, '../fixtures'), - RootsUtil = require('roots-util'), - h = new RootsUtil.Helpers({ base: _path }), - roots_contentful = require('../../lib'); - -var should = chai.should(); -chai.use(chai_promise); - -global.should = should; -global.mockery = mockery; -global._path = _path; -global.h = h; -global.roots_contentful = roots_contentful; diff --git a/test/test.coffee b/test/test.coffee deleted file mode 100644 index cc46f31..0000000 --- a/test/test.coffee +++ /dev/null @@ -1,314 +0,0 @@ -Roots = require 'roots' -S = require 'string' -W = require 'when' -_ = require 'lodash' -path = require 'path' - -# setup, teardown, and utils - -compile_fixture = (fixture_name, done) -> - @public = path.join(fixture_name, 'public') - h.project.compile(Roots, fixture_name) - -mock_contentful = (opts = {}) -> - mockery.enable - warnOnUnregistered: false - useCleanCache: true - - opts = _.defaults opts, - entries: [ - sys: - sys: 'data' - fields: - title: 'Default Title' - body: 'Default Body' - ] - content_type: - name: 'Blog Post' - displayField: 'title' - - mockery.registerMock 'contentful', - createClient: -> - contentType: -> W.resolve(opts.content_type) - entries: -> W.resolve(opts.entries) - -unmock_contentful = -> - mockery.deregisterAll() - mockery.disable() - -before (done) -> h.project.install_dependencies('*', done) - -after -> h.project.remove_folders('**/public') - -# tests - -describe 'config', -> - before -> - @title = 'Gatorade' - @body = 'Yung Lean' - mock_contentful(entries: [{fields: {title: @title, body: @body}}]) - - it 'should throw an error when missing an access token', -> - (-> compile_fixture.call(@, 'missing_token')).should.throw() - - it 'should throw an error without content type id', -> - compile_fixture.call(@, 'missing_config').should.be.rejected - - it 'allows the content type name to be set through a k/v object config', - (done) -> - compile_fixture.call(@, 'alt-content-type-config') - .with(@) - .then -> - p = path.join(@public, 'index.html') - h.file.contains(p, @title).should.be.true - h.file.contains(p, @body).should.be.true - .then(-> done()).catch(done) - - after -> unmock_contentful() - -describe 'contentful content type fields', -> - before -> mock_contentful(entries: [{fields: {sys: 'test'}}]) - - it 'should throw an error if `sys` is a field name', -> - compile_fixture.call(@, 'basic').should.be.rejected - - after -> unmock_contentful() - -describe 'basic compile', -> - before (done) -> - @title = 'Throw Some Ds' - @body = 'Rich Boy selling crick' - mock_contentful(entries: [{fields: {title: @title, body: @body}}]) - compile_fixture.call(@, 'basic').then(-> done()).catch(done) - - it 'compiles basic project', -> - p = path.join(@public, 'index.html') - h.file.exists(p).should.be.ok - - it 'has contentful data available in views', -> - p = path.join(@public, 'index.html') - h.file.contains(p, @title).should.be.true - h.file.contains(p, @body).should.be.true - - after -> unmock_contentful() - -describe 'write as json', -> - before (done) -> - @title = 'Throw Some Ds' - @body = 'Rich Boy selling crick' - mock_contentful(entries: [{fields: {title: @title, body: @body}}]) - compile_fixture.call(@, 'write').then(-> done()).catch(done) - - it 'compiles project', -> - p = path.join(@public, 'index.html') - h.file.exists(p).should.be.ok - - it 'has written data as json', -> - p = path.join(@public, 'posts.json') - h.file.exists(p).should.be.ok - h.file.contains(p, @title).should.be.true - h.file.contains(p, @body).should.be.true - - after -> unmock_contentful() - -describe 'data manipulation', -> - describe 'sort', -> - before (done) -> - @titles = ['Title C', 'Title B', 'Title A'] - @bodies = [ - 'Rich Boy selling crick', - 'Something else', - 'Nothing interesting' - ] - @entries = for index in [0..2] - {fields: {title: @titles[index], body: @bodies[index]}} - - mock_contentful(entries: @entries) - compile_fixture.call(@, 'sort').then(-> done()).catch(done) - - it 'compiles project', -> - p = path.join(@public, 'index.html') - h.file.exists(p).should.be.ok - - it 'orders data correctly for the project', -> - p = path.join(@public, 'index.html') - # Titles should be order A before B before C - h.file.contains_match( - p, - '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$' - ).should.be.true - - for body in @bodies - h.file.contains(p, body).should.be.true - - it 'has written data as json', -> - p = path.join(@public, 'posts.json') - h.file.exists(p).should.be.ok - h.file.matches_file(p, 'sort/posts_expected.json').should.be.true - - after -> unmock_contentful() - - describe 'transform', -> - before (done) -> - @titles = ['Title C', 'Title B', 'Title A'] - @bodies = [ - 'Rich Boy selling crick', - 'Something else', - 'Nothing interesting' - ] - @entries = for index in [0..2] - {fields: {title: @titles[index], body: @bodies[index]}} - - mock_contentful(entries: @entries) - compile_fixture.call(@, 'transform').then(-> done()).catch(done) - - it 'compiles project', -> - p = path.join(@public, 'index.html') - h.file.exists(p).should.be.ok - - it 'does not reorder data', -> - p = path.join(@public, 'index.html') - # Titles should be order C before B before A - h.file.contains_match( - p, - '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$' - ).should.be.true - - it 'has manipulated data correctly for the project', -> - p = path.join(@public, 'index.html') - for body in @bodies - h.file.contains(p, body).should.be.false - - it 'has written data as json', -> - p = path.join(@public, 'posts.json') - h.file.exists(p).should.be.ok - h.file.matches_file(p, 'transform/posts_expected.json').should.be.true - - after -> unmock_contentful() - -describe 'custom name for view helper local', -> - before (done) -> - @title = 'Throw Some Ds' - @body = 'Rich Boy selling crack' - mock_contentful(entries: [{fields: {title: @title, body: @body}}]) - compile_fixture.call(@, 'custom_name').then(-> done()).catch(done) - - it 'has contentful data available in views under a custom name', -> - p = path.join(@public, 'index.html') - h.file.contains(p, @title).should.be.true - h.file.contains(p, @body).should.be.true - - after -> unmock_contentful() - -describe 'single entry views', -> - describe 'default path function', -> - before (done) -> - @title = 'Real Talk' - @body = 'I\'m not about to sit up here, and argue about who\'s to blame.' - mock_contentful - entries: [{fields: {title: @title, body: @body}}], - content_type: {name: 'Blog Post', displayField: 'title'} - compile_fixture.call(@, 'single_entry').then(-> done()).catch(done) - - it 'compiles a single entry file based off the slugified display field', -> - p = path.join(@public, "blog_posts/#{S(@title).slugify().s}.html") - h.file.exists(p).should.be.ok - h.file.contains(p, @title).should.be.true - h.file.contains(p, @body).should.be.true - - it 'has access to other roots locals inside the single entry view', -> - p = path.join(@public, "blog_posts/#{S(@title).slugify().s}.html") - h.file.contains(p, 'such local').should.be.true - - it 'sets a _url attribute to allow links to each entry', -> - p = path.join(@public, 'index.html') - h.file.contains(p, '/blog_posts/real-talk.html').should.be.true - - after -> unmock_contentful() - - describe 'should clear entry locals between each single view compile', -> - before (done) -> - @title = 'Wow such doge' - @body = 'such amaze' - @title_2 = 'Totes McGotes' - @body_2 = null - - mock_contentful - entries: [ - {fields: {title: @title, body: @body}}, - {fields: {title: @title_2}} - ], - content_type: {name: 'Blog Post', displayField: 'title'} - compile_fixture.call(@, 'single_entry').then(-> done()).catch(done) - - after -> unmock_contentful() - - it 'should not have first entry\'s content in second entries single view', -> - p = path.join(@public, "blog_posts/#{S(@title_2).slugify().s}.html") - h.file.contains(p, @body).should.not.be.true - - describe 'custom path function', -> - before (done) -> - @title = 'Real Talk' - @body = 'I\'m not about to sit up here, and argue about who\'s to blame.' - @category = 'greatest_hits' - mock_contentful - entries: [{fields: {title: @title, body: @body, category: @category}}], - content_type: {name: 'Blog Post', displayField: 'title'} - compile_fixture.call(@, 'single_entry_custom').then(-> done()).catch(done) - - it 'compiles a single entry file using custom path', -> - output = "blogging/#{@category}/#{S(@title).slugify().s}.html" - p = path.join(@public, output) - h.file.exists(p).should.be.ok - h.file.contains(p, @title).should.be.true - h.file.contains(p, @body).should.be.true - - after -> unmock_contentful() - - describe 'custom multi-path function', -> - before (done) -> - @title = ['Real Talk', 'Fake Talk'] - @body = [ - 'I\'m not about to sit up here, and argue about who\'s to blame.', - 'I\'m about to sit up here, and not argue about who\'s not to blame.' - ] - mock_contentful - entries: [ - {fields: {title: @title[0], body: @body[0]}}, - {fields: {title: @title[1], body: @body[1]}} - ], - content_type: {name: 'Blog Post', displayField: 'title'} - compile_fixture.call(@, 'single_entry_multi').then(-> done()).catch(done) - - it 'compiles a single entry to multiple files', -> - for lang in ['en', 'fr'] - for i in [0, 1] - output = "/#{lang}/#{S(@title[i]).slugify().s}.html" - p = path.join(@public, output) - h.file.exists(p).should.be.ok - h.file.contains(p, @title[i]).should.be.true - h.file.contains(p, @body[i]).should.be.true - h.file.contains(p, "

#{output}

").should.be.true - - it 'sets _urls attribute to all of the entry\'s compiled files', -> - p = path.join(@public, 'index.html') - for lang in ['en', 'fr'] - for i in [0, 1] - h.file.contains(p, "/#{lang}/#{S(@title[i]).slugify().s}.html") - .should.be.true - - after -> unmock_contentful() - - describe 'image view helper function', -> - before (done) -> - @img_path = 'http://dogesay.com/wow.jpg' - mock_contentful - entries: [{fields: {image: fields: {file: {url: @img_path}}}}] - compile_fixture.call(@, 'image_view_helper').then(-> done()).catch(done) - - it 'adds query string params to the image', -> - p = path.join(@public, 'index.html') - h.file.contains(p, "#{@img_path}?w=100&h=100").should.be.true - - after -> unmock_contentful() diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..d51f449 --- /dev/null +++ b/test/test.js @@ -0,0 +1,573 @@ +/* + NOTE: these tests have been (mostly) compiled by coffee-script. + We require minimal ES6 syntax so we can take advantage + of the way modules are structured in `lib/`. + These tests are temporary and will eventually be translated + to the AVA test runner pending an update to support Babel 6.0. + */ + +import chai from 'chai'; +import chai_promise from 'chai-as-promised' +import mockery from 'mockery' +import path from 'path' +import RootsUtil from 'roots-util' +import roots_contentful from '../lib' +import Roots from 'roots' +import S from 'string' +import W from 'when' +import _ from 'lodash' + +let _path = path.join(__dirname, './fixtures') +let h = new RootsUtil.Helpers({ base: _path }) + +var should = chai.should(); +chai.use(chai_promise); + +function compile_fixture(fixture_name, done) { + this["public"] = path.join(fixture_name, 'public'); + return h.project.compile(Roots, fixture_name); +}; + +function mock_contentful(opts) { + if (opts == null) { + opts = {}; + } + mockery.enable({ + warnOnUnregistered: false, + useCleanCache: true + }); + opts = _.defaults(opts, { + entries: [ + { + sys: { + sys: 'data' + }, + fields: { + title: 'Default Title', + body: 'Default Body' + } + } + ], + content_type: { + name: 'Blog Post', + displayField: 'title' + } + }); + return mockery.registerMock('contentful', { + createClient: function() { + return { + contentType: function() { + return W.resolve(opts.content_type); + }, + entries: function() { + return W.resolve(opts.entries); + } + }; + } + }); +}; + +function unmock_contentful() { + mockery.deregisterAll(); + return mockery.disable(); +}; + +before(function(done) { + return h.project.install_dependencies('*', done); +}); + +after(function() { + return h.project.remove_folders('**/public'); +}); + +describe('config', function() { + before(function() { + this.title = 'Gatorade'; + this.body = 'Yung Lean'; + return mock_contentful({ + entries: [ + { + fields: { + title: this.title, + body: this.body + } + } + ] + }); + }); + it('should throw an error when missing an access token', function() { + return (function() { + return compile_fixture.call(this, 'missing_token'); + }).should["throw"](); + }); + it('should throw an error without content type id', function() { + return compile_fixture.call(this, 'missing_config').should.be.rejected; + }); + it('allows the content type name to be set through a k/v object config', function(done) { + return compile_fixture.call(this, 'alt-content-type-config')["with"](this).then(function() { + var p; + p = path.join(this["public"], 'index.html'); + h.file.contains(p, this.title).should.be["true"]; + return h.file.contains(p, this.body).should.be["true"]; + }).then(function() { + return done(); + })["catch"](done); + }); + return after(function() { + return unmock_contentful(); + }); +}); + +describe('contentful content type fields', function() { + before(function() { + return mock_contentful({ + entries: [ + { + fields: { + sys: 'test' + } + } + ] + }); + }); + it('should throw an error if `sys` is a field name', function() { + return compile_fixture.call(this, 'basic').should.be.rejected; + }); + return after(function() { + return unmock_contentful(); + }); +}); + +describe('basic compile', function() { + before(function(done) { + this.title = 'Throw Some Ds'; + this.body = 'Rich Boy selling crick'; + mock_contentful({ + entries: [ + { + fields: { + title: this.title, + body: this.body + } + } + ] + }); + return compile_fixture.call(this, 'basic').then(function() { + return done(); + })["catch"](done); + }); + it('compiles basic project', function() { + var p; + p = path.join(this["public"], 'index.html'); + return h.file.exists(p).should.be.ok; + }); + it('has contentful data available in views', function() { + var p; + p = path.join(this["public"], 'index.html'); + h.file.contains(p, this.title).should.be["true"]; + return h.file.contains(p, this.body).should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); +}); + +describe('write as json', function() { + before(function(done) { + this.title = 'Throw Some Ds'; + this.body = 'Rich Boy selling crick'; + mock_contentful({ + entries: [ + { + fields: { + title: this.title, + body: this.body + } + } + ] + }); + return compile_fixture.call(this, 'write').then(function() { + return done(); + })["catch"](done); + }); + it('compiles project', function() { + var p; + p = path.join(this["public"], 'index.html'); + return h.file.exists(p).should.be.ok; + }); + it('has written data as json', function() { + var p; + p = path.join(this["public"], 'posts.json'); + h.file.exists(p).should.be.ok; + h.file.contains(p, this.title).should.be["true"]; + return h.file.contains(p, this.body).should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); +}); + +describe('data manipulation', function() { + describe('sort', function() { + before(function(done) { + var index; + this.titles = ['Title C', 'Title B', 'Title A']; + this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting']; + this.entries = (function() { + var j, results; + results = []; + for (index = j = 0; j <= 2; index = ++j) { + results.push({ + fields: { + title: this.titles[index], + body: this.bodies[index] + } + }); + } + return results; + }).call(this); + mock_contentful({ + entries: this.entries + }); + return compile_fixture.call(this, 'sort').then(function() { + return done(); + })["catch"](done); + }); + it('compiles project', function() { + var p; + p = path.join(this["public"], 'index.html'); + return h.file.exists(p).should.be.ok; + }); + it('orders data correctly for the project', function() { + var body, j, len, p, ref, results; + p = path.join(this["public"], 'index.html'); + h.file.contains_match(p, '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$').should.be["true"]; + ref = this.bodies; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + body = ref[j]; + results.push(h.file.contains(p, body).should.be["true"]); + } + return results; + }); + it('has written data as json', function() { + var p; + p = path.join(this["public"], 'posts.json'); + h.file.exists(p).should.be.ok; + return h.file.matches_file(p, 'sort/posts_expected.json').should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); + }); + return describe('transform', function() { + before(function(done) { + var index; + this.titles = ['Title C', 'Title B', 'Title A']; + this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting']; + this.entries = (function() { + var j, results; + results = []; + for (index = j = 0; j <= 2; index = ++j) { + results.push({ + fields: { + title: this.titles[index], + body: this.bodies[index] + } + }); + } + return results; + }).call(this); + mock_contentful({ + entries: this.entries + }); + return compile_fixture.call(this, 'transform').then(function() { + return done(); + })["catch"](done); + }); + it('compiles project', function() { + var p; + p = path.join(this["public"], 'index.html'); + return h.file.exists(p).should.be.ok; + }); + it('does not reorder data', function() { + var p; + p = path.join(this["public"], 'index.html'); + return h.file.contains_match(p, '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$').should.be["true"]; + }); + it('has manipulated data correctly for the project', function() { + var body, j, len, p, ref, results; + p = path.join(this["public"], 'index.html'); + ref = this.bodies; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + body = ref[j]; + results.push(h.file.contains(p, body).should.be["false"]); + } + return results; + }); + it('has written data as json', function() { + var p; + p = path.join(this["public"], 'posts.json'); + h.file.exists(p).should.be.ok; + return h.file.matches_file(p, 'transform/posts_expected.json').should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); + }); +}); + +describe('custom name for view helper local', function() { + before(function(done) { + this.title = 'Throw Some Ds'; + this.body = 'Rich Boy selling crack'; + mock_contentful({ + entries: [ + { + fields: { + title: this.title, + body: this.body + } + } + ] + }); + return compile_fixture.call(this, 'custom_name').then(function() { + return done(); + })["catch"](done); + }); + it('has contentful data available in views under a custom name', function() { + var p; + p = path.join(this["public"], 'index.html'); + h.file.contains(p, this.title).should.be["true"]; + return h.file.contains(p, this.body).should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); +}); + +describe('single entry views', function() { + describe('default path function', function() { + before(function(done) { + this.title = 'Real Talk'; + this.body = 'I\'m not about to sit up here, and argue about who\'s to blame.'; + mock_contentful({ + entries: [ + { + fields: { + title: this.title, + body: this.body + } + } + ], + content_type: { + name: 'Blog Post', + displayField: 'title' + } + }); + return compile_fixture.call(this, 'single_entry').then(function() { + return done(); + })["catch"](done); + }); + it('compiles a single entry file based off the slugified display field', function() { + var p; + p = path.join(this["public"], "blog_posts/" + (S(this.title).slugify().s) + ".html"); + h.file.exists(p).should.be.ok; + h.file.contains(p, this.title).should.be["true"]; + return h.file.contains(p, this.body).should.be["true"]; + }); + it('has access to other roots locals inside the single entry view', function() { + var p; + p = path.join(this["public"], "blog_posts/" + (S(this.title).slugify().s) + ".html"); + return h.file.contains(p, 'such local').should.be["true"]; + }); + it('sets a _url attribute to allow links to each entry', function() { + var p; + p = path.join(this["public"], 'index.html'); + return h.file.contains(p, '/blog_posts/real-talk.html').should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); + }); + describe('should clear entry locals between each single view compile', function() { + before(function(done) { + this.title = 'Wow such doge'; + this.body = 'such amaze'; + this.title_2 = 'Totes McGotes'; + this.body_2 = null; + mock_contentful({ + entries: [ + { + fields: { + title: this.title, + body: this.body + } + }, { + fields: { + title: this.title_2 + } + } + ], + content_type: { + name: 'Blog Post', + displayField: 'title' + } + }); + return compile_fixture.call(this, 'single_entry').then(function() { + return done(); + })["catch"](done); + }); + after(function() { + return unmock_contentful(); + }); + return it('should not have first entry\'s content in second entries single view', function() { + var p; + p = path.join(this["public"], "blog_posts/" + (S(this.title_2).slugify().s) + ".html"); + return h.file.contains(p, this.body).should.not.be["true"]; + }); + }); + describe('custom path function', function() { + before(function(done) { + this.title = 'Real Talk'; + this.body = 'I\'m not about to sit up here, and argue about who\'s to blame.'; + this.category = 'greatest_hits'; + mock_contentful({ + entries: [ + { + fields: { + title: this.title, + body: this.body, + category: this.category + } + } + ], + content_type: { + name: 'Blog Post', + displayField: 'title' + } + }); + return compile_fixture.call(this, 'single_entry_custom').then(function() { + return done(); + })["catch"](done); + }); + it('compiles a single entry file using custom path', function() { + var output, p; + output = "blogging/" + this.category + "/" + (S(this.title).slugify().s) + ".html"; + p = path.join(this["public"], output); + h.file.exists(p).should.be.ok; + h.file.contains(p, this.title).should.be["true"]; + return h.file.contains(p, this.body).should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); + }); + describe('custom multi-path function', function() { + before(function(done) { + this.title = ['Real Talk', 'Fake Talk']; + this.body = ['I\'m not about to sit up here, and argue about who\'s to blame.', 'I\'m about to sit up here, and not argue about who\'s not to blame.']; + mock_contentful({ + entries: [ + { + fields: { + title: this.title[0], + body: this.body[0] + } + }, { + fields: { + title: this.title[1], + body: this.body[1] + } + } + ], + content_type: { + name: 'Blog Post', + displayField: 'title' + } + }); + return compile_fixture.call(this, 'single_entry_multi').then(function() { + return done(); + })["catch"](done); + }); + it('compiles a single entry to multiple files', function() { + var i, j, lang, len, output, p, ref, results; + ref = ['en', 'fr']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + lang = ref[j]; + results.push((function() { + var k, len1, ref1, results1; + ref1 = [0, 1]; + results1 = []; + for (k = 0, len1 = ref1.length; k < len1; k++) { + i = ref1[k]; + output = "/" + lang + "/" + (S(this.title[i]).slugify().s) + ".html"; + p = path.join(this["public"], output); + h.file.exists(p).should.be.ok; + h.file.contains(p, this.title[i]).should.be["true"]; + h.file.contains(p, this.body[i]).should.be["true"]; + results1.push(h.file.contains(p, "

" + output + "

").should.be["true"]); + } + return results1; + }).call(this)); + } + return results; + }); + it('sets _urls attribute to all of the entry\'s compiled files', function() { + var i, j, lang, len, p, ref, results; + p = path.join(this["public"], 'index.html'); + ref = ['en', 'fr']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + lang = ref[j]; + results.push((function() { + var k, len1, ref1, results1; + ref1 = [0, 1]; + results1 = []; + for (k = 0, len1 = ref1.length; k < len1; k++) { + i = ref1[k]; + results1.push(h.file.contains(p, "/" + lang + "/" + (S(this.title[i]).slugify().s) + ".html").should.be["true"]); + } + return results1; + }).call(this)); + } + return results; + }); + return after(function() { + return unmock_contentful(); + }); + }); + return describe('image view helper function', function() { + before(function(done) { + this.img_path = 'http://dogesay.com/wow.jpg'; + mock_contentful({ + entries: [ + { + fields: { + image: { + fields: { + file: { + url: this.img_path + } + } + } + } + } + ] + }); + return compile_fixture.call(this, 'image_view_helper').then(function() { + return done(); + })["catch"](done); + }); + it('adds query string params to the image', function() { + var p; + p = path.join(this["public"], 'index.html'); + return h.file.contains(p, this.img_path + "?w=100&h=100").should.be["true"]; + }); + return after(function() { + return unmock_contentful(); + }); + }); +}); diff --git a/wallaby.js b/wallaby.js new file mode 100644 index 0000000..099bc2c --- /dev/null +++ b/wallaby.js @@ -0,0 +1,38 @@ + +/* + this file is not currently being used, + but will be used once the AVA test runner + is updated to support Babel 6.0 + Q: what is this file for? A: http://wallabyjs.com/ +*/ + +var fs = require('fs'); +var path = require('path'); +var babel = require('babel-core'); + +var babelConfig = JSON.parse( + fs.readFileSync(path.join(__dirname, '.babelrc')) +); +babelConfig.babel = babel; + +module.exports = function (w) { + return { + files: [ + 'lib/**/*.js' + ], + + tests: [ + 'test/**/*.js' + ], + + compilers: { + '**/*.js': w.compilers.babel(babelConfig) + }, + + env: { + type: 'node' + }, + + testFramework: 'ava' + }; +} From 242ba56dafb41bc5803ed9fe0d1f4f581d7772b9 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 10 Dec 2015 07:59:43 +0200 Subject: [PATCH 08/58] point to existing roots-contentful --- package.json | 8 ++++---- readme.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 1513323..1f05072 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "roots-contentful-es2016", + "name": "roots-contentful", "description": "An extension for using roots with the API-driven Contentful CMS, written in ES2016", "version": "0.0.9", "author": "Carrot Creative", @@ -39,7 +39,7 @@ } ], "bugs": { - "url": "https://github.com/declandewet/roots-contentful-es2016/issues" + "url": "https://github.com/carrot/roots-contentful/issues" }, "dependencies": { "contentful": "^1.1.5", @@ -69,7 +69,7 @@ "lib": "lib", "test": "test" }, - "homepage": "https://github.com/declandewet/roots-contentful-es2016", + "homepage": "https://github.com/carrot/roots-contentful", "keywords": [ "roots-extension" ], @@ -77,7 +77,7 @@ "main": "lib", "repository": { "type": "git", - "url": "https://github.com/declandewet/roots-contentful-es2016.git" + "url": "https://github.com/carrot/roots-contentful.git" }, "scripts": { "prebuild": "mv lib src", diff --git a/readme.md b/readme.md index b814ea1..a147aff 100644 --- a/readme.md +++ b/readme.md @@ -2,13 +2,13 @@ Info | Badges -----|------- -Version | [![github release](https://img.shields.io/github/release/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://github.com/declandewet/roots-contentful-es2016/releases/latest) [![npm version](https://img.shields.io/npm/v/roots-contentful-es2016.svg?style=flat-square)](http://npmjs.org/package/roots-contentful-es2016) -License | [![npm license](https://img.shields.io/npm/l/roots-contentful-es2016.svg?style=flat-square)](https://github.com/declandewet/roots-contentful-es2016/blob/master/license.md) +Version | [![github release](https://img.shields.io/github/release/carrot/roots-contentful.svg?style=flat-square)](https://github.com/carrot/roots-contentful/releases/latest) [![npm version](https://img.shields.io/npm/v/roots-contentful-es2016.svg?style=flat-square)](http://npmjs.org/package/roots-contentful-es2016) +License | [![npm license](https://img.shields.io/npm/l/roots-contentful-es2016.svg?style=flat-square)](https://github.com/carrot/roots-contentful/blob/master/license.md) Popularity | [![npm downloads](https://img.shields.io/npm/dm/roots-contentful-es2016.svg?style=flat-square)](http://npm-stat.com/charts.html?package=roots-contentful-es2016) -Testing | [![build status](https://img.shields.io/travis/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://travis-ci.org/declandewet/roots-contentful-es2016) [![test coverage](https://img.shields.io/coveralls/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://coveralls.io/github/declandewet/roots-contentful-es2016) -Quality | [![dependency status](https://img.shields.io/david/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://david-dm.org/declandewet/roots-contentful-es2016) [![dev dependency status](https://img.shields.io/david/dev/declandewet/roots-contentful-es2016.svg?style=flat-square)](https://david-dm.org/declandewet/roots-contentful-es2016#info=devDependencies) +Testing | [![build status](https://img.shields.io/travis/carrot/roots-contentful.svg?style=flat-square)](https://travis-ci.org/carrot/roots-contentful) [![test coverage](https://img.shields.io/coveralls/carrot/roots-contentful.svg?style=flat-square)](https://coveralls.io/github/carrot/roots-contentful) +Quality | [![dependency status](https://img.shields.io/david/carrot/roots-contentful.svg?style=flat-square)](https://david-dm.org/carrot/roots-contentful) [![dev dependency status](https://img.shields.io/david/dev/carrot/roots-contentful.svg?style=flat-square)](https://david-dm.org/carrot/roots-contentful#info=devDependencies) -An extension for using [roots](https://github.com/jenius/roots) with the Contentful CMS API, written in ES2016. +An extension for using [roots](https://github.com/jenius/roots) with the Contentful CMS API. > **Note:** This project is in early development, and versioning is a little different. [Read this](http://markup.im/#q4_cRZ1Q) for more details. From 3f3550bafbba5c5ad62dedb70ea3e3113e4503aa Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Fri, 11 Dec 2015 21:59:15 +0200 Subject: [PATCH 09/58] modular file structure --- lib/errors.js | 16 +++ lib/hosts.js | 4 + lib/index.js | 294 ++++---------------------------------------- lib/plugin.js | 286 ++++++++++++++++++++++++++++++++++++++++++ lib/util/oneline.js | 7 ++ 5 files changed, 339 insertions(+), 268 deletions(-) create mode 100644 lib/errors.js create mode 100644 lib/hosts.js create mode 100644 lib/plugin.js create mode 100644 lib/util/oneline.js diff --git a/lib/errors.js b/lib/errors.js new file mode 100644 index 0000000..099f203 --- /dev/null +++ b/lib/errors.js @@ -0,0 +1,16 @@ +import oneline from './util/oneline' + +export default { + no_token: oneline` + Missing required options for roots-contentful. + Please ensure \`access_token\` and \`space_id\` + are present.`, + no_type_id: oneline` + One or more of your content types is missing an + \`id\` value`, + sys_conflict: oneline` + One of your content types has \`sys\` as a field. + This is reserved for storing Contentful system + metadata, please rename this field to a different + value.` +} diff --git a/lib/hosts.js b/lib/hosts.js new file mode 100644 index 0000000..f8a09dd --- /dev/null +++ b/lib/hosts.js @@ -0,0 +1,4 @@ +export default { + develop: 'preview.contentful.com', + production: 'cdn.contentful.com' +} diff --git a/lib/index.js b/lib/index.js index 4379d33..e49a5fe 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,273 +1,31 @@ - -import _ from 'lodash'; -import W from 'when'; -import S from 'string'; -import path from 'path'; -import contentful from 'contentful'; -import pluralize from 'pluralize'; -import RootsUtil from 'roots-util'; -import querystring from 'querystring'; - -const errors = { - no_token: 'Missing required options for roots-contentful. Please ensure `access_token` and `space_id` are present.', - no_type_id: 'One or more of your content types is missing an `id` value', - sys_conflict: 'One of your content types has `sys` as a field. This is reserved for storing Contentful system metadata, please rename this field to a different value.' -}; - -const hosts = { - develop: 'preview.contentful.com', - production: 'cdn.contentful.com' -}; - -module.exports = function(opts) { - +import RootsContentful from './plugin' + +/** + * validates user options before + * the extension is passed to Roots + * @param {Object} opts - user-supplied settings + * @return {Object} opts - if there are no errors + * opts is returned as-is + */ +function validate(opts) { if (!(opts.access_token && opts.space_id)) { throw new Error(errors.no_token); } + return opts +} - let client = contentful.createClient({ - host: - hosts[process.env.CONTENTFUL_ENV] || - (opts.preview ? hosts.develop : null) || - hosts.production, - accessToken: opts.access_token, - space: opts.space_id - }); - - /* - * Configures content types set in app.coffee. Sets default values if - * optional config options are missing. - * @param {Array} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of configured content types - */ - function configure_content(types) { - if (_.isPlainObject(types)) { - types = reconfigure_alt_type_config(types); - } - return W.map(types, t => { - if (!t.id) { - return W.reject(errors.no_type_id); - } - t.filters = t.filters || {}; - if (!t.name || (t.template && !t.path)) { - return W( - client.contentType(t.id) - .then(res => { - t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s); - if (t.template) { - t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`); - } - return t - }) - ) - } - return W.resolve(t); - }); - } - - /* - * Reconfigures content types set in app.coffee using an object instead of - * an array. The keys of the object set as the `name` option in the config - * @param {Object} types - content_types set in app.coffee extension config - * @return {Promise} - returns an array of content types - */ - function reconfigure_alt_type_config(types) { - return _.reduce(types, (res, type, k) => { - type.name = k; - res.push(type); - return res; - }, []); - } - - /* - * Fetches data from Contentful for content types, and formats the raw data - * @param {Array} types - configured content_type objects - * @return {Promise} - returns formatted locals object with all content - */ - function get_all_content(types) { - return W.map(types, t => { - return fetch_content(t) - .then(format_content) - .then(c => t.content = c) - .yield(t) - }); - } - - /* - * Fetch entries for a single content type object - * @param {Object} type - content type object - * @return {Promise} - returns response from Contentful API - */ - function fetch_content(type) { - return W(client.entries( - _.merge(type.filters, { - content_type: type.id, - include: 10 - }) - )); - } - - /* - * Formats raw response from Contentful - * @param {Object} content - entries API response for a content type - * @return {Promise} - returns formatted content type entries object - */ - function format_content(content) { - return W.map(content, format_entry); - } - - /* - * Formats a single entry object from Contentful API response - * @param {Object} e - single entry object from API response - * @return {Promise} - returns formatted entry object - */ - function format_entry(e) { - if (_.has(e.fields, 'sys')) { - return W.reject(errors.sys_conflict); - } - return _.assign(_.omit(e, 'fields'), e.fields); - } - - /* - * Sets `_url` and `_urls` properties on content with single entry views - * `_url` takes the value `null` if the content type's custom path function - * returns multiple paths - * @param {Array} types - content type objects - * return {Promise} - promise when urls are set - */ - function set_urls(types) { - return W.map(types, t => { - if (t.template) { - return W.map(t.content, entry => { - let paths = t.path(entry); - if (_.isString(paths)) { - paths = [paths]; - } - entry._urls = paths.map(p => `/${p}.html`); - return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; - }); - } - }) - } - - /* - * Builds locals object from types objects with content - * @param {Array} types - populated content type objects - * @return {Promise} - promise for when complete - */ - function set_locals(types) { - return W.map(types, t => { - return this.roots.config.locals.contentful[t.name] = t.content - }); - } - - /* - * Transforms every type with content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ - function transform_entries(types) { - return W.map(types, t => { - if (t.transform) { - W.map(t.content, entry => { - return W(entry, t.transform); - }); - } - return W.resolve(t); - }); - } - - /* - * Sort every type content with the user provided callback - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ - function sort_entries(types) { - return W.map(types, t => { - if (t.sort) { - // in order to sort promises we have to resolve them first. - W.all(t.content) - .then(data => t.content = data.sort(t.sort)); - } - return W.resolve(t); - }); - } - - /* - * Compiles single entry views for content types - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ - function compile_entries(types) { - return W.map(types, t => { - if (!t.template) { - return W.resolve(); - } - return W.map(t.content, entry => { - let template = path.join(this.roots.root, t.template); - let compiler = _.find(this.roots.config.compilers, c => { - return _.contains(c.extensions, path.extname(template).substring(1)); - }); - return W.map(entry._urls, url => { - this.roots.config.locals.entry = _.assign({}, entry, { _url: url }); - return compiler.renderFile(template, this.roots.config.locals) - .then(res => { - this.roots.config.locals.entry = null; - return this.util.write(url, res.result); - }); - }); - }); - }); - } - - /* - * Writes all data for type with content as json - * @param {Array} types - Populated content type objects - * @return {Promise} - promise for when compilation is finished - */ - function write_entries(types) { - return W.map(types, t => { - if (!t.write) { - return W.resolve(); - } - return this.util.write(t.write, JSON.stringify(t.content)); - }); - } - - /* - * View helper for accessing the actual url from a Contentful asset - * and appends any query string params - * @param {Object} asset - Asset object returned from Contentful API - * @param {Object} opts - Query string params to append to the URL - * @return {String} - URL string for the asset - */ - function asset_view_helper(asset = {}, params) { - asset.fields = asset.fields || {}; - asset.fields.file = asset.fields.file || {}; - let url = asset.fields.file.url; - if (params) { - return `${url}?${querystring.stringify(params)}`; - } - return url; - } - - return class RootsContentful { - constructor(roots) { - this.roots = roots; - this.util = new RootsUtil(this.roots); - this.roots.config.locals = this.roots.config.locals || {}; - this.roots.config.locals.contentful = this.roots.config.locals.contentful || {}; - this.roots.config.locals.asset = asset_view_helper; - } - setup() { - return configure_content(opts.content_types).with(this) - .then(get_all_content) - .tap(set_urls) - .then(transform_entries) - .then(sort_entries) - .tap(set_locals) - .tap(compile_entries) - .tap(write_entries); - } - } +/** + * transfers validated user-settings to + * the extension class + * @param {Object} opts - user-supplied settings + * @return {Function} - the extension class + */ +function extension(opts) { + RootsContentful.opts = validate(opts) + return RootsContentful } + +// we need to explicitly use +// module.exports since we're +// targeting CommonJS, not ES6 Modules. +module.exports = extension diff --git a/lib/plugin.js b/lib/plugin.js new file mode 100644 index 0000000..47d4f7f --- /dev/null +++ b/lib/plugin.js @@ -0,0 +1,286 @@ + +import _ from 'lodash'; +import W from 'when'; +import S from 'string'; +import path from 'path'; +import contentful from 'contentful'; +import pluralize from 'pluralize'; +import RootsUtil from 'roots-util'; +import querystring from 'querystring'; +import errors from './errors' +import hosts from './hosts' + +let client = null // init contentful client + +/** + * @class RootsContentful + */ +export default class RootsContentful { + + opts = { + /* defaults */ + + /* user-provided */ + ...RootsContentful.opts + } + + /** + * @constructs RootsContentful + * @param {Object} roots - the roots instance + * @return {Object} - an instance of the extension + */ + constructor(roots) { + + // set default locals + this.roots = roots + this.util = new RootsUtil(this.roots) + this.roots.config.locals = this.roots.config.locals || {} + this.roots.config.locals.contentful = this.roots.config.locals.contentful || {} + this.roots.config.locals.asset = asset_view_helper + + // grab host info + let host = hosts[process.env.CONTENTFUL_ENV] + || this.opts.preview + ? hosts.develop + : hosts.production + + // set contenful client + client = contentful.createClient({ + host, + accessToken: this.opts.access_token, + space: this.opts.space_id + }) + + } + + setup() { + return configure_content(this.opts.content_types) + .with(this) + .then(get_all_content) + .tap(set_urls) + .then(transform_entries) + .then(sort_entries) + .tap(set_locals) + .tap(compile_entries) + .tap(write_entries); + } + +} + + + +/** + * Configures content types set in app.coffee. Sets default values if + * optional config options are missing. + * @param {Array} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of configured content types + */ +function configure_content(types) { + if (_.isPlainObject(types)) { + types = reconfigure_alt_type_config(types); + } + return W.map(types, t => { + if (!t.id) { + return W.reject(errors.no_type_id); + } + t.filters = t.filters || {}; + if (!t.name || (t.template && !t.path)) { + return W( + client.contentType(t.id) + .then(res => { + t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s); + if (t.template) { + t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`); + } + return t + }) + ) + } + return W.resolve(t); + }); +} + +/** + * Reconfigures content types set in app.coffee using an object instead of + * an array. The keys of the object set as the `name` option in the config + * @param {Object} types - content_types set in app.coffee extension config + * @return {Promise} - returns an array of content types + */ +function reconfigure_alt_type_config(types) { + return _.reduce(types, (res, type, k) => { + type.name = k; + res.push(type); + return res; + }, []); +} + +/** + * Fetches data from Contentful for content types, and formats the raw data + * @param {Array} types - configured content_type objects + * @return {Promise} - returns formatted locals object with all content + */ +function get_all_content(types) { + return W.map(types, t => { + return fetch_content(t) + .then(format_content) + .then(c => t.content = c) + .yield(t) + }); +} + +/** + * Fetch entries for a single content type object + * @param {Object} type - content type object + * @return {Promise} - returns response from Contentful API + */ +function fetch_content(type) { + return W(client.entries( + _.merge(type.filters, { + content_type: type.id, + include: 10 + }) + )); +} + +/** + * Formats raw response from Contentful + * @param {Object} content - entries API response for a content type + * @return {Promise} - returns formatted content type entries object + */ +function format_content(content) { + return W.map(content, format_entry); +} + +/** + * Formats a single entry object from Contentful API response + * @param {Object} e - single entry object from API response + * @return {Promise} - returns formatted entry object + */ +function format_entry(e) { + if (_.has(e.fields, 'sys')) { + return W.reject(errors.sys_conflict); + } + return _.assign(_.omit(e, 'fields'), e.fields); +} + +/** + * Sets `_url` and `_urls` properties on content with single entry views + * `_url` takes the value `null` if the content type's custom path function + * returns multiple paths + * @param {Array} types - content type objects + * @return {Promise} - promise when urls are set + */ +function set_urls(types) { + return W.map(types, t => { + if (t.template) { + return W.map(t.content, entry => { + let paths = t.path(entry); + if (_.isString(paths)) { + paths = [paths]; + } + entry._urls = paths.map(p => `/${p}.html`); + return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; + }); + } + }) +} + +/** + * Builds locals object from types objects with content + * @param {Array} types - populated content type objects + * @return {Promise} - promise for when complete + */ +function set_locals(types) { + return W.map(types, t => { + return this.roots.config.locals.contentful[t.name] = t.content + }); +} + +/** + * Transforms every type with content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function transform_entries(types) { + return W.map(types, t => { + if (t.transform) { + W.map(t.content, entry => { + return W(entry, t.transform); + }); + } + return W.resolve(t); + }); +} + +/** + * Sort every type content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function sort_entries(types) { + return W.map(types, t => { + if (t.sort) { + // in order to sort promises we have to resolve them first. + W.all(t.content) + .then(data => t.content = data.sort(t.sort)); + } + return W.resolve(t); + }); +} + +/** + * Compiles single entry views for content types + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function compile_entries(types) { + return W.map(types, t => { + if (!t.template) { + return W.resolve(); + } + return W.map(t.content, entry => { + let template = path.join(this.roots.root, t.template); + let compiler = _.find(this.roots.config.compilers, c => { + return _.contains(c.extensions, path.extname(template).substring(1)); + }); + return W.map(entry._urls, url => { + this.roots.config.locals.entry = _.assign({}, entry, { _url: url }); + return compiler.renderFile(template, this.roots.config.locals) + .then(res => { + this.roots.config.locals.entry = null; + return this.util.write(url, res.result); + }); + }); + }); + }); +} + +/** + * Writes all data for type with content as json + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +function write_entries(types) { + return W.map(types, t => { + if (!t.write) { + return W.resolve(); + } + return this.util.write(t.write, JSON.stringify(t.content)); + }); +} + +/** + * View helper for accessing the actual url from a Contentful asset + * and appends any query string params + * @param {Object} asset - Asset object returned from Contentful API + * @param {Object} params - Query string params to append to the URL + * @return {String} - URL string for the asset + */ +function asset_view_helper(asset = {}, params) { + asset = { fields: { file: {} }, ...asset } + let url = asset.fields.file.url; + if (params) { + return `${url}?${querystring.stringify(params)}`; + } + return url; +} diff --git a/lib/util/oneline.js b/lib/util/oneline.js new file mode 100644 index 0000000..22836ac --- /dev/null +++ b/lib/util/oneline.js @@ -0,0 +1,7 @@ +const oneline = (template, ...expressions) => ( + template.reduce((accumulator, part, i) => ( + accumulator + expressions[i - 1] + part + )).replace(/(?:\s+)/g, ' ').trim() +) + +export default oneline From 88891d4f49e3885e59afb143deb84b85263136a0 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Fri, 11 Dec 2015 22:56:12 +0200 Subject: [PATCH 10/58] add linting, pre-commit linting, pre-test linting, pre-build testing --- Makefile | 6 + contributing.md | 29 +- lib/index.js | 7 +- lib/plugin.js | 156 +++++----- lib/util/oneline.js | 10 +- package.json | 23 +- readme.md | 1 + test/test.js | 729 ++++++++++++++++++++++---------------------- wallaby.js | 13 +- 9 files changed, 508 insertions(+), 466 deletions(-) diff --git a/Makefile b/Makefile index c83aa03..a60d35c 100644 --- a/Makefile +++ b/Makefile @@ -7,5 +7,11 @@ unbuild: coverage: npm run coverage +coveralls: + npm run coveralls + release: npm run release + +lint: + npm run lint diff --git a/contributing.md b/contributing.md index 2971797..3f56b55 100644 --- a/contributing.md +++ b/contributing.md @@ -14,6 +14,11 @@ If you are opening an issue about a bug, make sure that you include clear steps - Run `npm install` - Put in work +### `pre-push`, `pre-commit` Linting + +Provided dependencies are installed, `git push` and `git commit` will +not work unless this project passes a linting check. + ### Build Commands > **Note:** if your environment does not support `make` utilities, @@ -23,7 +28,13 @@ If you are opening an issue about a bug, make sure that you include clear steps This project is constantly evolving, and to ensure that things are secure and working for everyone, we need to have tests. If you are adding a new feature, please make sure to add a test for it. The test suite for this project uses [mocha](http://visionmedia.github.io/mocha/) and [chai](http://chaijs.com/) -To run the tests: +To lint the source: + +``` +$ make lint +``` + +To lint the source and run the tests: ``` $ npm test @@ -35,17 +46,25 @@ To create a coverage report: $ make coverage ``` +To feed a coverage report to coveralls: + +``` +$ make coveralls +``` + #### Building -Building the project involves compiling the ES2016 syntax down to -regular ES5 using [Babel](http://babeljs.io). This command will move -the contents of `lib/` into `src/` and then compiles `src/` into `lib/`. +> **Note:** Building the project will not work if any of the tests fail. + +Building involves compiling the ES2016 syntax down to +regular ES5 using [Babel](http://babeljs.io). This command will run the tests - on success it will then move +the contents of `lib/` into `src/` and then compile `src/` into `lib/`. ``` $ make build ``` -The nature of the above command requires cleanup if you want the original +**Important:** The nature of the above command requires cleanup if you want the original source code back in it's original place. To do that, use the following: ``` diff --git a/lib/index.js b/lib/index.js index e49a5fe..dc85607 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,5 @@ import RootsContentful from './plugin' +import errors from './errors' /** * validates user options before @@ -7,9 +8,9 @@ import RootsContentful from './plugin' * @return {Object} opts - if there are no errors * opts is returned as-is */ -function validate(opts) { +function validate (opts) { if (!(opts.access_token && opts.space_id)) { - throw new Error(errors.no_token); + throw new Error(errors.no_token) } return opts } @@ -20,7 +21,7 @@ function validate(opts) { * @param {Object} opts - user-supplied settings * @return {Function} - the extension class */ -function extension(opts) { +function extension (opts) { RootsContentful.opts = validate(opts) return RootsContentful } diff --git a/lib/plugin.js b/lib/plugin.js index 47d4f7f..13a135b 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -1,14 +1,14 @@ -import _ from 'lodash'; -import W from 'when'; -import S from 'string'; -import path from 'path'; -import contentful from 'contentful'; -import pluralize from 'pluralize'; -import RootsUtil from 'roots-util'; -import querystring from 'querystring'; -import errors from './errors' -import hosts from './hosts' +import _ from 'lodash' +import W from 'when' +import S from 'string' +import path from 'path' +import contentful from 'contentful' +import pluralize from 'pluralize' +import RootsUtil from 'roots-util' +import querystring from 'querystring' +import errors from './errors' +import hosts from './hosts' let client = null // init contentful client @@ -29,8 +29,7 @@ export default class RootsContentful { * @param {Object} roots - the roots instance * @return {Object} - an instance of the extension */ - constructor(roots) { - + constructor (roots) { // set default locals this.roots = roots this.util = new RootsUtil(this.roots) @@ -39,8 +38,7 @@ export default class RootsContentful { this.roots.config.locals.asset = asset_view_helper // grab host info - let host = hosts[process.env.CONTENTFUL_ENV] - || this.opts.preview + let host = hosts[process.env.CONTENTFUL_ENV] || this.opts.preview ? hosts.develop : hosts.production @@ -50,10 +48,9 @@ export default class RootsContentful { accessToken: this.opts.access_token, space: this.opts.space_id }) - } - setup() { + setup () { return configure_content(this.opts.content_types) .with(this) .then(get_all_content) @@ -62,42 +59,40 @@ export default class RootsContentful { .then(sort_entries) .tap(set_locals) .tap(compile_entries) - .tap(write_entries); + .tap(write_entries) } } - - /** * Configures content types set in app.coffee. Sets default values if * optional config options are missing. * @param {Array} types - content_types set in app.coffee extension config * @return {Promise} - returns an array of configured content types */ -function configure_content(types) { +function configure_content (types) { if (_.isPlainObject(types)) { - types = reconfigure_alt_type_config(types); + types = reconfigure_alt_type_config(types) } return W.map(types, t => { if (!t.id) { - return W.reject(errors.no_type_id); + return W.reject(errors.no_type_id) } - t.filters = t.filters || {}; + t.filters = t.filters || {} if (!t.name || (t.template && !t.path)) { return W( client.contentType(t.id) .then(res => { - t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s); + t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s) if (t.template) { - t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`); + t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`) } return t }) ) } - return W.resolve(t); - }); + return W.resolve(t) + }) } /** @@ -106,12 +101,12 @@ function configure_content(types) { * @param {Object} types - content_types set in app.coffee extension config * @return {Promise} - returns an array of content types */ -function reconfigure_alt_type_config(types) { +function reconfigure_alt_type_config (types) { return _.reduce(types, (res, type, k) => { - type.name = k; - res.push(type); - return res; - }, []); + type.name = k + res.push(type) + return res + }, []) } /** @@ -119,13 +114,13 @@ function reconfigure_alt_type_config(types) { * @param {Array} types - configured content_type objects * @return {Promise} - returns formatted locals object with all content */ -function get_all_content(types) { +function get_all_content (types) { return W.map(types, t => { return fetch_content(t) .then(format_content) .then(c => t.content = c) .yield(t) - }); + }) } /** @@ -133,13 +128,13 @@ function get_all_content(types) { * @param {Object} type - content type object * @return {Promise} - returns response from Contentful API */ -function fetch_content(type) { +function fetch_content (type) { return W(client.entries( _.merge(type.filters, { content_type: type.id, include: 10 }) - )); + )) } /** @@ -147,8 +142,8 @@ function fetch_content(type) { * @param {Object} content - entries API response for a content type * @return {Promise} - returns formatted content type entries object */ -function format_content(content) { - return W.map(content, format_entry); +function format_content (content) { + return W.map(content, format_entry) } /** @@ -156,11 +151,11 @@ function format_content(content) { * @param {Object} e - single entry object from API response * @return {Promise} - returns formatted entry object */ -function format_entry(e) { +function format_entry (e) { if (_.has(e.fields, 'sys')) { - return W.reject(errors.sys_conflict); + return W.reject(errors.sys_conflict) } - return _.assign(_.omit(e, 'fields'), e.fields); + return _.assign(_.omit(e, 'fields'), e.fields) } /** @@ -170,17 +165,18 @@ function format_entry(e) { * @param {Array} types - content type objects * @return {Promise} - promise when urls are set */ -function set_urls(types) { +function set_urls (types) { return W.map(types, t => { if (t.template) { return W.map(t.content, entry => { - let paths = t.path(entry); + let paths = t.path(entry) if (_.isString(paths)) { - paths = [paths]; + paths = [paths] } - entry._urls = paths.map(p => `/${p}.html`); - return entry._url = entry._urls.length === 1 ? entry._urls[0] : null; - }); + entry._urls = paths.map(p => `/${p}.html`) + entry._url = entry._urls.length === 1 ? entry._urls[0] : null + return entry._url + }) } }) } @@ -190,10 +186,12 @@ function set_urls(types) { * @param {Array} types - populated content type objects * @return {Promise} - promise for when complete */ -function set_locals(types) { +function set_locals (types) { return W.map(types, t => { - return this.roots.config.locals.contentful[t.name] = t.content - }); + this.roots.config.locals.contentful[t.name] = t.content + let ref = this.roots.config.locals.contentful[t.name] + return ref + }) } /** @@ -201,15 +199,15 @@ function set_locals(types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function transform_entries(types) { +function transform_entries (types) { return W.map(types, t => { if (t.transform) { W.map(t.content, entry => { - return W(entry, t.transform); - }); + return W(entry, t.transform) + }) } - return W.resolve(t); - }); + return W.resolve(t) + }) } /** @@ -217,15 +215,15 @@ function transform_entries(types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function sort_entries(types) { +function sort_entries (types) { return W.map(types, t => { if (t.sort) { // in order to sort promises we have to resolve them first. W.all(t.content) - .then(data => t.content = data.sort(t.sort)); + .then(data => t.content = data.sort(t.sort)) } - return W.resolve(t); - }); + return W.resolve(t) + }) } /** @@ -233,26 +231,26 @@ function sort_entries(types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function compile_entries(types) { +function compile_entries (types) { return W.map(types, t => { if (!t.template) { - return W.resolve(); + return W.resolve() } return W.map(t.content, entry => { - let template = path.join(this.roots.root, t.template); + let template = path.join(this.roots.root, t.template) let compiler = _.find(this.roots.config.compilers, c => { - return _.contains(c.extensions, path.extname(template).substring(1)); - }); + return _.contains(c.extensions, path.extname(template).substring(1)) + }) return W.map(entry._urls, url => { - this.roots.config.locals.entry = _.assign({}, entry, { _url: url }); + this.roots.config.locals.entry = _.assign({}, entry, { _url: url }) return compiler.renderFile(template, this.roots.config.locals) .then(res => { - this.roots.config.locals.entry = null; - return this.util.write(url, res.result); - }); - }); - }); - }); + this.roots.config.locals.entry = null + return this.util.write(url, res.result) + }) + }) + }) + }) } /** @@ -260,13 +258,13 @@ function compile_entries(types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function write_entries(types) { +function write_entries (types) { return W.map(types, t => { if (!t.write) { - return W.resolve(); + return W.resolve() } - return this.util.write(t.write, JSON.stringify(t.content)); - }); + return this.util.write(t.write, JSON.stringify(t.content)) + }) } /** @@ -276,11 +274,11 @@ function write_entries(types) { * @param {Object} params - Query string params to append to the URL * @return {String} - URL string for the asset */ -function asset_view_helper(asset = {}, params) { +function asset_view_helper (asset = {}, params) { asset = { fields: { file: {} }, ...asset } - let url = asset.fields.file.url; + let url = asset.fields.file.url if (params) { - return `${url}?${querystring.stringify(params)}`; + return `${url}?${querystring.stringify(params)}` } - return url; + return url } diff --git a/lib/util/oneline.js b/lib/util/oneline.js index 22836ac..8bcf27e 100644 --- a/lib/util/oneline.js +++ b/lib/util/oneline.js @@ -1,7 +1,7 @@ -const oneline = (template, ...expressions) => ( - template.reduce((accumulator, part, i) => ( - accumulator + expressions[i - 1] + part - )).replace(/(?:\s+)/g, ' ').trim() -) +function oneline (template, ...expressions) { + return template.reduce((accumulator, part, i) => { + return accumulator + expressions[i - 1] + part + }).replace(/(?:\s+)/g, ' ').trim() +} export default oneline diff --git a/package.json b/package.json index d3fefdd..3b596e3 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "devDependencies": { "ava": "^0.7.0", "babel-cli": "^6.3.15", + "babel-eslint": "^4.1.6", "babel-preset-es2015": "^6.3.13", "babel-preset-stage-0": "^6.3.13", "babel-register": "^6.3.13", @@ -24,11 +25,14 @@ "chai-as-promised": "5.1.0", "coffee-script": "1.10.0", "coveralls": "2.x", + "husky": "^0.10.2", "isparta": "^4.0.0", "istanbul": "0.4.1", "mocha": "2.3.4", "mockery": "1.4.x", - "roots": "3.1.0" + "roots": "3.1.0", + "snazzy": "^2.0.1", + "standard": "^5.4.1" }, "directories": { "lib": "lib", @@ -44,14 +48,29 @@ "type": "git", "url": "https://github.com/carrot/roots-contentful.git" }, + "standard": { + "parser": "babel-eslint", + "globals": [ + "describe", + "it", + "before", + "after", + "beforeAll", + "afterAll" + ] + }, "scripts": { - "prebuild": "mv lib src", + "precommit": "npm run lint", + "prepush": "npm run lint", + "lint": "standard --verbose | snazzy", + "prebuild": "npm test && mv lib src", "build": "babel src -d lib", "preunbuild": "rm -rf lib", "unbuild": "mv src lib", "prerelease": "npm run build", "release": "npm publish", "postrelease": "npm run unbuild", + "pretest": "npm run lint", "test": "mocha", "coverage": "babel-node ./node_modules/.bin/isparta cover _mocha", "precoveralls": "npm run coverage", diff --git a/readme.md b/readme.md index a147aff..cb7e562 100644 --- a/readme.md +++ b/readme.md @@ -7,6 +7,7 @@ License | [![npm license](https://img.shields.io/npm/l/roots-contentful-es2016.s Popularity | [![npm downloads](https://img.shields.io/npm/dm/roots-contentful-es2016.svg?style=flat-square)](http://npm-stat.com/charts.html?package=roots-contentful-es2016) Testing | [![build status](https://img.shields.io/travis/carrot/roots-contentful.svg?style=flat-square)](https://travis-ci.org/carrot/roots-contentful) [![test coverage](https://img.shields.io/coveralls/carrot/roots-contentful.svg?style=flat-square)](https://coveralls.io/github/carrot/roots-contentful) Quality | [![dependency status](https://img.shields.io/david/carrot/roots-contentful.svg?style=flat-square)](https://david-dm.org/carrot/roots-contentful) [![dev dependency status](https://img.shields.io/david/dev/carrot/roots-contentful.svg?style=flat-square)](https://david-dm.org/carrot/roots-contentful#info=devDependencies) +Code Style | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) An extension for using [roots](https://github.com/jenius/roots) with the Contentful CMS API. diff --git a/test/test.js b/test/test.js index d51f449..ecb436e 100644 --- a/test/test.js +++ b/test/test.js @@ -6,12 +6,11 @@ to the AVA test runner pending an update to support Babel 6.0. */ -import chai from 'chai'; +import chai from 'chai' import chai_promise from 'chai-as-promised' import mockery from 'mockery' import path from 'path' import RootsUtil from 'roots-util' -import roots_contentful from '../lib' import Roots from 'roots' import S from 'string' import W from 'when' @@ -20,22 +19,22 @@ import _ from 'lodash' let _path = path.join(__dirname, './fixtures') let h = new RootsUtil.Helpers({ base: _path }) -var should = chai.should(); -chai.use(chai_promise); +chai.should() +chai.use(chai_promise) -function compile_fixture(fixture_name, done) { - this["public"] = path.join(fixture_name, 'public'); - return h.project.compile(Roots, fixture_name); -}; +function compile_fixture (fixture_name, done) { + this['public'] = path.join(fixture_name, 'public') + return h.project.compile(Roots, fixture_name) +} -function mock_contentful(opts) { +function mock_contentful (opts) { if (opts == null) { - opts = {}; + opts = {} } mockery.enable({ warnOnUnregistered: false, useCleanCache: true - }); + }) opts = _.defaults(opts, { entries: [ { @@ -52,38 +51,38 @@ function mock_contentful(opts) { name: 'Blog Post', displayField: 'title' } - }); + }) return mockery.registerMock('contentful', { - createClient: function() { + createClient: function () { return { - contentType: function() { - return W.resolve(opts.content_type); + contentType: function () { + return W.resolve(opts.content_type) }, - entries: function() { - return W.resolve(opts.entries); + entries: function () { + return W.resolve(opts.entries) } - }; + } } - }); -}; + }) +} -function unmock_contentful() { - mockery.deregisterAll(); - return mockery.disable(); -}; +function unmock_contentful () { + mockery.deregisterAll() + return mockery.disable() +} -before(function(done) { - return h.project.install_dependencies('*', done); -}); +before(function (done) { + return h.project.install_dependencies('*', done) +}) -after(function() { - return h.project.remove_folders('**/public'); -}); +after(function () { + return h.project.remove_folders('**/public') +}) -describe('config', function() { - before(function() { - this.title = 'Gatorade'; - this.body = 'Yung Lean'; +describe('config', function () { + before(function () { + this.title = 'Gatorade' + this.body = 'Yung Lean' return mock_contentful({ entries: [ { @@ -93,33 +92,33 @@ describe('config', function() { } } ] - }); - }); - it('should throw an error when missing an access token', function() { - return (function() { - return compile_fixture.call(this, 'missing_token'); - }).should["throw"](); - }); - it('should throw an error without content type id', function() { - return compile_fixture.call(this, 'missing_config').should.be.rejected; - }); - it('allows the content type name to be set through a k/v object config', function(done) { - return compile_fixture.call(this, 'alt-content-type-config')["with"](this).then(function() { - var p; - p = path.join(this["public"], 'index.html'); - h.file.contains(p, this.title).should.be["true"]; - return h.file.contains(p, this.body).should.be["true"]; - }).then(function() { - return done(); - })["catch"](done); - }); - return after(function() { - return unmock_contentful(); - }); -}); + }) + }) + it('should throw an error when missing an access token', function () { + return function () { + return compile_fixture.call(this, 'missing_token') + }.should['throw']() + }) + it('should throw an error without content type id', function () { + return compile_fixture.call(this, 'missing_config').should.be.rejected + }) + it('allows the content type name to be set through a k/v object config', function (done) { + return compile_fixture.call(this, 'alt-content-type-config')['with'](this).then(function () { + var p + p = path.join(this['public'], 'index.html') + h.file.contains(p, this.title).should.be['true'] + return h.file.contains(p, this.body).should.be['true'] + }).then(function () { + return done() + })['catch'](done) + }) + return after(function () { + return unmock_contentful() + }) +}) -describe('contentful content type fields', function() { - before(function() { +describe('contentful content type fields', function () { + before(function () { return mock_contentful({ entries: [ { @@ -128,20 +127,20 @@ describe('contentful content type fields', function() { } } ] - }); - }); - it('should throw an error if `sys` is a field name', function() { - return compile_fixture.call(this, 'basic').should.be.rejected; - }); - return after(function() { - return unmock_contentful(); - }); -}); + }) + }) + it('should throw an error if `sys` is a field name', function () { + return compile_fixture.call(this, 'basic').should.be.rejected + }) + return after(function () { + return unmock_contentful() + }) +}) -describe('basic compile', function() { - before(function(done) { - this.title = 'Throw Some Ds'; - this.body = 'Rich Boy selling crick'; +describe('basic compile', function () { + before(function (done) { + this.title = 'Throw Some Ds' + this.body = 'Rich Boy selling crick' mock_contentful({ entries: [ { @@ -151,31 +150,31 @@ describe('basic compile', function() { } } ] - }); - return compile_fixture.call(this, 'basic').then(function() { - return done(); - })["catch"](done); - }); - it('compiles basic project', function() { - var p; - p = path.join(this["public"], 'index.html'); - return h.file.exists(p).should.be.ok; - }); - it('has contentful data available in views', function() { - var p; - p = path.join(this["public"], 'index.html'); - h.file.contains(p, this.title).should.be["true"]; - return h.file.contains(p, this.body).should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); -}); + }) + return compile_fixture.call(this, 'basic').then(function () { + return done() + })['catch'](done) + }) + it('compiles basic project', function () { + var p + p = path.join(this['public'], 'index.html') + return h.file.exists(p).should.be.ok + }) + it('has contentful data available in views', function () { + var p + p = path.join(this['public'], 'index.html') + h.file.contains(p, this.title).should.be['true'] + return h.file.contains(p, this.body).should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) +}) -describe('write as json', function() { - before(function(done) { - this.title = 'Throw Some Ds'; - this.body = 'Rich Boy selling crick'; +describe('write as json', function () { + before(function (done) { + this.title = 'Throw Some Ds' + this.body = 'Rich Boy selling crick' mock_contentful({ entries: [ { @@ -185,143 +184,143 @@ describe('write as json', function() { } } ] - }); - return compile_fixture.call(this, 'write').then(function() { - return done(); - })["catch"](done); - }); - it('compiles project', function() { - var p; - p = path.join(this["public"], 'index.html'); - return h.file.exists(p).should.be.ok; - }); - it('has written data as json', function() { - var p; - p = path.join(this["public"], 'posts.json'); - h.file.exists(p).should.be.ok; - h.file.contains(p, this.title).should.be["true"]; - return h.file.contains(p, this.body).should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); -}); + }) + return compile_fixture.call(this, 'write').then(function () { + return done() + })['catch'](done) + }) + it('compiles project', function () { + var p + p = path.join(this['public'], 'index.html') + return h.file.exists(p).should.be.ok + }) + it('has written data as json', function () { + var p + p = path.join(this['public'], 'posts.json') + h.file.exists(p).should.be.ok + h.file.contains(p, this.title).should.be['true'] + return h.file.contains(p, this.body).should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) +}) -describe('data manipulation', function() { - describe('sort', function() { - before(function(done) { - var index; - this.titles = ['Title C', 'Title B', 'Title A']; - this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting']; - this.entries = (function() { - var j, results; - results = []; +describe('data manipulation', function () { + describe('sort', function () { + before(function (done) { + var index + this.titles = ['Title C', 'Title B', 'Title A'] + this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting'] + this.entries = function () { + var j, results + results = [] for (index = j = 0; j <= 2; index = ++j) { results.push({ fields: { title: this.titles[index], body: this.bodies[index] } - }); + }) } - return results; - }).call(this); + return results + }.call(this) mock_contentful({ entries: this.entries - }); - return compile_fixture.call(this, 'sort').then(function() { - return done(); - })["catch"](done); - }); - it('compiles project', function() { - var p; - p = path.join(this["public"], 'index.html'); - return h.file.exists(p).should.be.ok; - }); - it('orders data correctly for the project', function() { - var body, j, len, p, ref, results; - p = path.join(this["public"], 'index.html'); - h.file.contains_match(p, '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$').should.be["true"]; - ref = this.bodies; - results = []; + }) + return compile_fixture.call(this, 'sort').then(function () { + return done() + })['catch'](done) + }) + it('compiles project', function () { + var p + p = path.join(this['public'], 'index.html') + return h.file.exists(p).should.be.ok + }) + it('orders data correctly for the project', function () { + var body, j, len, p, ref, results + p = path.join(this['public'], 'index.html') + h.file.contains_match(p, '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$').should.be['true'] + ref = this.bodies + results = [] for (j = 0, len = ref.length; j < len; j++) { - body = ref[j]; - results.push(h.file.contains(p, body).should.be["true"]); + body = ref[j] + results.push(h.file.contains(p, body).should.be['true']) } - return results; - }); - it('has written data as json', function() { - var p; - p = path.join(this["public"], 'posts.json'); - h.file.exists(p).should.be.ok; - return h.file.matches_file(p, 'sort/posts_expected.json').should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); - }); - return describe('transform', function() { - before(function(done) { - var index; - this.titles = ['Title C', 'Title B', 'Title A']; - this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting']; - this.entries = (function() { - var j, results; - results = []; + return results + }) + it('has written data as json', function () { + var p + p = path.join(this['public'], 'posts.json') + h.file.exists(p).should.be.ok + return h.file.matches_file(p, 'sort/posts_expected.json').should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) + }) + return describe('transform', function () { + before(function (done) { + var index + this.titles = ['Title C', 'Title B', 'Title A'] + this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting'] + this.entries = function () { + var j, results + results = [] for (index = j = 0; j <= 2; index = ++j) { results.push({ fields: { title: this.titles[index], body: this.bodies[index] } - }); + }) } - return results; - }).call(this); + return results + }.call(this) mock_contentful({ entries: this.entries - }); - return compile_fixture.call(this, 'transform').then(function() { - return done(); - })["catch"](done); - }); - it('compiles project', function() { - var p; - p = path.join(this["public"], 'index.html'); - return h.file.exists(p).should.be.ok; - }); - it('does not reorder data', function() { - var p; - p = path.join(this["public"], 'index.html'); - return h.file.contains_match(p, '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$').should.be["true"]; - }); - it('has manipulated data correctly for the project', function() { - var body, j, len, p, ref, results; - p = path.join(this["public"], 'index.html'); - ref = this.bodies; - results = []; + }) + return compile_fixture.call(this, 'transform').then(function () { + return done() + })['catch'](done) + }) + it('compiles project', function () { + var p + p = path.join(this['public'], 'index.html') + return h.file.exists(p).should.be.ok + }) + it('does not reorder data', function () { + var p + p = path.join(this['public'], 'index.html') + return h.file.contains_match(p, '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$').should.be['true'] + }) + it('has manipulated data correctly for the project', function () { + var body, j, len, p, ref, results + p = path.join(this['public'], 'index.html') + ref = this.bodies + results = [] for (j = 0, len = ref.length; j < len; j++) { - body = ref[j]; - results.push(h.file.contains(p, body).should.be["false"]); + body = ref[j] + results.push(h.file.contains(p, body).should.be['false']) } - return results; - }); - it('has written data as json', function() { - var p; - p = path.join(this["public"], 'posts.json'); - h.file.exists(p).should.be.ok; - return h.file.matches_file(p, 'transform/posts_expected.json').should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); - }); -}); + return results + }) + it('has written data as json', function () { + var p + p = path.join(this['public'], 'posts.json') + h.file.exists(p).should.be.ok + return h.file.matches_file(p, 'transform/posts_expected.json').should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) + }) +}) -describe('custom name for view helper local', function() { - before(function(done) { - this.title = 'Throw Some Ds'; - this.body = 'Rich Boy selling crack'; +describe('custom name for view helper local', function () { + before(function (done) { + this.title = 'Throw Some Ds' + this.body = 'Rich Boy selling crack' mock_contentful({ entries: [ { @@ -331,27 +330,27 @@ describe('custom name for view helper local', function() { } } ] - }); - return compile_fixture.call(this, 'custom_name').then(function() { - return done(); - })["catch"](done); - }); - it('has contentful data available in views under a custom name', function() { - var p; - p = path.join(this["public"], 'index.html'); - h.file.contains(p, this.title).should.be["true"]; - return h.file.contains(p, this.body).should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); -}); + }) + return compile_fixture.call(this, 'custom_name').then(function () { + return done() + })['catch'](done) + }) + it('has contentful data available in views under a custom name', function () { + var p + p = path.join(this['public'], 'index.html') + h.file.contains(p, this.title).should.be['true'] + return h.file.contains(p, this.body).should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) +}) -describe('single entry views', function() { - describe('default path function', function() { - before(function(done) { - this.title = 'Real Talk'; - this.body = 'I\'m not about to sit up here, and argue about who\'s to blame.'; +describe('single entry views', function () { + describe('default path function', function () { + before(function (done) { + this.title = 'Real Talk' + this.body = "I'm not about to sit up here, and argue about who's to blame." mock_contentful({ entries: [ { @@ -365,38 +364,38 @@ describe('single entry views', function() { name: 'Blog Post', displayField: 'title' } - }); - return compile_fixture.call(this, 'single_entry').then(function() { - return done(); - })["catch"](done); - }); - it('compiles a single entry file based off the slugified display field', function() { - var p; - p = path.join(this["public"], "blog_posts/" + (S(this.title).slugify().s) + ".html"); - h.file.exists(p).should.be.ok; - h.file.contains(p, this.title).should.be["true"]; - return h.file.contains(p, this.body).should.be["true"]; - }); - it('has access to other roots locals inside the single entry view', function() { - var p; - p = path.join(this["public"], "blog_posts/" + (S(this.title).slugify().s) + ".html"); - return h.file.contains(p, 'such local').should.be["true"]; - }); - it('sets a _url attribute to allow links to each entry', function() { - var p; - p = path.join(this["public"], 'index.html'); - return h.file.contains(p, '/blog_posts/real-talk.html').should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); - }); - describe('should clear entry locals between each single view compile', function() { - before(function(done) { - this.title = 'Wow such doge'; - this.body = 'such amaze'; - this.title_2 = 'Totes McGotes'; - this.body_2 = null; + }) + return compile_fixture.call(this, 'single_entry').then(function () { + return done() + })['catch'](done) + }) + it('compiles a single entry file based off the slugified display field', function () { + var p + p = path.join(this['public'], 'blog_posts/' + (S(this.title).slugify().s) + '.html') + h.file.exists(p).should.be.ok + h.file.contains(p, this.title).should.be['true'] + return h.file.contains(p, this.body).should.be['true'] + }) + it('has access to other roots locals inside the single entry view', function () { + var p + p = path.join(this['public'], 'blog_posts/' + (S(this.title).slugify().s) + '.html') + return h.file.contains(p, 'such local').should.be['true'] + }) + it('sets a _url attribute to allow links to each entry', function () { + var p + p = path.join(this['public'], 'index.html') + return h.file.contains(p, '/blog_posts/real-talk.html').should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) + }) + describe('should clear entry locals between each single view compile', function () { + before(function (done) { + this.title = 'Wow such doge' + this.body = 'such amaze' + this.title_2 = 'Totes McGotes' + this.body_2 = null mock_contentful({ entries: [ { @@ -414,25 +413,25 @@ describe('single entry views', function() { name: 'Blog Post', displayField: 'title' } - }); - return compile_fixture.call(this, 'single_entry').then(function() { - return done(); - })["catch"](done); - }); - after(function() { - return unmock_contentful(); - }); - return it('should not have first entry\'s content in second entries single view', function() { - var p; - p = path.join(this["public"], "blog_posts/" + (S(this.title_2).slugify().s) + ".html"); - return h.file.contains(p, this.body).should.not.be["true"]; - }); - }); - describe('custom path function', function() { - before(function(done) { - this.title = 'Real Talk'; - this.body = 'I\'m not about to sit up here, and argue about who\'s to blame.'; - this.category = 'greatest_hits'; + }) + return compile_fixture.call(this, 'single_entry').then(function () { + return done() + })['catch'](done) + }) + after(function () { + return unmock_contentful() + }) + return it("should not have first entry's content in second entries single view", function () { + var p + p = path.join(this['public'], 'blog_posts/' + (S(this.title_2).slugify().s) + '.html') + return h.file.contains(p, this.body).should.not.be['true'] + }) + }) + describe('custom path function', function () { + before(function (done) { + this.title = 'Real Talk' + this.body = "I'm not about to sit up here, and argue about who's to blame." + this.category = 'greatest_hits' mock_contentful({ entries: [ { @@ -447,27 +446,27 @@ describe('single entry views', function() { name: 'Blog Post', displayField: 'title' } - }); - return compile_fixture.call(this, 'single_entry_custom').then(function() { - return done(); - })["catch"](done); - }); - it('compiles a single entry file using custom path', function() { - var output, p; - output = "blogging/" + this.category + "/" + (S(this.title).slugify().s) + ".html"; - p = path.join(this["public"], output); - h.file.exists(p).should.be.ok; - h.file.contains(p, this.title).should.be["true"]; - return h.file.contains(p, this.body).should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); - }); - describe('custom multi-path function', function() { - before(function(done) { - this.title = ['Real Talk', 'Fake Talk']; - this.body = ['I\'m not about to sit up here, and argue about who\'s to blame.', 'I\'m about to sit up here, and not argue about who\'s not to blame.']; + }) + return compile_fixture.call(this, 'single_entry_custom').then(function () { + return done() + })['catch'](done) + }) + it('compiles a single entry file using custom path', function () { + var output, p + output = 'blogging/' + this.category + '/' + (S(this.title).slugify().s) + '.html' + p = path.join(this['public'], output) + h.file.exists(p).should.be.ok + h.file.contains(p, this.title).should.be['true'] + return h.file.contains(p, this.body).should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) + }) + describe('custom multi-path function', function () { + before(function (done) { + this.title = ['Real Talk', 'Fake Talk'] + this.body = ["I'm not about to sit up here, and argue about who's to blame.", "I'm about to sit up here, and not argue about who's not to blame."] mock_contentful({ entries: [ { @@ -486,62 +485,62 @@ describe('single entry views', function() { name: 'Blog Post', displayField: 'title' } - }); - return compile_fixture.call(this, 'single_entry_multi').then(function() { - return done(); - })["catch"](done); - }); - it('compiles a single entry to multiple files', function() { - var i, j, lang, len, output, p, ref, results; - ref = ['en', 'fr']; - results = []; + }) + return compile_fixture.call(this, 'single_entry_multi').then(function () { + return done() + })['catch'](done) + }) + it('compiles a single entry to multiple files', function () { + var i, j, lang, len, output, p, ref, results + ref = ['en', 'fr'] + results = [] for (j = 0, len = ref.length; j < len; j++) { - lang = ref[j]; - results.push((function() { - var k, len1, ref1, results1; - ref1 = [0, 1]; - results1 = []; + lang = ref[j] + results.push(function () { + var k, len1, ref1, results1 + ref1 = [0, 1] + results1 = [] for (k = 0, len1 = ref1.length; k < len1; k++) { - i = ref1[k]; - output = "/" + lang + "/" + (S(this.title[i]).slugify().s) + ".html"; - p = path.join(this["public"], output); - h.file.exists(p).should.be.ok; - h.file.contains(p, this.title[i]).should.be["true"]; - h.file.contains(p, this.body[i]).should.be["true"]; - results1.push(h.file.contains(p, "

" + output + "

").should.be["true"]); + i = ref1[k] + output = '/' + lang + '/' + (S(this.title[i]).slugify().s) + '.html' + p = path.join(this['public'], output) + h.file.exists(p).should.be.ok + h.file.contains(p, this.title[i]).should.be['true'] + h.file.contains(p, this.body[i]).should.be['true'] + results1.push(h.file.contains(p, '

' + output + '

').should.be['true']) } - return results1; - }).call(this)); + return results1 + }.call(this)) } - return results; - }); - it('sets _urls attribute to all of the entry\'s compiled files', function() { - var i, j, lang, len, p, ref, results; - p = path.join(this["public"], 'index.html'); - ref = ['en', 'fr']; - results = []; + return results + }) + it("sets _urls attribute to all of the entry's compiled files", function () { + var i, j, lang, len, p, ref, results + p = path.join(this['public'], 'index.html') + ref = ['en', 'fr'] + results = [] for (j = 0, len = ref.length; j < len; j++) { - lang = ref[j]; - results.push((function() { - var k, len1, ref1, results1; - ref1 = [0, 1]; - results1 = []; + lang = ref[j] + results.push(function () { + var k, len1, ref1, results1 + ref1 = [0, 1] + results1 = [] for (k = 0, len1 = ref1.length; k < len1; k++) { - i = ref1[k]; - results1.push(h.file.contains(p, "/" + lang + "/" + (S(this.title[i]).slugify().s) + ".html").should.be["true"]); + i = ref1[k] + results1.push(h.file.contains(p, '/' + lang + '/' + (S(this.title[i]).slugify().s) + '.html').should.be['true']) } - return results1; - }).call(this)); + return results1 + }.call(this)) } - return results; - }); - return after(function() { - return unmock_contentful(); - }); - }); - return describe('image view helper function', function() { - before(function(done) { - this.img_path = 'http://dogesay.com/wow.jpg'; + return results + }) + return after(function () { + return unmock_contentful() + }) + }) + return describe('image view helper function', function () { + before(function (done) { + this.img_path = 'http://dogesay.com/wow.jpg' mock_contentful({ entries: [ { @@ -556,18 +555,18 @@ describe('single entry views', function() { } } ] - }); - return compile_fixture.call(this, 'image_view_helper').then(function() { - return done(); - })["catch"](done); - }); - it('adds query string params to the image', function() { - var p; - p = path.join(this["public"], 'index.html'); - return h.file.contains(p, this.img_path + "?w=100&h=100").should.be["true"]; - }); - return after(function() { - return unmock_contentful(); - }); - }); -}); + }) + return compile_fixture.call(this, 'image_view_helper').then(function () { + return done() + })['catch'](done) + }) + it('adds query string params to the image', function () { + var p + p = path.join(this['public'], 'index.html') + return h.file.contains(p, this.img_path + '?w=100&h=100').should.be['true'] + }) + return after(function () { + return unmock_contentful() + }) + }) +}) diff --git a/wallaby.js b/wallaby.js index 099bc2c..063cd76 100644 --- a/wallaby.js +++ b/wallaby.js @@ -1,4 +1,3 @@ - /* this file is not currently being used, but will be used once the AVA test runner @@ -6,14 +5,14 @@ Q: what is this file for? A: http://wallabyjs.com/ */ -var fs = require('fs'); -var path = require('path'); -var babel = require('babel-core'); +var fs = require('fs') +var path = require('path') +var babel = require('babel-core') var babelConfig = JSON.parse( fs.readFileSync(path.join(__dirname, '.babelrc')) -); -babelConfig.babel = babel; +) +babelConfig.babel = babel module.exports = function (w) { return { @@ -34,5 +33,5 @@ module.exports = function (w) { }, testFramework: 'ava' - }; + } } From 92d18754f05347a0c6a5b5cfb29eba299cba77d3 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Sat, 12 Dec 2015 00:01:37 +0200 Subject: [PATCH 11/58] remove risky unbuild step --- .gitignore | 2 +- .npmignore | 4 ++++ Makefile | 3 --- contributing.md | 10 +--------- package.json | 4 +--- {lib => src}/errors.js | 0 {lib => src}/hosts.js | 0 {lib => src}/index.js | 0 {lib => src}/plugin.js | 0 {lib => src}/util/oneline.js | 0 test/fixtures/alt-content-type-config/app.coffee | 2 +- test/fixtures/basic/app.coffee | 2 +- test/fixtures/custom_name/app.coffee | 2 +- test/fixtures/image_view_helper/app.coffee | 2 +- test/fixtures/missing_config/app.coffee | 2 +- test/fixtures/missing_token/app.coffee | 2 +- test/fixtures/single_entry/app.coffee | 2 +- test/fixtures/single_entry_custom/app.coffee | 2 +- test/fixtures/single_entry_multi/app.coffee | 2 +- test/fixtures/sort/app.coffee | 2 +- test/fixtures/transform/app.coffee | 2 +- test/fixtures/write/app.coffee | 2 +- wallaby.js | 2 +- 23 files changed, 20 insertions(+), 29 deletions(-) rename {lib => src}/errors.js (100%) rename {lib => src}/hosts.js (100%) rename {lib => src}/index.js (100%) rename {lib => src}/plugin.js (100%) rename {lib => src}/util/oneline.js (100%) diff --git a/.gitignore b/.gitignore index 0338f48..a727f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store node_modules coverage -src +lib logs *.log npm-debug.log* diff --git a/.npmignore b/.npmignore index 1e0d924..9b7555b 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,8 @@ test src .travis.yml +.istanbul.yml +.editorconfig +.babelrc +wallaby.js Makefile diff --git a/Makefile b/Makefile index a60d35c..a6586d7 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,6 @@ build: npm run build -unbuild: - npm run unbuild - coverage: npm run coverage diff --git a/contributing.md b/contributing.md index 3f56b55..9cd408d 100644 --- a/contributing.md +++ b/contributing.md @@ -57,20 +57,12 @@ $ make coveralls > **Note:** Building the project will not work if any of the tests fail. Building involves compiling the ES2016 syntax down to -regular ES5 using [Babel](http://babeljs.io). This command will run the tests - on success it will then move -the contents of `lib/` into `src/` and then compile `src/` into `lib/`. +regular ES5 using [Babel](http://babeljs.io). This command will run the tests - on success it will then compile the contents of `src/` into `lib/`. ``` $ make build ``` -**Important:** The nature of the above command requires cleanup if you want the original -source code back in it's original place. To do that, use the following: - -``` -$ make unbuild -``` - #### Publishing to NPM This command will run `make build`, then `npm publish`, then `make unbuild`. diff --git a/package.json b/package.json index 3b596e3..b3c412f 100644 --- a/package.json +++ b/package.json @@ -63,10 +63,8 @@ "precommit": "npm run lint", "prepush": "npm run lint", "lint": "standard --verbose | snazzy", - "prebuild": "npm test && mv lib src", + "prebuild": "npm test", "build": "babel src -d lib", - "preunbuild": "rm -rf lib", - "unbuild": "mv src lib", "prerelease": "npm run build", "release": "npm publish", "postrelease": "npm run unbuild", diff --git a/lib/errors.js b/src/errors.js similarity index 100% rename from lib/errors.js rename to src/errors.js diff --git a/lib/hosts.js b/src/hosts.js similarity index 100% rename from lib/hosts.js rename to src/hosts.js diff --git a/lib/index.js b/src/index.js similarity index 100% rename from lib/index.js rename to src/index.js diff --git a/lib/plugin.js b/src/plugin.js similarity index 100% rename from lib/plugin.js rename to src/plugin.js diff --git a/lib/util/oneline.js b/src/util/oneline.js similarity index 100% rename from lib/util/oneline.js rename to src/util/oneline.js diff --git a/test/fixtures/alt-content-type-config/app.coffee b/test/fixtures/alt-content-type-config/app.coffee index 846c93d..c3d2b72 100644 --- a/test/fixtures/alt-content-type-config/app.coffee +++ b/test/fixtures/alt-content-type-config/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/basic/app.coffee b/test/fixtures/basic/app.coffee index b303f3c..c2c261c 100644 --- a/test/fixtures/basic/app.coffee +++ b/test/fixtures/basic/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/custom_name/app.coffee b/test/fixtures/custom_name/app.coffee index be6dbd9..af220ac 100644 --- a/test/fixtures/custom_name/app.coffee +++ b/test/fixtures/custom_name/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/image_view_helper/app.coffee b/test/fixtures/image_view_helper/app.coffee index b303f3c..c2c261c 100644 --- a/test/fixtures/image_view_helper/app.coffee +++ b/test/fixtures/image_view_helper/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/missing_config/app.coffee b/test/fixtures/missing_config/app.coffee index 9428575..2a318b6 100644 --- a/test/fixtures/missing_config/app.coffee +++ b/test/fixtures/missing_config/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/missing_token/app.coffee b/test/fixtures/missing_token/app.coffee index 96412f9..6b8b34a 100644 --- a/test/fixtures/missing_token/app.coffee +++ b/test/fixtures/missing_token/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/single_entry/app.coffee b/test/fixtures/single_entry/app.coffee index c058599..7f3d0af 100644 --- a/test/fixtures/single_entry/app.coffee +++ b/test/fixtures/single_entry/app.coffee @@ -1,5 +1,5 @@ S = require 'string' -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/single_entry_custom/app.coffee b/test/fixtures/single_entry_custom/app.coffee index c5b8d3d..f76c99d 100644 --- a/test/fixtures/single_entry_custom/app.coffee +++ b/test/fixtures/single_entry_custom/app.coffee @@ -1,5 +1,5 @@ S = require 'string' -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/single_entry_multi/app.coffee b/test/fixtures/single_entry_multi/app.coffee index 514691c..3410e51 100644 --- a/test/fixtures/single_entry_multi/app.coffee +++ b/test/fixtures/single_entry_multi/app.coffee @@ -1,5 +1,5 @@ S = require 'string' -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/sort/app.coffee b/test/fixtures/sort/app.coffee index 094f066..22f1105 100644 --- a/test/fixtures/sort/app.coffee +++ b/test/fixtures/sort/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' megaSort = (a, b)-> a.title.localeCompare(b.title) diff --git a/test/fixtures/transform/app.coffee b/test/fixtures/transform/app.coffee index 415f892..68f04ec 100644 --- a/test/fixtures/transform/app.coffee +++ b/test/fixtures/transform/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' megaTransform = (entry)-> delete entry.body diff --git a/test/fixtures/write/app.coffee b/test/fixtures/write/app.coffee index 289ef73..4bd0770 100644 --- a/test/fixtures/write/app.coffee +++ b/test/fixtures/write/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/wallaby.js b/wallaby.js index 063cd76..57feca2 100644 --- a/wallaby.js +++ b/wallaby.js @@ -17,7 +17,7 @@ babelConfig.babel = babel module.exports = function (w) { return { files: [ - 'lib/**/*.js' + 'src/**/*.js' ], tests: [ From 52a44b3847d96a56cbbc896a4473dce49a9df570 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Sat, 12 Dec 2015 00:07:37 +0200 Subject: [PATCH 12/58] document programming style --- contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 9cd408d..638cd6f 100644 --- a/contributing.md +++ b/contributing.md @@ -73,7 +73,7 @@ $ make release ### Code Style -To keep a consistant coding style in the project, we're using [Polar Mobile's guide](https://github.com/polarmobile/coffeescript-style-guide), with one difference begin that much of this project uses `under_scores` rather than `camelCase` for variable naming. For any inline documentation in the code, we're using [JSDoc](http://usejsdoc.org/). +To keep a consistent coding style in the project, we're using [JavaScript Standard Style](https://github.com/feross/standard), with one difference being that much of this project uses `under_scores` rather than `camelCase` for variable naming. For any inline documentation in the code, we're using [JSDoc](http://usejsdoc.org/). ### Commit Cleanliness From d54cf15366458ac748d42a80809da05a398b7d25 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Sat, 12 Dec 2015 00:38:47 +0200 Subject: [PATCH 13/58] add notes on debugging requests - closes #47 --- contributing.md | 5 ++++- readme.md | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 638cd6f..a25e036 100644 --- a/contributing.md +++ b/contributing.md @@ -36,10 +36,13 @@ $ make lint To lint the source and run the tests: -``` +```bash $ npm test +# or, for optional request logging: +$ NODE_DEBUG=request npm test ``` + To create a coverage report: ``` diff --git a/readme.md b/readme.md index cb7e562..78cbd07 100644 --- a/readme.md +++ b/readme.md @@ -182,6 +182,16 @@ roots-contentful also provides a convenient view helper called `asset` that allo img(src!= asset(post.image, {w: 100, h: 100, q: 50})) ``` +### Debugging Requests + +This extension makes network requests. If you need to log requests for debugging, set your `NODE_DEBUG` environment variable to `request`. + +For example, in a `roots compile` command: + +``` +$ NODE_DEBUG=request roots compile --verbose +``` + ### License & Contributing - Details on the license [can be found here](license.md) From ce0006be235fd3b4f1db7e5ef065db5ececdc25a Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Sat, 12 Dec 2015 01:35:23 +0200 Subject: [PATCH 14/58] move description to top of readme --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 78cbd07..31366dc 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,9 @@ # Roots Contentful +An extension for using [roots](https://github.com/jenius/roots) with the Contentful CMS API. + +> **Note:** This project is in early development, and versioning is a little different. [Read this](http://markup.im/#q4_cRZ1Q) for more details. + Info | Badges -----|------- Version | [![github release](https://img.shields.io/github/release/carrot/roots-contentful.svg?style=flat-square)](https://github.com/carrot/roots-contentful/releases/latest) [![npm version](https://img.shields.io/npm/v/roots-contentful-es2016.svg?style=flat-square)](http://npmjs.org/package/roots-contentful-es2016) @@ -9,10 +13,6 @@ Testing | [![build status](https://img.shields.io/travis/carrot/roots-contentful Quality | [![dependency status](https://img.shields.io/david/carrot/roots-contentful.svg?style=flat-square)](https://david-dm.org/carrot/roots-contentful) [![dev dependency status](https://img.shields.io/david/dev/carrot/roots-contentful.svg?style=flat-square)](https://david-dm.org/carrot/roots-contentful#info=devDependencies) Code Style | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) -An extension for using [roots](https://github.com/jenius/roots) with the Contentful CMS API. - -> **Note:** This project is in early development, and versioning is a little different. [Read this](http://markup.im/#q4_cRZ1Q) for more details. - ### Why Should You Care? We love static sites. They're fast, resilient, simple, and cheap. From be06374966366d9f05e09fa9508751a12e2f2ed3 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Sat, 12 Dec 2015 03:45:24 +0200 Subject: [PATCH 15/58] improve publish workflow --- contributing.md | 12 +++++++++++- package.json | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/contributing.md b/contributing.md index a25e036..0486c33 100644 --- a/contributing.md +++ b/contributing.md @@ -68,12 +68,22 @@ $ make build #### Publishing to NPM -This command will run `make build`, then `npm publish`, then `make unbuild`. +This command will lint the project files, run the tests, build the project, publish the build to NPM and then perform a `git push --follow-tags`. ``` $ make release ``` +A typical publish workflow might look something like this: + +``` +$ git checkout master +$ git add . +$ git commit -m "fixed a bug" +$ npm version patch +$ make release +``` + ### Code Style To keep a consistent coding style in the project, we're using [JavaScript Standard Style](https://github.com/feross/standard), with one difference being that much of this project uses `under_scores` rather than `camelCase` for variable naming. For any inline documentation in the code, we're using [JSDoc](http://usejsdoc.org/). diff --git a/package.json b/package.json index b3c412f..89b6625 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "build": "babel src -d lib", "prerelease": "npm run build", "release": "npm publish", - "postrelease": "npm run unbuild", + "postpublish": "git push --follow-tags", "pretest": "npm run lint", "test": "mocha", "coverage": "babel-node ./node_modules/.bin/isparta cover _mocha", From eec8ba73ad2d358b56673a0005188886167c4964 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Mon, 14 Dec 2015 15:13:03 +0200 Subject: [PATCH 16/58] replace dependance on when.js with native es2016 constructs --- package.json | 15 +++- src/plugin.js | 200 +++++++++++++++++++++++++------------------------- test/test.js | 1 + 3 files changed, 113 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index 89b6625..6c22545 100644 --- a/package.json +++ b/package.json @@ -7,17 +7,19 @@ "url": "https://github.com/carrot/roots-contentful/issues" }, "dependencies": { + "babel-polyfill": "^6.3.14", + "babel-runtime": "^6.3.13", "contentful": "^1.1.5", "lodash": "^3.10.1", "pluralize": "^1.2.1", "roots-util": "0.1.x", - "string": "^3.1.3", - "when": "3.7.x" + "string": "^3.1.3" }, "devDependencies": { "ava": "^0.7.0", "babel-cli": "^6.3.15", "babel-eslint": "^4.1.6", + "babel-plugin-transform-runtime": "^6.3.13", "babel-preset-es2015": "^6.3.13", "babel-preset-stage-0": "^6.3.13", "babel-register": "^6.3.13", @@ -40,6 +42,15 @@ }, "homepage": "https://github.com/carrot/roots-contentful", "keywords": [ + "roots", + "contentful", + "content", + "dynamic", + "cms", + "management", + "system", + "extension", + "plugin", "roots-extension" ], "license": "MIT", diff --git a/src/plugin.js b/src/plugin.js index 13a135b..adc7fe3 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,6 +1,4 @@ - import _ from 'lodash' -import W from 'when' import S from 'string' import path from 'path' import contentful from 'contentful' @@ -31,7 +29,7 @@ export default class RootsContentful { */ constructor (roots) { // set default locals - this.roots = roots + this.roots = roots || { config: {} } this.util = new RootsUtil(this.roots) this.roots.config.locals = this.roots.config.locals || {} this.roots.config.locals.contentful = this.roots.config.locals.contentful || {} @@ -50,16 +48,21 @@ export default class RootsContentful { }) } - setup () { - return configure_content(this.opts.content_types) - .with(this) - .then(get_all_content) - .tap(set_urls) - .then(transform_entries) - .then(sort_entries) - .tap(set_locals) - .tap(compile_entries) - .tap(write_entries) + /** + * Performs asynchronous setup tasks required + * for the extension to work + * @return {Promise} an array for the sorted contentful data + */ + async setup () { + let configuration = await configure_content(this.opts.content_types) + let content = await get_all_content(configuration) + await set_urls(content) + let entries = await transform_entries(content) + let sorted = await sort_entries(entries) + await this::set_locals(sorted) + await this::compile_entries(sorted) + await this::write_entries(sorted) + return sorted } } @@ -70,28 +73,23 @@ export default class RootsContentful { * @param {Array} types - content_types set in app.coffee extension config * @return {Promise} - returns an array of configured content types */ -function configure_content (types) { - if (_.isPlainObject(types)) { +async function configure_content (types) { + // check if `types` is a plain object - if so, convert to array + if (types != null && !Array.isArray(types) && typeof types === 'object') { types = reconfigure_alt_type_config(types) } - return W.map(types, t => { - if (!t.id) { - return W.reject(errors.no_type_id) - } - t.filters = t.filters || {} - if (!t.name || (t.template && !t.path)) { - return W( - client.contentType(t.id) - .then(res => { - t.name = t.name || pluralize(S(res.name).toLowerCase().underscore().s) - if (t.template) { - t.path = t.path || (e => `${t.name}/${S(e[res.displayField]).slugify().s}`) - } - return t - }) - ) + types = await Promise.all(types) + return types.map(async type => { + if (!type.id) throw new Error(errors.no_type_id) + type.filters = type.filters || {} + if (!type.name || (type.template && !type.path)) { + let content_type = await client.contentType(type.id) + type.name = type.name || pluralize(S(content_type.name).toLowerCase().underscore().s) + if (type.template) { + type.path = type.path || (e => `${type.name}/${S(e[content_type.displayField]).slugify().s}`) + } } - return W.resolve(t) + return type }) } @@ -102,10 +100,10 @@ function configure_content (types) { * @return {Promise} - returns an array of content types */ function reconfigure_alt_type_config (types) { - return _.reduce(types, (res, type, k) => { - type.name = k - res.push(type) - return res + return _.reduce(types, (results, type, key) => { + type.name = key + results.push(type) + return results }, []) } @@ -114,12 +112,12 @@ function reconfigure_alt_type_config (types) { * @param {Array} types - configured content_type objects * @return {Promise} - returns formatted locals object with all content */ -function get_all_content (types) { - return W.map(types, t => { - return fetch_content(t) - .then(format_content) - .then(c => t.content = c) - .yield(t) +async function get_all_content (types) { + types = await Promise.all(types) + return types.map(async type => { + let content = await fetch_content(type) + type.content = await format_content(content) + return type }) } @@ -128,13 +126,13 @@ function get_all_content (types) { * @param {Object} type - content type object * @return {Promise} - returns response from Contentful API */ -function fetch_content (type) { - return W(client.entries( - _.merge(type.filters, { - content_type: type.id, - include: 10 - }) - )) +async function fetch_content (type) { + let entries = await client.entries({ + ...type.filters, + content_type: type.id, + include: 10 + }) + return entries } /** @@ -142,20 +140,23 @@ function fetch_content (type) { * @param {Object} content - entries API response for a content type * @return {Promise} - returns formatted content type entries object */ -function format_content (content) { - return W.map(content, format_entry) +async function format_content (content) { + content = await Promise.all(content) + return content.map(format_entry) } /** * Formats a single entry object from Contentful API response - * @param {Object} e - single entry object from API response + * @param {Object} entry - single entry object from API response * @return {Promise} - returns formatted entry object */ -function format_entry (e) { - if (_.has(e.fields, 'sys')) { - return W.reject(errors.sys_conflict) +function format_entry (entry) { + if (entry.fields.sys != null) { + throw new Error(errors.sys_conflict) } - return _.assign(_.omit(e, 'fields'), e.fields) + let formatted = { ...entry, ...entry.fields } + delete formatted.fields + return formatted } /** @@ -165,15 +166,16 @@ function format_entry (e) { * @param {Array} types - content type objects * @return {Promise} - promise when urls are set */ -function set_urls (types) { - return W.map(types, t => { - if (t.template) { - return W.map(t.content, entry => { - let paths = t.path(entry) - if (_.isString(paths)) { +async function set_urls (types) { + types = await Promise.all(types) + return types.map(type => { + if (type.template) { + return type.content.map(entry => { + let paths = type.path(entry) + if (typeof paths === 'string') { paths = [paths] } - entry._urls = paths.map(p => `/${p}.html`) + entry._urls = paths.map(path => `/${path}.html`) entry._url = entry._urls.length === 1 ? entry._urls[0] : null return entry._url }) @@ -186,11 +188,11 @@ function set_urls (types) { * @param {Array} types - populated content type objects * @return {Promise} - promise for when complete */ -function set_locals (types) { - return W.map(types, t => { - this.roots.config.locals.contentful[t.name] = t.content - let ref = this.roots.config.locals.contentful[t.name] - return ref +async function set_locals (types) { + types = await Promise.all(types) + return types.map(type => { + this.roots.config.locals.contentful[type.name] = type.content + return this.roots.config.locals.contentful[type.name] }) } @@ -199,14 +201,13 @@ function set_locals (types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function transform_entries (types) { - return W.map(types, t => { - if (t.transform) { - W.map(t.content, entry => { - return W(entry, t.transform) - }) +async function transform_entries (types) { + types = await Promise.all(types) + return types.map(type => { + if (type.transform) { + type.content.map(entry => type.transform(entry)) } - return W.resolve(t) + return type }) } @@ -215,14 +216,13 @@ function transform_entries (types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function sort_entries (types) { - return W.map(types, t => { - if (t.sort) { - // in order to sort promises we have to resolve them first. - W.all(t.content) - .then(data => t.content = data.sort(t.sort)) +async function sort_entries (types) { + types = await Promise.all(types) + return types.map(type => { + if (type.sort) { + type.content = type.content.sort(type.sort) } - return W.resolve(t) + return type }) } @@ -231,22 +231,21 @@ function sort_entries (types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function compile_entries (types) { - return W.map(types, t => { - if (!t.template) { - return W.resolve() - } - return W.map(t.content, entry => { - let template = path.join(this.roots.root, t.template) - let compiler = _.find(this.roots.config.compilers, c => { - return _.contains(c.extensions, path.extname(template).substring(1)) +async function compile_entries (types) { + types = await Promise.all(types) + return types.map(type => { + if (!type.template) return + return type.content.map(entry => { + let template = path.join(this.roots.root, type.template) + let compiler = _.find(this.roots.config.compilers, compiler => { + return compiler.extensions.includes(path.extname(template).substring(1)) }) - return W.map(entry._urls, url => { - this.roots.config.locals.entry = _.assign({}, entry, { _url: url }) + return entry._urls.map(url => { + this.roots.config.locals.entry = { ...entry, _url: url } return compiler.renderFile(template, this.roots.config.locals) - .then(res => { + .then(compiled => { this.roots.config.locals.entry = null - return this.util.write(url, res.result) + return this.util.write(url, compiled.result) }) }) }) @@ -258,12 +257,11 @@ function compile_entries (types) { * @param {Array} types - Populated content type objects * @return {Promise} - promise for when compilation is finished */ -function write_entries (types) { - return W.map(types, t => { - if (!t.write) { - return W.resolve() - } - return this.util.write(t.write, JSON.stringify(t.content)) +async function write_entries (types) { + types = await Promise.all(types) + return types.map(type => { + if (!type.write) return + return this.util.write(type.write, JSON.stringify(type.content)) }) } diff --git a/test/test.js b/test/test.js index ecb436e..3dd99d7 100644 --- a/test/test.js +++ b/test/test.js @@ -6,6 +6,7 @@ to the AVA test runner pending an update to support Babel 6.0. */ +import 'babel-polyfill' import chai from 'chai' import chai_promise from 'chai-as-promised' import mockery from 'mockery' From 9fcd043fbd5613573345f49f985b10b43998d45b Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Mon, 14 Dec 2015 16:21:54 +0200 Subject: [PATCH 17/58] add when.js to package.json for use in tests --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c22545..f088f67 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "lodash": "^3.10.1", "pluralize": "^1.2.1", "roots-util": "0.1.x", - "string": "^3.1.3" + "string": "^3.1.3", + "when": "^3.7.5" }, "devDependencies": { "ava": "^0.7.0", From 3f66c53794d941c831cf8c17464232a497371962 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Mon, 14 Dec 2015 19:04:21 +0200 Subject: [PATCH 18/58] convert to use underscore.string --- package.json | 6 +++--- src/plugin.js | 11 ++++++----- test/fixtures/single_entry/app.coffee | 1 - test/fixtures/single_entry/package.json | 3 +-- test/fixtures/single_entry_custom/app.coffee | 4 ++-- test/fixtures/single_entry_custom/package.json | 2 +- test/fixtures/single_entry_multi/app.coffee | 4 ++-- test/fixtures/single_entry_multi/package.json | 2 +- test/test.js | 14 +++++++------- 9 files changed, 23 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index f088f67..e2e9927 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "lodash": "^3.10.1", "pluralize": "^1.2.1", "roots-util": "0.1.x", - "string": "^3.1.3", - "when": "^3.7.5" + "underscore.string": "^3.2.2" }, "devDependencies": { "ava": "^0.7.0", @@ -35,7 +34,8 @@ "mockery": "1.4.x", "roots": "3.1.0", "snazzy": "^2.0.1", - "standard": "^5.4.1" + "standard": "^5.4.1", + "when": "^3.7.5" }, "directories": { "lib": "lib", diff --git a/src/plugin.js b/src/plugin.js index adc7fe3..caf6609 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,10 +1,11 @@ -import _ from 'lodash' -import S from 'string' import path from 'path' +import querystring from 'querystring' +import _ from 'lodash' import contentful from 'contentful' import pluralize from 'pluralize' +import slugify from 'underscore.string/slugify' +import underscored from 'underscore.string/underscored' import RootsUtil from 'roots-util' -import querystring from 'querystring' import errors from './errors' import hosts from './hosts' @@ -84,9 +85,9 @@ async function configure_content (types) { type.filters = type.filters || {} if (!type.name || (type.template && !type.path)) { let content_type = await client.contentType(type.id) - type.name = type.name || pluralize(S(content_type.name).toLowerCase().underscore().s) + type.name = type.name || pluralize(underscored(content_type.name)) if (type.template) { - type.path = type.path || (e => `${type.name}/${S(e[content_type.displayField]).slugify().s}`) + type.path = type.path || (entry => `${type.name}/${slugify(entry[content_type.displayField])}`) } } return type diff --git a/test/fixtures/single_entry/app.coffee b/test/fixtures/single_entry/app.coffee index 7f3d0af..b9d39a8 100644 --- a/test/fixtures/single_entry/app.coffee +++ b/test/fixtures/single_entry/app.coffee @@ -1,4 +1,3 @@ -S = require 'string' contentful = require '../../../src' module.exports = diff --git a/test/fixtures/single_entry/package.json b/test/fixtures/single_entry/package.json index c5d3ec0..2d0ae2c 100644 --- a/test/fixtures/single_entry/package.json +++ b/test/fixtures/single_entry/package.json @@ -1,7 +1,6 @@ { "name": "test", "dependencies": { - "jade": "*", - "string": "*" + "jade": "*" } } diff --git a/test/fixtures/single_entry_custom/app.coffee b/test/fixtures/single_entry_custom/app.coffee index f76c99d..a73a4bc 100644 --- a/test/fixtures/single_entry_custom/app.coffee +++ b/test/fixtures/single_entry_custom/app.coffee @@ -1,4 +1,4 @@ -S = require 'string' +slugify = require 'underscore.string/slugify' contentful = require '../../../src' module.exports = @@ -12,7 +12,7 @@ module.exports = id: '6BYT1gNiIEyIw8Og8aQAO6' name: 'blog_posts' template: 'views/_blog_post.jade' - path: (e) -> "blogging/#{e.category}/#{S(e.title).slugify().s}" + path: (e) -> "blogging/#{e.category}/#{slugify(e.title)}" } ] ) diff --git a/test/fixtures/single_entry_custom/package.json b/test/fixtures/single_entry_custom/package.json index c5d3ec0..1a248fc 100644 --- a/test/fixtures/single_entry_custom/package.json +++ b/test/fixtures/single_entry_custom/package.json @@ -2,6 +2,6 @@ "name": "test", "dependencies": { "jade": "*", - "string": "*" + "underscore.string": "*" } } diff --git a/test/fixtures/single_entry_multi/app.coffee b/test/fixtures/single_entry_multi/app.coffee index 3410e51..503e364 100644 --- a/test/fixtures/single_entry_multi/app.coffee +++ b/test/fixtures/single_entry_multi/app.coffee @@ -1,4 +1,4 @@ -S = require 'string' +slugify = require 'underscore.string/slugify' contentful = require '../../../src' module.exports = @@ -12,7 +12,7 @@ module.exports = id: '6BYT1gNiIEyIw8Og8aQAO6' name: 'blog_posts' template: 'views/_blog_post.jade' - path: (e) -> ("#{lang}/#{S(e.title).slugify().s}" for lang in ['en', 'fr']) + path: (e) -> ("#{lang}/#{slugify(e.title)}" for lang in ['en', 'fr']) } ] ) diff --git a/test/fixtures/single_entry_multi/package.json b/test/fixtures/single_entry_multi/package.json index c5d3ec0..1a248fc 100644 --- a/test/fixtures/single_entry_multi/package.json +++ b/test/fixtures/single_entry_multi/package.json @@ -2,6 +2,6 @@ "name": "test", "dependencies": { "jade": "*", - "string": "*" + "underscore.string": "*" } } diff --git a/test/test.js b/test/test.js index 3dd99d7..72422ec 100644 --- a/test/test.js +++ b/test/test.js @@ -13,7 +13,7 @@ import mockery from 'mockery' import path from 'path' import RootsUtil from 'roots-util' import Roots from 'roots' -import S from 'string' +import slugify from 'underscore.string/slugify' import W from 'when' import _ from 'lodash' @@ -372,14 +372,14 @@ describe('single entry views', function () { }) it('compiles a single entry file based off the slugified display field', function () { var p - p = path.join(this['public'], 'blog_posts/' + (S(this.title).slugify().s) + '.html') + p = path.join(this['public'], 'blog_posts/' + slugify(this.title) + '.html') h.file.exists(p).should.be.ok h.file.contains(p, this.title).should.be['true'] return h.file.contains(p, this.body).should.be['true'] }) it('has access to other roots locals inside the single entry view', function () { var p - p = path.join(this['public'], 'blog_posts/' + (S(this.title).slugify().s) + '.html') + p = path.join(this['public'], 'blog_posts/' + slugify(this.title) + '.html') return h.file.contains(p, 'such local').should.be['true'] }) it('sets a _url attribute to allow links to each entry', function () { @@ -424,7 +424,7 @@ describe('single entry views', function () { }) return it("should not have first entry's content in second entries single view", function () { var p - p = path.join(this['public'], 'blog_posts/' + (S(this.title_2).slugify().s) + '.html') + p = path.join(this['public'], 'blog_posts/' + slugify(this.title_2) + '.html') return h.file.contains(p, this.body).should.not.be['true'] }) }) @@ -454,7 +454,7 @@ describe('single entry views', function () { }) it('compiles a single entry file using custom path', function () { var output, p - output = 'blogging/' + this.category + '/' + (S(this.title).slugify().s) + '.html' + output = 'blogging/' + this.category + '/' + slugify(this.title) + '.html' p = path.join(this['public'], output) h.file.exists(p).should.be.ok h.file.contains(p, this.title).should.be['true'] @@ -503,7 +503,7 @@ describe('single entry views', function () { results1 = [] for (k = 0, len1 = ref1.length; k < len1; k++) { i = ref1[k] - output = '/' + lang + '/' + (S(this.title[i]).slugify().s) + '.html' + output = '/' + lang + '/' + slugify(this.title[i]) + '.html' p = path.join(this['public'], output) h.file.exists(p).should.be.ok h.file.contains(p, this.title[i]).should.be['true'] @@ -528,7 +528,7 @@ describe('single entry views', function () { results1 = [] for (k = 0, len1 = ref1.length; k < len1; k++) { i = ref1[k] - results1.push(h.file.contains(p, '/' + lang + '/' + (S(this.title[i]).slugify().s) + '.html').should.be['true']) + results1.push(h.file.contains(p, '/' + lang + '/' + slugify(this.title[i]) + '.html').should.be['true']) } return results1 }.call(this)) From 33400b641efa5ede3e20668ab02ef0831c431834 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Mon, 14 Dec 2015 19:47:51 +0200 Subject: [PATCH 19/58] bump test timeout to larger epoch --- test/mocha.opts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocha.opts b/test/mocha.opts index 56c13ab..9ee4f1d 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,3 @@ --reporter spec --compilers js:babel-register,coffee:coffee-script/register ---timeout 30000 +--timeout 90000 From 2be1be6973607846d202f71412f1742d1895f917 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 16 Dec 2015 08:22:23 +0200 Subject: [PATCH 20/58] remove runtime dependency on lodash, slight syntax improvements --- package.json | 2 +- src/plugin.js | 89 +++++++++++++++++++------------------ src/util/is-plain-object.js | 10 +++++ 3 files changed, 56 insertions(+), 45 deletions(-) create mode 100644 src/util/is-plain-object.js diff --git a/package.json b/package.json index e2e9927..5c53828 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "babel-polyfill": "^6.3.14", "babel-runtime": "^6.3.13", "contentful": "^1.1.5", - "lodash": "^3.10.1", "pluralize": "^1.2.1", "roots-util": "0.1.x", "underscore.string": "^3.2.2" @@ -30,6 +29,7 @@ "husky": "^0.10.2", "isparta": "^4.0.0", "istanbul": "0.4.1", + "lodash": "^3.10.1", "mocha": "2.3.4", "mockery": "1.4.x", "roots": "3.1.0", diff --git a/src/plugin.js b/src/plugin.js index caf6609..c901750 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,6 +1,5 @@ import path from 'path' import querystring from 'querystring' -import _ from 'lodash' import contentful from 'contentful' import pluralize from 'pluralize' import slugify from 'underscore.string/slugify' @@ -8,6 +7,7 @@ import underscored from 'underscore.string/underscored' import RootsUtil from 'roots-util' import errors from './errors' import hosts from './hosts' +import is_plain_object from './util/is-plain-object' let client = null // init contentful client @@ -30,7 +30,7 @@ export default class RootsContentful { */ constructor (roots) { // set default locals - this.roots = roots || { config: {} } + this.roots = roots this.util = new RootsUtil(this.roots) this.roots.config.locals = this.roots.config.locals || {} this.roots.config.locals.contentful = this.roots.config.locals.contentful || {} @@ -75,19 +75,18 @@ export default class RootsContentful { * @return {Promise} - returns an array of configured content types */ async function configure_content (types) { - // check if `types` is a plain object - if so, convert to array - if (types != null && !Array.isArray(types) && typeof types === 'object') { - types = reconfigure_alt_type_config(types) + if (is_plain_object(types)) { + types = convert_types_to_array(types) } - types = await Promise.all(types) return types.map(async type => { - if (!type.id) throw new Error(errors.no_type_id) - type.filters = type.filters || {} - if (!type.name || (type.template && !type.path)) { - let content_type = await client.contentType(type.id) - type.name = type.name || pluralize(underscored(content_type.name)) - if (type.template) { - type.path = type.path || (entry => `${type.name}/${slugify(entry[content_type.displayField])}`) + const { id, name, filters, template, path } = type + if (!id) throw new Error(errors.no_type_id) + type.filters = filters || {} + if (!name || (template && !path)) { + let content_type = await client.contentType(id) + type.name = name || pluralize(underscored(content_type.name)) + if (template) { + type.path = path || (entry => `${name}/${slugify(entry[content_type.displayField])}`) } } return type @@ -100,10 +99,9 @@ async function configure_content (types) { * @param {Object} types - content_types set in app.coffee extension config * @return {Promise} - returns an array of content types */ -function reconfigure_alt_type_config (types) { - return _.reduce(types, (results, type, key) => { - type.name = key - results.push(type) +function convert_types_to_array (types) { + return Object.keys(types).reduce((results, key) => { + results.push({ ...types[key], name: key }) return results }, []) } @@ -127,10 +125,10 @@ async function get_all_content (types) { * @param {Object} type - content type object * @return {Promise} - returns response from Contentful API */ -async function fetch_content (type) { +async function fetch_content ({ id, filters }) { let entries = await client.entries({ - ...type.filters, - content_type: type.id, + ...filters, + content_type: id, include: 10 }) return entries @@ -169,10 +167,10 @@ function format_entry (entry) { */ async function set_urls (types) { types = await Promise.all(types) - return types.map(type => { - if (type.template) { - return type.content.map(entry => { - let paths = type.path(entry) + return types.map(({ template, content, path }) => { + if (template) { + return content.map(entry => { + let paths = path(entry) if (typeof paths === 'string') { paths = [paths] } @@ -191,9 +189,9 @@ async function set_urls (types) { */ async function set_locals (types) { types = await Promise.all(types) - return types.map(type => { - this.roots.config.locals.contentful[type.name] = type.content - return this.roots.config.locals.contentful[type.name] + return types.map(({ name, content }) => { + this.roots.config.locals.contentful[name] = content + return content }) } @@ -205,8 +203,9 @@ async function set_locals (types) { async function transform_entries (types) { types = await Promise.all(types) return types.map(type => { - if (type.transform) { - type.content.map(entry => type.transform(entry)) + const { transform, content } = type + if (transform) { + content.forEach(transform) } return type }) @@ -220,8 +219,9 @@ async function transform_entries (types) { async function sort_entries (types) { types = await Promise.all(types) return types.map(type => { - if (type.sort) { - type.content = type.content.sort(type.sort) + const { sort, content } = type + if (sort) { + type.content = content.sort(sort) } return type }) @@ -233,20 +233,21 @@ async function sort_entries (types) { * @return {Promise} - promise for when compilation is finished */ async function compile_entries (types) { + const { util, roots: { root, config: { compilers, locals } } } = this types = await Promise.all(types) - return types.map(type => { - if (!type.template) return - return type.content.map(entry => { - let template = path.join(this.roots.root, type.template) - let compiler = _.find(this.roots.config.compilers, compiler => { - return compiler.extensions.includes(path.extname(template).substring(1)) + return types.map(({ template, content }) => { + if (!template) return + return content.map(entry => { + let tpl_path = path.join(root, template) + let compiler = Object.values(compilers).find(compiler => { + return compiler.extensions.includes(path.extname(tpl_path).substring(1)) }) - return entry._urls.map(url => { - this.roots.config.locals.entry = { ...entry, _url: url } - return compiler.renderFile(template, this.roots.config.locals) + return entry._urls.map(_url => { + this.roots.config.locals.entry = { ...entry, _url } + return compiler.renderFile(tpl_path, locals) .then(compiled => { this.roots.config.locals.entry = null - return this.util.write(url, compiled.result) + return util.write(_url, compiled.result) }) }) }) @@ -260,9 +261,9 @@ async function compile_entries (types) { */ async function write_entries (types) { types = await Promise.all(types) - return types.map(type => { - if (!type.write) return - return this.util.write(type.write, JSON.stringify(type.content)) + return types.map(({ write, content }) => { + if (!write) return + return this.util.write(write, JSON.stringify(content)) }) } diff --git a/src/util/is-plain-object.js b/src/util/is-plain-object.js new file mode 100644 index 0000000..8428bd4 --- /dev/null +++ b/src/util/is-plain-object.js @@ -0,0 +1,10 @@ +/** + * checks if an object is an object, but not an array + * @param {} obj - the object to check + * @return {Boolean} - true if is a plain object + */ +export default function is_plain_object (obj) { + return obj != null && + !Array.isArray(obj) && + typeof obj === 'object' +} From f0cfbdb626842f72ab2dab896db2673b3ebcb274 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Fri, 18 Dec 2015 13:42:28 +0200 Subject: [PATCH 21/58] add caching and test case for multiple compiles using roots.watch - closes #51 --- src/plugin.js | 10 ++++++++-- test/test.js | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/plugin.js b/src/plugin.js index c901750..0fde395 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -18,7 +18,7 @@ export default class RootsContentful { opts = { /* defaults */ - + cache: true, /* user-provided */ ...RootsContentful.opts } @@ -55,7 +55,13 @@ export default class RootsContentful { * @return {Promise} an array for the sorted contentful data */ async setup () { - let configuration = await configure_content(this.opts.content_types) + const { opts: { cache, content_types } } = this + const locals = this.roots.config.locals.contentful + // return cached locals if possible + if (cache && Object.keys(locals).length) { + return locals + } + let configuration = await configure_content(content_types) let content = await get_all_content(configuration) await set_urls(content) let entries = await transform_entries(content) diff --git a/test/test.js b/test/test.js index 72422ec..e112df7 100644 --- a/test/test.js +++ b/test/test.js @@ -167,6 +167,22 @@ describe('basic compile', function () { h.file.contains(p, this.title).should.be['true'] return h.file.contains(p, this.body).should.be['true'] }) + it('compiles multiple times including watch', async function () { + var project = new Roots(path.join(_path, 'basic')) + var watcher = null + project.on('error', function (e) { + throw new Error(e) + }) + project.on('done', function () { + var p = path.join(this['public'], 'index.html') + h.file.contains(p, this.title).should.be['true'] + h.file.contains(p, this.body).should.be['true'] + if (watcher != null) { + watcher.close() + } + }.bind(this)) + watcher = await project.watch() + }) return after(function () { return unmock_contentful() }) From a92576b60ceba09e9c8c00a26c46eefda0504bf9 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 23 Dec 2015 14:46:49 +0200 Subject: [PATCH 22/58] begin porting tests to AVA --- .../alt-content-type-config/app.coffee | 12 ++++ .../alt-content-type-config/index.jade | 5 ++ .../alt-content-type-config/package.json | 6 ++ old_test/fixtures/basic/about.jade | 1 + old_test/fixtures/basic/app.coffee | 18 ++++++ old_test/fixtures/basic/index.jade | 5 ++ old_test/fixtures/basic/package.json | 6 ++ old_test/fixtures/custom_name/about.jade | 1 + old_test/fixtures/custom_name/app.coffee | 19 ++++++ old_test/fixtures/custom_name/index.jade | 5 ++ old_test/fixtures/custom_name/package.json | 6 ++ .../fixtures/image_view_helper/app.coffee | 18 ++++++ .../fixtures/image_view_helper/index.jade | 4 ++ .../fixtures/image_view_helper/package.json | 6 ++ old_test/fixtures/missing_config/app.coffee | 18 ++++++ old_test/fixtures/missing_config/index.jade | 5 ++ old_test/fixtures/missing_config/package.json | 6 ++ old_test/fixtures/missing_token/app.coffee | 16 +++++ old_test/fixtures/missing_token/index.jade | 1 + old_test/fixtures/missing_token/package.json | 6 ++ old_test/fixtures/single_entry/app.coffee | 20 ++++++ old_test/fixtures/single_entry/index.jade | 4 ++ old_test/fixtures/single_entry/package.json | 6 ++ .../single_entry/views/_blog_post.jade | 3 + .../fixtures/single_entry_custom/app.coffee | 22 +++++++ .../fixtures/single_entry_custom/index.jade | 1 + .../fixtures/single_entry_custom/package.json | 7 +++ .../single_entry_custom/views/_blog_post.jade | 3 + .../fixtures/single_entry_multi/app.coffee | 22 +++++++ .../fixtures/single_entry_multi/index.jade | 7 +++ .../fixtures/single_entry_multi/package.json | 7 +++ .../single_entry_multi/views/_blog_post.jade | 4 ++ old_test/fixtures/sort/about.jade | 1 + old_test/fixtures/sort/app.coffee | 20 ++++++ old_test/fixtures/sort/index.jade | 5 ++ old_test/fixtures/sort/package.json | 6 ++ old_test/fixtures/sort/posts_expected.json | 1 + old_test/fixtures/transform/about.jade | 1 + old_test/fixtures/transform/app.coffee | 21 +++++++ old_test/fixtures/transform/index.jade | 5 ++ old_test/fixtures/transform/package.json | 6 ++ .../fixtures/transform/posts_expected.json | 1 + old_test/fixtures/write/about.jade | 1 + old_test/fixtures/write/app.coffee | 16 +++++ old_test/fixtures/write/index.jade | 5 ++ old_test/fixtures/write/package.json | 6 ++ {test => old_test}/mocha.opts | 0 {test => old_test}/test.js | 63 ++++++++++++++----- package.json | 5 +- src/plugin.js | 6 +- test/_helpers.js | 8 +++ test/_setup.js | 46 ++++++++++++++ test/basic.js | 29 +++++++++ test/fixtures/basic/about.jade | 2 +- 54 files changed, 506 insertions(+), 18 deletions(-) create mode 100644 old_test/fixtures/alt-content-type-config/app.coffee create mode 100644 old_test/fixtures/alt-content-type-config/index.jade create mode 100644 old_test/fixtures/alt-content-type-config/package.json create mode 100644 old_test/fixtures/basic/about.jade create mode 100644 old_test/fixtures/basic/app.coffee create mode 100644 old_test/fixtures/basic/index.jade create mode 100644 old_test/fixtures/basic/package.json create mode 100644 old_test/fixtures/custom_name/about.jade create mode 100644 old_test/fixtures/custom_name/app.coffee create mode 100644 old_test/fixtures/custom_name/index.jade create mode 100644 old_test/fixtures/custom_name/package.json create mode 100644 old_test/fixtures/image_view_helper/app.coffee create mode 100644 old_test/fixtures/image_view_helper/index.jade create mode 100644 old_test/fixtures/image_view_helper/package.json create mode 100644 old_test/fixtures/missing_config/app.coffee create mode 100644 old_test/fixtures/missing_config/index.jade create mode 100644 old_test/fixtures/missing_config/package.json create mode 100644 old_test/fixtures/missing_token/app.coffee create mode 100644 old_test/fixtures/missing_token/index.jade create mode 100644 old_test/fixtures/missing_token/package.json create mode 100644 old_test/fixtures/single_entry/app.coffee create mode 100644 old_test/fixtures/single_entry/index.jade create mode 100644 old_test/fixtures/single_entry/package.json create mode 100644 old_test/fixtures/single_entry/views/_blog_post.jade create mode 100644 old_test/fixtures/single_entry_custom/app.coffee create mode 100644 old_test/fixtures/single_entry_custom/index.jade create mode 100644 old_test/fixtures/single_entry_custom/package.json create mode 100644 old_test/fixtures/single_entry_custom/views/_blog_post.jade create mode 100644 old_test/fixtures/single_entry_multi/app.coffee create mode 100644 old_test/fixtures/single_entry_multi/index.jade create mode 100644 old_test/fixtures/single_entry_multi/package.json create mode 100644 old_test/fixtures/single_entry_multi/views/_blog_post.jade create mode 100644 old_test/fixtures/sort/about.jade create mode 100644 old_test/fixtures/sort/app.coffee create mode 100644 old_test/fixtures/sort/index.jade create mode 100644 old_test/fixtures/sort/package.json create mode 100644 old_test/fixtures/sort/posts_expected.json create mode 100644 old_test/fixtures/transform/about.jade create mode 100644 old_test/fixtures/transform/app.coffee create mode 100644 old_test/fixtures/transform/index.jade create mode 100644 old_test/fixtures/transform/package.json create mode 100644 old_test/fixtures/transform/posts_expected.json create mode 100644 old_test/fixtures/write/about.jade create mode 100644 old_test/fixtures/write/app.coffee create mode 100644 old_test/fixtures/write/index.jade create mode 100644 old_test/fixtures/write/package.json rename {test => old_test}/mocha.opts (100%) rename {test => old_test}/test.js (89%) create mode 100644 test/_helpers.js create mode 100644 test/_setup.js create mode 100644 test/basic.js diff --git a/old_test/fixtures/alt-content-type-config/app.coffee b/old_test/fixtures/alt-content-type-config/app.coffee new file mode 100644 index 0000000..c3d2b72 --- /dev/null +++ b/old_test/fixtures/alt-content-type-config/app.coffee @@ -0,0 +1,12 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: + blog_posts: + id: '6BYT1gNiIEyIw8Og8aQAO6' + ] diff --git a/old_test/fixtures/alt-content-type-config/index.jade b/old_test/fixtures/alt-content-type-config/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/old_test/fixtures/alt-content-type-config/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/old_test/fixtures/alt-content-type-config/package.json b/old_test/fixtures/alt-content-type-config/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/alt-content-type-config/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/basic/about.jade b/old_test/fixtures/basic/about.jade new file mode 100644 index 0000000..2763786 --- /dev/null +++ b/old_test/fixtures/basic/about.jade @@ -0,0 +1 @@ +h1 wow \ No newline at end of file diff --git a/old_test/fixtures/basic/app.coffee b/old_test/fixtures/basic/app.coffee new file mode 100644 index 0000000..c2c261c --- /dev/null +++ b/old_test/fixtures/basic/app.coffee @@ -0,0 +1,18 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + { + id: '7CDlVsacqQc88cmIEGYWMa' + } + ] + ) + ] diff --git a/old_test/fixtures/basic/index.jade b/old_test/fixtures/basic/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/old_test/fixtures/basic/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/old_test/fixtures/basic/package.json b/old_test/fixtures/basic/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/basic/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/custom_name/about.jade b/old_test/fixtures/custom_name/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/old_test/fixtures/custom_name/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/old_test/fixtures/custom_name/app.coffee b/old_test/fixtures/custom_name/app.coffee new file mode 100644 index 0000000..af220ac --- /dev/null +++ b/old_test/fixtures/custom_name/app.coffee @@ -0,0 +1,19 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + name: 'press_links' + }, + { + id: '7CDlVsacqQc88cmIEGYWMa' + } + ] + ) + ] diff --git a/old_test/fixtures/custom_name/index.jade b/old_test/fixtures/custom_name/index.jade new file mode 100644 index 0000000..190e01d --- /dev/null +++ b/old_test/fixtures/custom_name/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.press_links + li + h1= p.title + p= p.body diff --git a/old_test/fixtures/custom_name/package.json b/old_test/fixtures/custom_name/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/custom_name/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/image_view_helper/app.coffee b/old_test/fixtures/image_view_helper/app.coffee new file mode 100644 index 0000000..c2c261c --- /dev/null +++ b/old_test/fixtures/image_view_helper/app.coffee @@ -0,0 +1,18 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + { + id: '7CDlVsacqQc88cmIEGYWMa' + } + ] + ) + ] diff --git a/old_test/fixtures/image_view_helper/index.jade b/old_test/fixtures/image_view_helper/index.jade new file mode 100644 index 0000000..3eee69d --- /dev/null +++ b/old_test/fixtures/image_view_helper/index.jade @@ -0,0 +1,4 @@ +ul + - for p in contentful.blog_posts + li + img(src!= asset(p.image, {w: 100, h: 100})) diff --git a/old_test/fixtures/image_view_helper/package.json b/old_test/fixtures/image_view_helper/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/image_view_helper/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/missing_config/app.coffee b/old_test/fixtures/missing_config/app.coffee new file mode 100644 index 0000000..2a318b6 --- /dev/null +++ b/old_test/fixtures/missing_config/app.coffee @@ -0,0 +1,18 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + name: 'test' + } + { + id: '7CDlVsacqQc88cmIEGYWMa' + } + ] + ) + ] diff --git a/old_test/fixtures/missing_config/index.jade b/old_test/fixtures/missing_config/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/old_test/fixtures/missing_config/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/old_test/fixtures/missing_config/package.json b/old_test/fixtures/missing_config/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/missing_config/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/missing_token/app.coffee b/old_test/fixtures/missing_token/app.coffee new file mode 100644 index 0000000..6b8b34a --- /dev/null +++ b/old_test/fixtures/missing_token/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + content_types: [ + { + name: 'test' + } + { + id: '7CDlVsacqQc88cmIEGYWMa' + } + ] + ) + ] diff --git a/old_test/fixtures/missing_token/index.jade b/old_test/fixtures/missing_token/index.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/old_test/fixtures/missing_token/index.jade @@ -0,0 +1 @@ +h1 wow diff --git a/old_test/fixtures/missing_token/package.json b/old_test/fixtures/missing_token/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/missing_token/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/single_entry/app.coffee b/old_test/fixtures/single_entry/app.coffee new file mode 100644 index 0000000..b9d39a8 --- /dev/null +++ b/old_test/fixtures/single_entry/app.coffee @@ -0,0 +1,20 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + name: 'blog_posts' + template: 'views/_blog_post.jade' + } + ] + ) + ] + + locals: + wow: 'such local' diff --git a/old_test/fixtures/single_entry/index.jade b/old_test/fixtures/single_entry/index.jade new file mode 100644 index 0000000..b881d53 --- /dev/null +++ b/old_test/fixtures/single_entry/index.jade @@ -0,0 +1,4 @@ +- for p in contentful.blog_posts + h1= p.title + p= p.body + a(href= p._url) Link diff --git a/old_test/fixtures/single_entry/package.json b/old_test/fixtures/single_entry/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/single_entry/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/single_entry/views/_blog_post.jade b/old_test/fixtures/single_entry/views/_blog_post.jade new file mode 100644 index 0000000..78a5b6c --- /dev/null +++ b/old_test/fixtures/single_entry/views/_blog_post.jade @@ -0,0 +1,3 @@ +h1= entry.title +p= entry.body +p= wow diff --git a/old_test/fixtures/single_entry_custom/app.coffee b/old_test/fixtures/single_entry_custom/app.coffee new file mode 100644 index 0000000..a73a4bc --- /dev/null +++ b/old_test/fixtures/single_entry_custom/app.coffee @@ -0,0 +1,22 @@ +slugify = require 'underscore.string/slugify' +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + name: 'blog_posts' + template: 'views/_blog_post.jade' + path: (e) -> "blogging/#{e.category}/#{slugify(e.title)}" + } + ] + ) + ] + + locals: + wow: 'such local' diff --git a/old_test/fixtures/single_entry_custom/index.jade b/old_test/fixtures/single_entry_custom/index.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/old_test/fixtures/single_entry_custom/index.jade @@ -0,0 +1 @@ +h1 wow diff --git a/old_test/fixtures/single_entry_custom/package.json b/old_test/fixtures/single_entry_custom/package.json new file mode 100644 index 0000000..1a248fc --- /dev/null +++ b/old_test/fixtures/single_entry_custom/package.json @@ -0,0 +1,7 @@ +{ + "name": "test", + "dependencies": { + "jade": "*", + "underscore.string": "*" + } +} diff --git a/old_test/fixtures/single_entry_custom/views/_blog_post.jade b/old_test/fixtures/single_entry_custom/views/_blog_post.jade new file mode 100644 index 0000000..78a5b6c --- /dev/null +++ b/old_test/fixtures/single_entry_custom/views/_blog_post.jade @@ -0,0 +1,3 @@ +h1= entry.title +p= entry.body +p= wow diff --git a/old_test/fixtures/single_entry_multi/app.coffee b/old_test/fixtures/single_entry_multi/app.coffee new file mode 100644 index 0000000..503e364 --- /dev/null +++ b/old_test/fixtures/single_entry_multi/app.coffee @@ -0,0 +1,22 @@ +slugify = require 'underscore.string/slugify' +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + name: 'blog_posts' + template: 'views/_blog_post.jade' + path: (e) -> ("#{lang}/#{slugify(e.title)}" for lang in ['en', 'fr']) + } + ] + ) + ] + + locals: + wow: 'such local' diff --git a/old_test/fixtures/single_entry_multi/index.jade b/old_test/fixtures/single_entry_multi/index.jade new file mode 100644 index 0000000..ee98265 --- /dev/null +++ b/old_test/fixtures/single_entry_multi/index.jade @@ -0,0 +1,7 @@ +- for p in contentful.blog_posts + h1= p.title + p= p.body + ul + each url in p._urls + li + a(href=url)= url diff --git a/old_test/fixtures/single_entry_multi/package.json b/old_test/fixtures/single_entry_multi/package.json new file mode 100644 index 0000000..1a248fc --- /dev/null +++ b/old_test/fixtures/single_entry_multi/package.json @@ -0,0 +1,7 @@ +{ + "name": "test", + "dependencies": { + "jade": "*", + "underscore.string": "*" + } +} diff --git a/old_test/fixtures/single_entry_multi/views/_blog_post.jade b/old_test/fixtures/single_entry_multi/views/_blog_post.jade new file mode 100644 index 0000000..4355d6d --- /dev/null +++ b/old_test/fixtures/single_entry_multi/views/_blog_post.jade @@ -0,0 +1,4 @@ +h1= entry.title +p= entry.body +p= entry._url +p= wow diff --git a/old_test/fixtures/sort/about.jade b/old_test/fixtures/sort/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/old_test/fixtures/sort/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/old_test/fixtures/sort/app.coffee b/old_test/fixtures/sort/app.coffee new file mode 100644 index 0000000..22f1105 --- /dev/null +++ b/old_test/fixtures/sort/app.coffee @@ -0,0 +1,20 @@ +contentful = require '../../../src' + +megaSort = (a, b)-> + a.title.localeCompare(b.title) + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6', + write: 'posts.json', + sort: megaSort + } + ] + ) + ] diff --git a/old_test/fixtures/sort/index.jade b/old_test/fixtures/sort/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/old_test/fixtures/sort/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/old_test/fixtures/sort/package.json b/old_test/fixtures/sort/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/sort/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/sort/posts_expected.json b/old_test/fixtures/sort/posts_expected.json new file mode 100644 index 0000000..2801a26 --- /dev/null +++ b/old_test/fixtures/sort/posts_expected.json @@ -0,0 +1 @@ +[{"title":"Title A","body":"Nothing interesting"},{"title":"Title B","body":"Something else"},{"title":"Title C","body":"Rich Boy selling crick"}] \ No newline at end of file diff --git a/old_test/fixtures/transform/about.jade b/old_test/fixtures/transform/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/old_test/fixtures/transform/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/old_test/fixtures/transform/app.coffee b/old_test/fixtures/transform/app.coffee new file mode 100644 index 0000000..68f04ec --- /dev/null +++ b/old_test/fixtures/transform/app.coffee @@ -0,0 +1,21 @@ +contentful = require '../../../src' + +megaTransform = (entry)-> + delete entry.body + entry + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6', + write: 'posts.json', + transform: megaTransform + } + ] + ) + ] diff --git a/old_test/fixtures/transform/index.jade b/old_test/fixtures/transform/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/old_test/fixtures/transform/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/old_test/fixtures/transform/package.json b/old_test/fixtures/transform/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/transform/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/old_test/fixtures/transform/posts_expected.json b/old_test/fixtures/transform/posts_expected.json new file mode 100644 index 0000000..719b928 --- /dev/null +++ b/old_test/fixtures/transform/posts_expected.json @@ -0,0 +1 @@ +[{"title":"Title C"},{"title":"Title B"},{"title":"Title A"}] \ No newline at end of file diff --git a/old_test/fixtures/write/about.jade b/old_test/fixtures/write/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/old_test/fixtures/write/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/old_test/fixtures/write/app.coffee b/old_test/fixtures/write/app.coffee new file mode 100644 index 0000000..4bd0770 --- /dev/null +++ b/old_test/fixtures/write/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6', + write: 'posts.json' + } + ] + ) + ] diff --git a/old_test/fixtures/write/index.jade b/old_test/fixtures/write/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/old_test/fixtures/write/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/old_test/fixtures/write/package.json b/old_test/fixtures/write/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/old_test/fixtures/write/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/mocha.opts b/old_test/mocha.opts similarity index 100% rename from test/mocha.opts rename to old_test/mocha.opts diff --git a/test/test.js b/old_test/test.js similarity index 89% rename from test/test.js rename to old_test/test.js index e112df7..66ebbf9 100644 --- a/test/test.js +++ b/old_test/test.js @@ -139,6 +139,8 @@ describe('contentful content type fields', function () { }) describe('basic compile', function () { + var project = new Roots(path.join(_path, 'basic')) + var util = new RootsUtil(project) before(function (done) { this.title = 'Throw Some Ds' this.body = 'Rich Boy selling crick' @@ -152,6 +154,7 @@ describe('basic compile', function () { } ] }) + util.write('../about.jade', 'h1 wow') return compile_fixture.call(this, 'basic').then(function () { return done() })['catch'](done) @@ -168,22 +171,54 @@ describe('basic compile', function () { return h.file.contains(p, this.body).should.be['true'] }) it('compiles multiple times including watch', async function () { - var project = new Roots(path.join(_path, 'basic')) - var watcher = null - project.on('error', function (e) { - throw new Error(e) - }) - project.on('done', function () { - var p = path.join(this['public'], 'index.html') - h.file.contains(p, this.title).should.be['true'] - h.file.contains(p, this.body).should.be['true'] - if (watcher != null) { - watcher.close() - } - }.bind(this)) - watcher = await project.watch() + var index = path.join(this['public'], 'index.html') + var about = path.join(this['public'], 'about.html') + + console.log('starting watcher') + var watcher = await project.watch() + + console.log('compile 1') + h.file.contains(index, this.title).should.be['true'] + h.file.contains(index, this.body).should.be['true'] + + console.log('modifying fixture') + await util.write('../about.jade', 'h1 Chuck some Ds\nh2 Wealthy lad peddling crick') + + console.log('verifying changes') + h.file.contains(about, 'Chuck some Ds').should.be['true'] + h.file.contains(about, 'Wealthy lad peddling crick').should.be['true'] + + console.log('closing watcher') + watcher.close() + console.log('watcher closed') + + // var watcher = null + // var second_compile = false + // project.on('done', async function () { + // var p = path.join(this['public'], 'index.html') + // if (!second_compile) { + // console.log('compile 1') + // h.file.contains(p, this.title).should.be['true'] + // h.file.contains(p, this.body).should.be['true'] + // second_compile = true + // } else { + // console.log('compile 2') + // h.file.contains(p, 'Chuck some Ds').should.be['true'] + // h.file.contains(p, 'Wealthy lad peddling crick').should.be['true'] + // console.log(watcher) + // if (watcher != null) { + // await watcher.close() + // console.log('watcher closed') + // } + // } + // }.bind(this)) + // watcher = await project.watch() + // console.log('watcher opened') + // console.log(watcher) + // await util.write('../about.jade', 'h1 Chuck some Ds\nh2 Wealthy lad peddling crick') }) return after(function () { + util.write('../about.jade', 'h1 wow') return unmock_contentful() }) }) diff --git a/package.json b/package.json index 5c53828..0521418 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,12 @@ "underscore.string": "^3.2.2" }, "devDependencies": { - "ava": "^0.7.0", + "ava": "github:sindresorhus/ava", "babel-cli": "^6.3.15", "babel-eslint": "^4.1.6", "babel-plugin-transform-runtime": "^6.3.13", "babel-preset-es2015": "^6.3.13", + "babel-preset-es2015-node5": "^1.1.1", "babel-preset-stage-0": "^6.3.13", "babel-register": "^6.3.13", "chai": "3.4.1", @@ -81,7 +82,7 @@ "release": "npm publish", "postpublish": "git push --follow-tags", "pretest": "npm run lint", - "test": "mocha", + "test": "ava", "coverage": "babel-node ./node_modules/.bin/isparta cover _mocha", "precoveralls": "npm run coverage", "coveralls": "coveralls < coverage/lcov.info" diff --git a/src/plugin.js b/src/plugin.js index 0fde395..9adf601 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -55,10 +55,13 @@ export default class RootsContentful { * @return {Promise} an array for the sorted contentful data */ async setup () { + console.time('async') + console.time('cached') const { opts: { cache, content_types } } = this - const locals = this.roots.config.locals.contentful + let locals = this.roots.config.locals.contentful // return cached locals if possible if (cache && Object.keys(locals).length) { + console.timeEnd('cached') return locals } let configuration = await configure_content(content_types) @@ -69,6 +72,7 @@ export default class RootsContentful { await this::set_locals(sorted) await this::compile_entries(sorted) await this::write_entries(sorted) + console.timeEnd('async') return sorted } diff --git a/test/_helpers.js b/test/_helpers.js new file mode 100644 index 0000000..f68328f --- /dev/null +++ b/test/_helpers.js @@ -0,0 +1,8 @@ +import path from 'path' +import RootsUtil from 'roots-util' + +let helpers = new RootsUtil.Helpers({ + base: path.join(__dirname, './fixtures') +}) + +export default helpers diff --git a/test/_setup.js b/test/_setup.js new file mode 100644 index 0000000..3be5216 --- /dev/null +++ b/test/_setup.js @@ -0,0 +1,46 @@ +import mockery from 'mockery' +import Roots from 'roots' +import helpers from './_helpers' + +export default { + async compile_fixture (name) { + this.context.public = `${name}/public` + return await helpers.project.compile(Roots, name) + }, + unmock_contentful () { + mockery.deregisterAll() + return mockery.disable() + }, + mock_contentful (opts = {}) { + mockery.enable({ + warnOnUnregistered: false, + useCleanCache: true + }) + opts = { + entries: [{ + sys: { sys: 'data' }, + fields: { + title: 'Default Title', + body: 'Default Body' + } + }], + content_type: { + name: 'Blog Post', + displayField: 'title' + }, + ...opts + } + return mockery.registerMock('contentful', { + createClient () { + return { + contentType () { + return Promise.resolve(opts.content_type) + }, + entries () { + return Promise.resolve(opts.entries) + } + } + } + }) + } +} diff --git a/test/basic.js b/test/basic.js new file mode 100644 index 0000000..e5e1975 --- /dev/null +++ b/test/basic.js @@ -0,0 +1,29 @@ +import test from 'ava' +import 'babel-register' +import helpers from './_helpers' +import setup from './_setup' + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let title = 'Throw Some Ds' + let body = 'Rich Boy selling crick' + t.context = { ...t.context, title, body } + setup.mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) + return await t::setup.compile_fixture('basic') +}) + +test('compiles basic project', t => { + return t.ok(helpers.file.exists(`${t.context.public}/index.html`)) +}) + +test.after(async t => { + setup.unmock_contentful() + return await helpers.project.remove_folders('**/public') +}) diff --git a/test/fixtures/basic/about.jade b/test/fixtures/basic/about.jade index 8240f0e..2763786 100644 --- a/test/fixtures/basic/about.jade +++ b/test/fixtures/basic/about.jade @@ -1 +1 @@ -h1 wow +h1 wow \ No newline at end of file From 16ce55e94daf375bd6209f2ebeca70303d4df18c Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 5 Jan 2016 15:15:08 +0200 Subject: [PATCH 23/58] remove superflous package.json details, get tests running --- .babelrc | 2 +- .travis.yml | 5 +--- package.json | 64 ++++++++++++++++++----------------------- src/plugin.js | 4 --- test/_setup.js | 78 +++++++++++++++++++++++++------------------------- test/basic.js | 18 +++++++----- 6 files changed, 80 insertions(+), 91 deletions(-) diff --git a/.babelrc b/.babelrc index 7683066..9595dd6 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["es2015", "stage-0"], + "presets": ["es2015-node5", "stage-0"], "plugins": [] } diff --git a/.travis.yml b/.travis.yml index 80838f3..3a2448c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ language: node_js node_js: - - "0.12" - - "0.11" - - "0.10" - - "iojs" + - "5" after_script: - npm run coveralls sudo: false diff --git a/package.json b/package.json index 0521418..7b1fada 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "url": "https://github.com/carrot/roots-contentful/issues" }, "dependencies": { - "babel-polyfill": "^6.3.14", "babel-runtime": "^6.3.13", "contentful": "^1.1.5", "pluralize": "^1.2.1", @@ -15,45 +14,39 @@ "underscore.string": "^3.2.2" }, "devDependencies": { - "ava": "github:sindresorhus/ava", + "ava": "sindresorhus/ava#65ae07c", "babel-cli": "^6.3.15", + "babel-core": "^6.3.26", "babel-eslint": "^4.1.6", - "babel-plugin-transform-runtime": "^6.3.13", - "babel-preset-es2015": "^6.3.13", "babel-preset-es2015-node5": "^1.1.1", "babel-preset-stage-0": "^6.3.13", - "babel-register": "^6.3.13", - "chai": "3.4.1", - "chai-as-promised": "5.1.0", - "coffee-script": "1.10.0", - "coveralls": "2.x", + "coffee-script": "^1.10.0", "husky": "^0.10.2", - "isparta": "^4.0.0", - "istanbul": "0.4.1", - "lodash": "^3.10.1", - "mocha": "2.3.4", "mockery": "1.4.x", "roots": "3.1.0", "snazzy": "^2.0.1", - "standard": "^5.4.1", - "when": "^3.7.5" + "standard": "^5.4.1" }, "directories": { "lib": "lib", "test": "test" }, + "engines": { + "node": ">=5.0.0", + "npm": ">=3.0.0" + }, "homepage": "https://github.com/carrot/roots-contentful", "keywords": [ - "roots", - "contentful", + "cms", "content", + "contentful", "dynamic", - "cms", - "management", - "system", "extension", + "management", "plugin", - "roots-extension" + "roots", + "roots-extension", + "system" ], "license": "MIT", "main": "lib", @@ -61,6 +54,20 @@ "type": "git", "url": "https://github.com/carrot/roots-contentful.git" }, + "scripts": { + "build": "babel src -d lib", + "coverage": "babel-node ./node_modules/.bin/isparta cover _mocha", + "coveralls": "coveralls < coverage/lcov.info", + "lint": "standard --verbose | snazzy", + "postpublish": "git push --follow-tags", + "prebuild": "npm test", + "precommit": "npm run lint -s", + "precoveralls": "npm run coverage", + "prerelease": "npm run build", + "pretest": "npm run lint -s", + "release": "npm publish", + "test": "ava --require babel-core/register --require coffee-script/register" + }, "standard": { "parser": "babel-eslint", "globals": [ @@ -71,20 +78,5 @@ "beforeAll", "afterAll" ] - }, - "scripts": { - "precommit": "npm run lint", - "prepush": "npm run lint", - "lint": "standard --verbose | snazzy", - "prebuild": "npm test", - "build": "babel src -d lib", - "prerelease": "npm run build", - "release": "npm publish", - "postpublish": "git push --follow-tags", - "pretest": "npm run lint", - "test": "ava", - "coverage": "babel-node ./node_modules/.bin/isparta cover _mocha", - "precoveralls": "npm run coverage", - "coveralls": "coveralls < coverage/lcov.info" } } diff --git a/src/plugin.js b/src/plugin.js index 9adf601..e4e66de 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -55,13 +55,10 @@ export default class RootsContentful { * @return {Promise} an array for the sorted contentful data */ async setup () { - console.time('async') - console.time('cached') const { opts: { cache, content_types } } = this let locals = this.roots.config.locals.contentful // return cached locals if possible if (cache && Object.keys(locals).length) { - console.timeEnd('cached') return locals } let configuration = await configure_content(content_types) @@ -72,7 +69,6 @@ export default class RootsContentful { await this::set_locals(sorted) await this::compile_entries(sorted) await this::write_entries(sorted) - console.timeEnd('async') return sorted } diff --git a/test/_setup.js b/test/_setup.js index 3be5216..bceb6d7 100644 --- a/test/_setup.js +++ b/test/_setup.js @@ -2,45 +2,45 @@ import mockery from 'mockery' import Roots from 'roots' import helpers from './_helpers' -export default { - async compile_fixture (name) { - this.context.public = `${name}/public` - return await helpers.project.compile(Roots, name) - }, - unmock_contentful () { - mockery.deregisterAll() - return mockery.disable() - }, - mock_contentful (opts = {}) { - mockery.enable({ - warnOnUnregistered: false, - useCleanCache: true - }) - opts = { - entries: [{ - sys: { sys: 'data' }, - fields: { - title: 'Default Title', - body: 'Default Body' - } - }], - content_type: { - name: 'Blog Post', - displayField: 'title' - }, - ...opts - } - return mockery.registerMock('contentful', { - createClient () { - return { - contentType () { - return Promise.resolve(opts.content_type) - }, - entries () { - return Promise.resolve(opts.entries) - } - } +export async function compile_fixture (name) { + this.context.public = `${name}/public` + return await helpers.project.compile(Roots, name) +} + +export function unmock_contentful () { + mockery.deregisterAll() + return mockery.disable() +} + +export function mock_contentful (opts = {}) { + mockery.enable({ + warnOnUnregistered: false, + useCleanCache: true + }) + opts = { + entries: [{ + sys: { sys: 'data' }, + fields: { + title: 'Default Title', + body: 'Default Body' } - }) + }], + content_type: { + name: 'Blog Post', + displayField: 'title' + }, + ...opts } + return mockery.registerMock('contentful', { + createClient () { + return { + contentType () { + return Promise.resolve(opts.content_type) + }, + entries () { + return Promise.resolve(opts.entries) + } + } + } + }) } diff --git a/test/basic.js b/test/basic.js index e5e1975..2f3d110 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,29 +1,33 @@ import test from 'ava' import 'babel-register' import helpers from './_helpers' -import setup from './_setup' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' test.cb.before(t => { helpers.project.install_dependencies('*', t.end) }) -test.before(async t => { +test.beforeEach(async t => { let title = 'Throw Some Ds' let body = 'Rich Boy selling crick' t.context = { ...t.context, title, body } - setup.mock_contentful({ + mock_contentful({ entries: [{ fields: { title, body } }] }) - return await t::setup.compile_fixture('basic') + await t::compile_fixture('basic') }) test('compiles basic project', t => { - return t.ok(helpers.file.exists(`${t.context.public}/index.html`)) + t.ok(helpers.file.exists(`${t.context.public}/index.html`)) }) test.after(async t => { - setup.unmock_contentful() - return await helpers.project.remove_folders('**/public') + unmock_contentful() + await helpers.project.remove_folders('**/public') }) From 621fef02390603052398ef3535654f78b2d732a2 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 5 Jan 2016 15:51:23 +0200 Subject: [PATCH 24/58] switch to nyc code coverage --- .babelrc | 3 ++- .gitignore | 1 + .npmignore | 1 + .travis.yml | 2 +- package.json | 8 +++++--- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.babelrc b/.babelrc index 9595dd6..ffb6aa3 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,5 @@ { "presets": ["es2015-node5", "stage-0"], - "plugins": [] + "plugins": [], + "sourceMaps": "inline" } diff --git a/.gitignore b/.gitignore index a727f2f..d1412c4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ lib-cov .grunt .lock-wscript build/Release +.nyc_output diff --git a/.npmignore b/.npmignore index 9b7555b..556ae53 100644 --- a/.npmignore +++ b/.npmignore @@ -6,3 +6,4 @@ src .babelrc wallaby.js Makefile +.nyc_output diff --git a/.travis.yml b/.travis.yml index 3a2448c..a2d8312 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,5 @@ language: node_js node_js: - "5" after_script: - - npm run coveralls + - npm run coverage sudo: false diff --git a/package.json b/package.json index 7b1fada..847ad9b 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,10 @@ "babel-preset-es2015-node5": "^1.1.1", "babel-preset-stage-0": "^6.3.13", "coffee-script": "^1.10.0", + "coveralls": "^2.11.6", "husky": "^0.10.2", "mockery": "1.4.x", + "nyc": "^5.2.0", "roots": "3.1.0", "snazzy": "^2.0.1", "standard": "^5.4.1" @@ -56,8 +58,8 @@ }, "scripts": { "build": "babel src -d lib", - "coverage": "babel-node ./node_modules/.bin/isparta cover _mocha", - "coveralls": "coveralls < coverage/lcov.info", + "precoverage": "nyc npm test", + "coverage": "nyc report --reporter=text-lcov | coveralls", "lint": "standard --verbose | snazzy", "postpublish": "git push --follow-tags", "prebuild": "npm test", @@ -66,7 +68,7 @@ "prerelease": "npm run build", "pretest": "npm run lint -s", "release": "npm publish", - "test": "ava --require babel-core/register --require coffee-script/register" + "test": "nyc ava --require babel-core/register --require coffee-script/register" }, "standard": { "parser": "babel-eslint", From ae7bfcf99e2274dffc15c9b2c863cdf8f8063763 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 5 Jan 2016 15:56:32 +0200 Subject: [PATCH 25/58] remove superfluous babel-register --- test/basic.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/basic.js b/test/basic.js index 2f3d110..8605705 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,5 +1,4 @@ import test from 'ava' -import 'babel-register' import helpers from './_helpers' import { mock_contentful, From c65efeecb997c4194d4772c9ee086eebc58ba790 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 5 Jan 2016 16:34:27 +0200 Subject: [PATCH 26/58] better commonjs interoperability --- .babelrc | 2 +- package.json | 1 + src/{plugin.js => extension.js} | 0 src/index.js | 9 ++------- 4 files changed, 4 insertions(+), 8 deletions(-) rename src/{plugin.js => extension.js} (100%) diff --git a/.babelrc b/.babelrc index ffb6aa3..ab793d9 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,5 @@ { "presets": ["es2015-node5", "stage-0"], - "plugins": [], + "plugins": ["add-module-exports"], "sourceMaps": "inline" } diff --git a/package.json b/package.json index 847ad9b..cbabcc6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "babel-cli": "^6.3.15", "babel-core": "^6.3.26", "babel-eslint": "^4.1.6", + "babel-plugin-add-module-exports": "^0.1.2", "babel-preset-es2015-node5": "^1.1.1", "babel-preset-stage-0": "^6.3.13", "coffee-script": "^1.10.0", diff --git a/src/plugin.js b/src/extension.js similarity index 100% rename from src/plugin.js rename to src/extension.js diff --git a/src/index.js b/src/index.js index dc85607..7d8a3cf 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import RootsContentful from './plugin' +import RootsContentful from './extension' import errors from './errors' /** @@ -21,12 +21,7 @@ function validate (opts) { * @param {Object} opts - user-supplied settings * @return {Function} - the extension class */ -function extension (opts) { +export default function extension (opts) { RootsContentful.opts = validate(opts) return RootsContentful } - -// we need to explicitly use -// module.exports since we're -// targeting CommonJS, not ES6 Modules. -module.exports = extension From c9a5bb73ce8e74427972055ad760b2c65a2af7e7 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 5 Jan 2016 16:46:11 +0200 Subject: [PATCH 27/58] externalize helpers --- .babelrc | 2 +- package.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.babelrc b/.babelrc index ab793d9..1b2a49b 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,5 @@ { "presets": ["es2015-node5", "stage-0"], - "plugins": ["add-module-exports"], + "plugins": ["add-module-exports", "transform-runtime"], "sourceMaps": "inline" } diff --git a/package.json b/package.json index cbabcc6..ad350f7 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,10 @@ "babel-core": "^6.3.26", "babel-eslint": "^4.1.6", "babel-plugin-add-module-exports": "^0.1.2", + "babel-plugin-transform-runtime": "^6.3.13", "babel-preset-es2015-node5": "^1.1.1", "babel-preset-stage-0": "^6.3.13", + "babel-runtime": "^6.3.19", "coffee-script": "^1.10.0", "coveralls": "^2.11.6", "husky": "^0.10.2", From 483148ef4c6abfa3b159ce35f76da8e6a4f78592 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 5 Jan 2016 21:49:33 +0200 Subject: [PATCH 28/58] port basic-compile and config tests --- test/_setup.js | 2 +- test/{basic.js => basic-compile.js} | 16 ++++++++--- test/config.js | 44 +++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 5 deletions(-) rename test/{basic.js => basic-compile.js} (57%) create mode 100644 test/config.js diff --git a/test/_setup.js b/test/_setup.js index bceb6d7..f287f21 100644 --- a/test/_setup.js +++ b/test/_setup.js @@ -3,7 +3,7 @@ import Roots from 'roots' import helpers from './_helpers' export async function compile_fixture (name) { - this.context.public = `${name}/public` + this.public_dir = `${name}/public` return await helpers.project.compile(Roots, name) } diff --git a/test/basic.js b/test/basic-compile.js similarity index 57% rename from test/basic.js rename to test/basic-compile.js index 8605705..8363a54 100644 --- a/test/basic.js +++ b/test/basic-compile.js @@ -6,24 +6,32 @@ import { compile_fixture } from './_setup' +let ctx = {} + test.cb.before(t => { helpers.project.install_dependencies('*', t.end) }) -test.beforeEach(async t => { +test.before(async t => { let title = 'Throw Some Ds' let body = 'Rich Boy selling crick' - t.context = { ...t.context, title, body } + ctx = { ...ctx, title, body } mock_contentful({ entries: [{ fields: { title, body } }] }) - await t::compile_fixture('basic') + await ctx::compile_fixture('basic') + ctx.index_path = `${ctx.public_dir}/index.html` }) test('compiles basic project', t => { - t.ok(helpers.file.exists(`${t.context.public}/index.html`)) + t.ok(helpers.file.exists(ctx.index_path)) +}) + +test('has contentful data available in views', t => { + t.true(helpers.file.contains(ctx.index_path, ctx.title)) + t.true(helpers.file.contains(ctx.index_path, ctx.body)) }) test.after(async t => { diff --git a/test/config.js b/test/config.js new file mode 100644 index 0000000..23aea03 --- /dev/null +++ b/test/config.js @@ -0,0 +1,44 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let title = 'Gatorade' + let body = 'Yung Lean' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) +}) + +test('should throw an error when missing an access token', async t => { + t.throws(ctx::compile_fixture('missing_token')) +}) + +test('should throw an error without content type id', async t => { + t.throws(ctx::compile_fixture('missing_config')) +}) + +test('allows the content type name to be set through a k/v object config', async t => { + await ctx::compile_fixture('alt-content-type-config') + ctx.index_path = `${ctx.public_dir}/index.html` + t.true(helpers.file.contains(ctx.index_path, ctx.title)) + t.true(helpers.file.contains(ctx.index_path, ctx.body)) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) From 63c71275d6c3ae64512d1ecd6dc064d045f89efb Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 6 Jan 2016 13:19:25 +0200 Subject: [PATCH 29/58] port data-manipulation, json and content type tests --- test/contentful-content-type-fields.js | 30 +++++++++++++ test/data-manipulation-sort.js | 55 ++++++++++++++++++++++++ test/data-manipulation-transform.js | 58 ++++++++++++++++++++++++++ test/write-as-json.js | 42 +++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 test/contentful-content-type-fields.js create mode 100644 test/data-manipulation-sort.js create mode 100644 test/data-manipulation-transform.js create mode 100644 test/write-as-json.js diff --git a/test/contentful-content-type-fields.js b/test/contentful-content-type-fields.js new file mode 100644 index 0000000..cdd9507 --- /dev/null +++ b/test/contentful-content-type-fields.js @@ -0,0 +1,30 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + mock_contentful({ + entries: [{ + fields: { sys: 'test' } + }] + }) +}) + +test('should throw an error if `sys` is a field name', async t => { + t.throws(ctx::compile_fixture('basic')) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/data-manipulation-sort.js b/test/data-manipulation-sort.js new file mode 100644 index 0000000..39c035f --- /dev/null +++ b/test/data-manipulation-sort.js @@ -0,0 +1,55 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + ctx.titles = ['Title C', 'Title B', 'Title A'] + ctx.bodies = [ + 'Rich Boy selling crick', + 'Something else', + 'Nothing interesting' + ] + ctx.entries = ctx.titles.map((title, i) => ({ + fields: { title, body: ctx.bodies[i] } + })) + mock_contentful({ entries: ctx.entries }) + await ctx::compile_fixture('sort') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.posts_path = `${ctx.public_dir}/posts.json` +}) + +test('compiles project', t => { + t.ok(helpers.file.exists(ctx.index_path)) +}) + +test('orders data correctly for the project', t => { + t.plan(4) + // titles should be order A before B before C + t.true(helpers.file.contains_match( + ctx.index_path, + '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$' + )) + ctx.bodies.forEach(body => { + t.true(helpers.file.contains(ctx.index_path, body)) + }) +}) + +test('has written data as json', t => { + t.ok(helpers.file.exists(ctx.posts_path)) + t.true(helpers.file.matches_file(ctx.posts_path, 'sort/posts_expected.json')) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/data-manipulation-transform.js b/test/data-manipulation-transform.js new file mode 100644 index 0000000..4c50f00 --- /dev/null +++ b/test/data-manipulation-transform.js @@ -0,0 +1,58 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + ctx.titles = ['Title C', 'Title B', 'Title A'] + ctx.bodies = [ + 'Rich Boy selling crick', + 'Something else', + 'Nothing interesting' + ] + ctx.entries = ctx.titles.map((title, i) => ({ + fields: { title, body: ctx.bodies[i] } + })) + mock_contentful({ entries: ctx.entries }) + await ctx::compile_fixture('transform') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.posts_path = `${ctx.public_dir}/posts.json` +}) + +test('compiles project', t => { + t.ok(helpers.file.exists(ctx.index_path)) +}) + +test('does not reorder data', t => { + // titles should be order A before B before C + t.true(helpers.file.contains_match( + ctx.index_path, + '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$' + )) +}) + +test('has manipulated data correctly for the project', t => { + t.plan(3) + ctx.bodies.forEach(body => { + t.false(helpers.file.contains(ctx.index_path, body)) + }) +}) + +test('has written data as json', t => { + t.ok(helpers.file.exists(ctx.posts_path)) + t.true(helpers.file.matches_file(ctx.posts_path, 'transform/posts_expected.json')) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/write-as-json.js b/test/write-as-json.js new file mode 100644 index 0000000..be44093 --- /dev/null +++ b/test/write-as-json.js @@ -0,0 +1,42 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let title = 'Throw Some Ds' + let body = 'Rich Boy selling crick' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) + await ctx::compile_fixture('write') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.posts_path = `${ctx.public_dir}/posts.json` +}) + +test('compiles project', t => { + t.ok(helpers.file.exists(ctx.index_path)) +}) + +test('has written data as json', t => { + t.ok(helpers.file.exists(ctx.posts_path)) + t.true(helpers.file.contains(ctx.posts_path, ctx.title)) + t.true(helpers.file.contains(ctx.posts_path, ctx.body)) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) From 8ad22f9196ddbf66449aeae6252e87e3d4b059e7 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 6 Jan 2016 16:34:11 +0200 Subject: [PATCH 30/58] port single-entry-views & custom-name tests --- test/_setup.js | 12 ++++ test/custom-name-for-view-helper-local.js | 36 +++++++++++ ...clear-entry-locals-between-each-compile.js | 39 ++++++++++++ ...-entry-views-custom-multi-path-function.js | 60 +++++++++++++++++++ ...single-entry-views-custom-path-function.js | 40 +++++++++++++ ...ingle-entry-views-default-path-function.js | 48 +++++++++++++++ ...-entry-views-image-view-helper-function.js | 36 +++++++++++ 7 files changed, 271 insertions(+) create mode 100644 test/custom-name-for-view-helper-local.js create mode 100644 test/single-entry-views-clear-entry-locals-between-each-compile.js create mode 100644 test/single-entry-views-custom-multi-path-function.js create mode 100644 test/single-entry-views-custom-path-function.js create mode 100644 test/single-entry-views-default-path-function.js create mode 100644 test/single-entry-views-image-view-helper-function.js diff --git a/test/_setup.js b/test/_setup.js index f287f21..b823774 100644 --- a/test/_setup.js +++ b/test/_setup.js @@ -2,6 +2,18 @@ import mockery from 'mockery' import Roots from 'roots' import helpers from './_helpers' +// polyfill array includes because of +// https://github.com/sindresorhus/ava/issues/263 +/* eslint-disable */ +Array.prototype.includes = do { + typeof Array.prototype.includes === 'function' + ? Array.prototype.includes + : function includes (needle) { + return this.indexOf(needle) > -1 + } +} +/* eslint-enable */ + export async function compile_fixture (name) { this.public_dir = `${name}/public` return await helpers.project.compile(Roots, name) diff --git a/test/custom-name-for-view-helper-local.js b/test/custom-name-for-view-helper-local.js new file mode 100644 index 0000000..07a5894 --- /dev/null +++ b/test/custom-name-for-view-helper-local.js @@ -0,0 +1,36 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let title = 'Throw Some Ds' + let body = 'Rich Boy selling crack' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) + await ctx::compile_fixture('custom_name') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('has contentful data available in views under a custom name', t => { + t.true(helpers.file.contains(ctx.index_path, ctx.title)) + t.true(helpers.file.contains(ctx.index_path, ctx.body)) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/single-entry-views-clear-entry-locals-between-each-compile.js b/test/single-entry-views-clear-entry-locals-between-each-compile.js new file mode 100644 index 0000000..fa49cba --- /dev/null +++ b/test/single-entry-views-clear-entry-locals-between-each-compile.js @@ -0,0 +1,39 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let title = 'Wow such doge' + let body = 'such amaze' + let title_2 = 'Totes McGotes' + ctx = { ...ctx, title, title_2, body } + mock_contentful({ + entries: [ + { fields: { title, body } }, + { fields: { title: title_2 } } + ], + content_type: { name: 'Blog Post', displayField: 'title' } + }) + await ctx::compile_fixture('single_entry') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.post_path = `${ctx.public_dir}/blog_posts/totes-mcgotes.html` +}) + +test("should not have first entry's content in second entry's single view", t => { + t.false(helpers.file.contains(ctx.post_path, ctx.body)) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/single-entry-views-custom-multi-path-function.js b/test/single-entry-views-custom-multi-path-function.js new file mode 100644 index 0000000..111ea3b --- /dev/null +++ b/test/single-entry-views-custom-multi-path-function.js @@ -0,0 +1,60 @@ +import path from 'path' +import test from 'ava' +import slugify from 'underscore.string/slugify' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let titles = ['Real Talk', 'Fake Talk'] + let bodies = [ + "I'm not about to sit up here, and argue about who's to blame.", + "I'm about to sit up here, and not argue about who's not to blame." + ] + ctx = { ...ctx, titles, bodies, langs: ['en', 'fr'] } + mock_contentful({ + entries: titles.map((title, i) => ({ + fields: { title, body: bodies[i] } + })), + content_type: { name: 'Blog Post', displayField: 'title' } + }) + await ctx::compile_fixture('single_entry_multi') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('compiles a single entry to multiple files', t => { + t.plan(16) + ctx.langs.forEach(lang => { + ctx.titles.forEach((title, i) => { + const output = `/${lang}/${slugify(title)}.html` + const post_path = path.join(ctx.public_dir, output) + t.ok(helpers.file.exists(post_path)) + t.true(helpers.file.contains(post_path, title)) + t.true(helpers.file.contains(post_path, ctx.bodies[i])) + t.true(helpers.file.contains(post_path, `

${output}

`)) + }) + }) +}) + +test("sets _urls attribute to all of the entry's compiled files", t => { + t.plan(4) + ctx.langs.forEach(lang => { + ctx.titles.forEach(title => { + t.true(helpers.file.contains(ctx.index_path, `/${lang}/${slugify(title)}.html`)) + }) + }) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/single-entry-views-custom-path-function.js b/test/single-entry-views-custom-path-function.js new file mode 100644 index 0000000..7a4b76d --- /dev/null +++ b/test/single-entry-views-custom-path-function.js @@ -0,0 +1,40 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let title = 'Real Talk' + let body = "I'm not about to sit up here, and argue about who's to blame." + let category = 'greatest_hits' + ctx = { ...ctx, title, body, category } + mock_contentful({ + entries: [{ + fields: { title, body, category } + }], + content_type: { name: 'Blog Post', displayField: 'title' } + }) + await ctx::compile_fixture('single_entry_custom') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.post_path = `${ctx.public_dir}/blogging/${ctx.category}/real-talk.html` +}) + +test('compiles a single entry file using custom path', t => { + t.ok(helpers.file.exists(ctx.post_path)) + t.true(helpers.file.contains(ctx.post_path, ctx.title)) + t.true(helpers.file.contains(ctx.post_path, ctx.body)) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/single-entry-views-default-path-function.js b/test/single-entry-views-default-path-function.js new file mode 100644 index 0000000..df672f8 --- /dev/null +++ b/test/single-entry-views-default-path-function.js @@ -0,0 +1,48 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = {} + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + let title = 'Real Talk' + let body = "I'm not about to sit up here, and argue about who's to blame." + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }], + content_type: { name: 'Blog Post', displayField: 'title' } + }) + await ctx::compile_fixture('single_entry') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.post_path = `${ctx.public_dir}/blog_posts/real-talk.html` +}) + +test('compiles a single entry file based off the slugified display field', t => { + t.ok(helpers.file.exists(ctx.post_path)) + t.true(helpers.file.contains(ctx.post_path, ctx.title)) + t.true(helpers.file.contains(ctx.post_path, ctx.body)) +}) + +test('has access to other roots locals inside the single entry view', t => { + t.true(helpers.file.contains(ctx.post_path, 'such local')) +}) + +test('sets a _url attribute to allow links to each entry', t => { + t.ok(helpers.file.exists(ctx.index_path)) + t.true(helpers.file.contains(ctx.index_path, '/blog_posts/real-talk.html')) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) diff --git a/test/single-entry-views-image-view-helper-function.js b/test/single-entry-views-image-view-helper-function.js new file mode 100644 index 0000000..a44f6a1 --- /dev/null +++ b/test/single-entry-views-image-view-helper-function.js @@ -0,0 +1,36 @@ +import test from 'ava' +import helpers from './_helpers' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from './_setup' + +let ctx = { img_path: 'http://dogesay.com/wow.jpg' } + +test.cb.before(t => { + helpers.project.install_dependencies('*', t.end) +}) + +test.before(async t => { + mock_contentful({ + entries: [{ + fields: { + image: { + fields: { file: { url: ctx.img_path } } + } + } + }] + }) + await ctx::compile_fixture('image_view_helper') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('adds query string params to the image', t => { + t.true(helpers.file.contains(ctx.index_path, `${ctx.img_path}?w=100&h=100`)) +}) + +test.after(async t => { + unmock_contentful() + await helpers.project.remove_folders('**/public') +}) From 71b3268e447217251f083775034e238438767b99 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 6 Jan 2016 18:51:29 +0200 Subject: [PATCH 31/58] lifecycle cleanup --- .travis.yml | 2 +- .../alt-content-type-config/app.coffee | 12 - .../alt-content-type-config/index.jade | 5 - .../alt-content-type-config/package.json | 6 - old_test/fixtures/basic/about.jade | 1 - old_test/fixtures/basic/app.coffee | 18 - old_test/fixtures/basic/index.jade | 5 - old_test/fixtures/basic/package.json | 6 - old_test/fixtures/custom_name/about.jade | 1 - old_test/fixtures/custom_name/app.coffee | 19 - old_test/fixtures/custom_name/index.jade | 5 - old_test/fixtures/custom_name/package.json | 6 - .../fixtures/image_view_helper/app.coffee | 18 - .../fixtures/image_view_helper/index.jade | 4 - .../fixtures/image_view_helper/package.json | 6 - old_test/fixtures/missing_config/app.coffee | 18 - old_test/fixtures/missing_config/index.jade | 5 - old_test/fixtures/missing_config/package.json | 6 - old_test/fixtures/missing_token/app.coffee | 16 - old_test/fixtures/missing_token/index.jade | 1 - old_test/fixtures/missing_token/package.json | 6 - old_test/fixtures/single_entry/app.coffee | 20 - old_test/fixtures/single_entry/index.jade | 4 - old_test/fixtures/single_entry/package.json | 6 - .../single_entry/views/_blog_post.jade | 3 - .../fixtures/single_entry_custom/app.coffee | 22 - .../fixtures/single_entry_custom/index.jade | 1 - .../fixtures/single_entry_custom/package.json | 7 - .../single_entry_custom/views/_blog_post.jade | 3 - .../fixtures/single_entry_multi/app.coffee | 22 - .../fixtures/single_entry_multi/index.jade | 7 - .../fixtures/single_entry_multi/package.json | 7 - .../single_entry_multi/views/_blog_post.jade | 4 - old_test/fixtures/sort/about.jade | 1 - old_test/fixtures/sort/app.coffee | 20 - old_test/fixtures/sort/index.jade | 5 - old_test/fixtures/sort/package.json | 6 - old_test/fixtures/sort/posts_expected.json | 1 - old_test/fixtures/transform/about.jade | 1 - old_test/fixtures/transform/app.coffee | 21 - old_test/fixtures/transform/index.jade | 5 - old_test/fixtures/transform/package.json | 6 - .../fixtures/transform/posts_expected.json | 1 - old_test/fixtures/write/about.jade | 1 - old_test/fixtures/write/app.coffee | 16 - old_test/fixtures/write/index.jade | 5 - old_test/fixtures/write/package.json | 6 - old_test/mocha.opts | 3 - old_test/test.js | 624 ------------------ package.json | 19 +- 50 files changed, 8 insertions(+), 1005 deletions(-) delete mode 100644 old_test/fixtures/alt-content-type-config/app.coffee delete mode 100644 old_test/fixtures/alt-content-type-config/index.jade delete mode 100644 old_test/fixtures/alt-content-type-config/package.json delete mode 100644 old_test/fixtures/basic/about.jade delete mode 100644 old_test/fixtures/basic/app.coffee delete mode 100644 old_test/fixtures/basic/index.jade delete mode 100644 old_test/fixtures/basic/package.json delete mode 100644 old_test/fixtures/custom_name/about.jade delete mode 100644 old_test/fixtures/custom_name/app.coffee delete mode 100644 old_test/fixtures/custom_name/index.jade delete mode 100644 old_test/fixtures/custom_name/package.json delete mode 100644 old_test/fixtures/image_view_helper/app.coffee delete mode 100644 old_test/fixtures/image_view_helper/index.jade delete mode 100644 old_test/fixtures/image_view_helper/package.json delete mode 100644 old_test/fixtures/missing_config/app.coffee delete mode 100644 old_test/fixtures/missing_config/index.jade delete mode 100644 old_test/fixtures/missing_config/package.json delete mode 100644 old_test/fixtures/missing_token/app.coffee delete mode 100644 old_test/fixtures/missing_token/index.jade delete mode 100644 old_test/fixtures/missing_token/package.json delete mode 100644 old_test/fixtures/single_entry/app.coffee delete mode 100644 old_test/fixtures/single_entry/index.jade delete mode 100644 old_test/fixtures/single_entry/package.json delete mode 100644 old_test/fixtures/single_entry/views/_blog_post.jade delete mode 100644 old_test/fixtures/single_entry_custom/app.coffee delete mode 100644 old_test/fixtures/single_entry_custom/index.jade delete mode 100644 old_test/fixtures/single_entry_custom/package.json delete mode 100644 old_test/fixtures/single_entry_custom/views/_blog_post.jade delete mode 100644 old_test/fixtures/single_entry_multi/app.coffee delete mode 100644 old_test/fixtures/single_entry_multi/index.jade delete mode 100644 old_test/fixtures/single_entry_multi/package.json delete mode 100644 old_test/fixtures/single_entry_multi/views/_blog_post.jade delete mode 100644 old_test/fixtures/sort/about.jade delete mode 100644 old_test/fixtures/sort/app.coffee delete mode 100644 old_test/fixtures/sort/index.jade delete mode 100644 old_test/fixtures/sort/package.json delete mode 100644 old_test/fixtures/sort/posts_expected.json delete mode 100644 old_test/fixtures/transform/about.jade delete mode 100644 old_test/fixtures/transform/app.coffee delete mode 100644 old_test/fixtures/transform/index.jade delete mode 100644 old_test/fixtures/transform/package.json delete mode 100644 old_test/fixtures/transform/posts_expected.json delete mode 100644 old_test/fixtures/write/about.jade delete mode 100644 old_test/fixtures/write/app.coffee delete mode 100644 old_test/fixtures/write/index.jade delete mode 100644 old_test/fixtures/write/package.json delete mode 100644 old_test/mocha.opts delete mode 100644 old_test/test.js diff --git a/.travis.yml b/.travis.yml index a2d8312..3a2448c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,5 @@ language: node_js node_js: - "5" after_script: - - npm run coverage + - npm run coveralls sudo: false diff --git a/old_test/fixtures/alt-content-type-config/app.coffee b/old_test/fixtures/alt-content-type-config/app.coffee deleted file mode 100644 index c3d2b72..0000000 --- a/old_test/fixtures/alt-content-type-config/app.coffee +++ /dev/null @@ -1,12 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: - blog_posts: - id: '6BYT1gNiIEyIw8Og8aQAO6' - ] diff --git a/old_test/fixtures/alt-content-type-config/index.jade b/old_test/fixtures/alt-content-type-config/index.jade deleted file mode 100644 index 4769500..0000000 --- a/old_test/fixtures/alt-content-type-config/index.jade +++ /dev/null @@ -1,5 +0,0 @@ -ul - - for p in contentful.blog_posts - li - h1= p.title - p= p.body diff --git a/old_test/fixtures/alt-content-type-config/package.json b/old_test/fixtures/alt-content-type-config/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/alt-content-type-config/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/basic/about.jade b/old_test/fixtures/basic/about.jade deleted file mode 100644 index 2763786..0000000 --- a/old_test/fixtures/basic/about.jade +++ /dev/null @@ -1 +0,0 @@ -h1 wow \ No newline at end of file diff --git a/old_test/fixtures/basic/app.coffee b/old_test/fixtures/basic/app.coffee deleted file mode 100644 index c2c261c..0000000 --- a/old_test/fixtures/basic/app.coffee +++ /dev/null @@ -1,18 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6' - } - { - id: '7CDlVsacqQc88cmIEGYWMa' - } - ] - ) - ] diff --git a/old_test/fixtures/basic/index.jade b/old_test/fixtures/basic/index.jade deleted file mode 100644 index 4769500..0000000 --- a/old_test/fixtures/basic/index.jade +++ /dev/null @@ -1,5 +0,0 @@ -ul - - for p in contentful.blog_posts - li - h1= p.title - p= p.body diff --git a/old_test/fixtures/basic/package.json b/old_test/fixtures/basic/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/basic/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/custom_name/about.jade b/old_test/fixtures/custom_name/about.jade deleted file mode 100644 index 8240f0e..0000000 --- a/old_test/fixtures/custom_name/about.jade +++ /dev/null @@ -1 +0,0 @@ -h1 wow diff --git a/old_test/fixtures/custom_name/app.coffee b/old_test/fixtures/custom_name/app.coffee deleted file mode 100644 index af220ac..0000000 --- a/old_test/fixtures/custom_name/app.coffee +++ /dev/null @@ -1,19 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6' - name: 'press_links' - }, - { - id: '7CDlVsacqQc88cmIEGYWMa' - } - ] - ) - ] diff --git a/old_test/fixtures/custom_name/index.jade b/old_test/fixtures/custom_name/index.jade deleted file mode 100644 index 190e01d..0000000 --- a/old_test/fixtures/custom_name/index.jade +++ /dev/null @@ -1,5 +0,0 @@ -ul - - for p in contentful.press_links - li - h1= p.title - p= p.body diff --git a/old_test/fixtures/custom_name/package.json b/old_test/fixtures/custom_name/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/custom_name/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/image_view_helper/app.coffee b/old_test/fixtures/image_view_helper/app.coffee deleted file mode 100644 index c2c261c..0000000 --- a/old_test/fixtures/image_view_helper/app.coffee +++ /dev/null @@ -1,18 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6' - } - { - id: '7CDlVsacqQc88cmIEGYWMa' - } - ] - ) - ] diff --git a/old_test/fixtures/image_view_helper/index.jade b/old_test/fixtures/image_view_helper/index.jade deleted file mode 100644 index 3eee69d..0000000 --- a/old_test/fixtures/image_view_helper/index.jade +++ /dev/null @@ -1,4 +0,0 @@ -ul - - for p in contentful.blog_posts - li - img(src!= asset(p.image, {w: 100, h: 100})) diff --git a/old_test/fixtures/image_view_helper/package.json b/old_test/fixtures/image_view_helper/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/image_view_helper/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/missing_config/app.coffee b/old_test/fixtures/missing_config/app.coffee deleted file mode 100644 index 2a318b6..0000000 --- a/old_test/fixtures/missing_config/app.coffee +++ /dev/null @@ -1,18 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - name: 'test' - } - { - id: '7CDlVsacqQc88cmIEGYWMa' - } - ] - ) - ] diff --git a/old_test/fixtures/missing_config/index.jade b/old_test/fixtures/missing_config/index.jade deleted file mode 100644 index 4769500..0000000 --- a/old_test/fixtures/missing_config/index.jade +++ /dev/null @@ -1,5 +0,0 @@ -ul - - for p in contentful.blog_posts - li - h1= p.title - p= p.body diff --git a/old_test/fixtures/missing_config/package.json b/old_test/fixtures/missing_config/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/missing_config/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/missing_token/app.coffee b/old_test/fixtures/missing_token/app.coffee deleted file mode 100644 index 6b8b34a..0000000 --- a/old_test/fixtures/missing_token/app.coffee +++ /dev/null @@ -1,16 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - content_types: [ - { - name: 'test' - } - { - id: '7CDlVsacqQc88cmIEGYWMa' - } - ] - ) - ] diff --git a/old_test/fixtures/missing_token/index.jade b/old_test/fixtures/missing_token/index.jade deleted file mode 100644 index 8240f0e..0000000 --- a/old_test/fixtures/missing_token/index.jade +++ /dev/null @@ -1 +0,0 @@ -h1 wow diff --git a/old_test/fixtures/missing_token/package.json b/old_test/fixtures/missing_token/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/missing_token/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/single_entry/app.coffee b/old_test/fixtures/single_entry/app.coffee deleted file mode 100644 index b9d39a8..0000000 --- a/old_test/fixtures/single_entry/app.coffee +++ /dev/null @@ -1,20 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6' - name: 'blog_posts' - template: 'views/_blog_post.jade' - } - ] - ) - ] - - locals: - wow: 'such local' diff --git a/old_test/fixtures/single_entry/index.jade b/old_test/fixtures/single_entry/index.jade deleted file mode 100644 index b881d53..0000000 --- a/old_test/fixtures/single_entry/index.jade +++ /dev/null @@ -1,4 +0,0 @@ -- for p in contentful.blog_posts - h1= p.title - p= p.body - a(href= p._url) Link diff --git a/old_test/fixtures/single_entry/package.json b/old_test/fixtures/single_entry/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/single_entry/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/single_entry/views/_blog_post.jade b/old_test/fixtures/single_entry/views/_blog_post.jade deleted file mode 100644 index 78a5b6c..0000000 --- a/old_test/fixtures/single_entry/views/_blog_post.jade +++ /dev/null @@ -1,3 +0,0 @@ -h1= entry.title -p= entry.body -p= wow diff --git a/old_test/fixtures/single_entry_custom/app.coffee b/old_test/fixtures/single_entry_custom/app.coffee deleted file mode 100644 index a73a4bc..0000000 --- a/old_test/fixtures/single_entry_custom/app.coffee +++ /dev/null @@ -1,22 +0,0 @@ -slugify = require 'underscore.string/slugify' -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6' - name: 'blog_posts' - template: 'views/_blog_post.jade' - path: (e) -> "blogging/#{e.category}/#{slugify(e.title)}" - } - ] - ) - ] - - locals: - wow: 'such local' diff --git a/old_test/fixtures/single_entry_custom/index.jade b/old_test/fixtures/single_entry_custom/index.jade deleted file mode 100644 index 8240f0e..0000000 --- a/old_test/fixtures/single_entry_custom/index.jade +++ /dev/null @@ -1 +0,0 @@ -h1 wow diff --git a/old_test/fixtures/single_entry_custom/package.json b/old_test/fixtures/single_entry_custom/package.json deleted file mode 100644 index 1a248fc..0000000 --- a/old_test/fixtures/single_entry_custom/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*", - "underscore.string": "*" - } -} diff --git a/old_test/fixtures/single_entry_custom/views/_blog_post.jade b/old_test/fixtures/single_entry_custom/views/_blog_post.jade deleted file mode 100644 index 78a5b6c..0000000 --- a/old_test/fixtures/single_entry_custom/views/_blog_post.jade +++ /dev/null @@ -1,3 +0,0 @@ -h1= entry.title -p= entry.body -p= wow diff --git a/old_test/fixtures/single_entry_multi/app.coffee b/old_test/fixtures/single_entry_multi/app.coffee deleted file mode 100644 index 503e364..0000000 --- a/old_test/fixtures/single_entry_multi/app.coffee +++ /dev/null @@ -1,22 +0,0 @@ -slugify = require 'underscore.string/slugify' -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6' - name: 'blog_posts' - template: 'views/_blog_post.jade' - path: (e) -> ("#{lang}/#{slugify(e.title)}" for lang in ['en', 'fr']) - } - ] - ) - ] - - locals: - wow: 'such local' diff --git a/old_test/fixtures/single_entry_multi/index.jade b/old_test/fixtures/single_entry_multi/index.jade deleted file mode 100644 index ee98265..0000000 --- a/old_test/fixtures/single_entry_multi/index.jade +++ /dev/null @@ -1,7 +0,0 @@ -- for p in contentful.blog_posts - h1= p.title - p= p.body - ul - each url in p._urls - li - a(href=url)= url diff --git a/old_test/fixtures/single_entry_multi/package.json b/old_test/fixtures/single_entry_multi/package.json deleted file mode 100644 index 1a248fc..0000000 --- a/old_test/fixtures/single_entry_multi/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*", - "underscore.string": "*" - } -} diff --git a/old_test/fixtures/single_entry_multi/views/_blog_post.jade b/old_test/fixtures/single_entry_multi/views/_blog_post.jade deleted file mode 100644 index 4355d6d..0000000 --- a/old_test/fixtures/single_entry_multi/views/_blog_post.jade +++ /dev/null @@ -1,4 +0,0 @@ -h1= entry.title -p= entry.body -p= entry._url -p= wow diff --git a/old_test/fixtures/sort/about.jade b/old_test/fixtures/sort/about.jade deleted file mode 100644 index 8240f0e..0000000 --- a/old_test/fixtures/sort/about.jade +++ /dev/null @@ -1 +0,0 @@ -h1 wow diff --git a/old_test/fixtures/sort/app.coffee b/old_test/fixtures/sort/app.coffee deleted file mode 100644 index 22f1105..0000000 --- a/old_test/fixtures/sort/app.coffee +++ /dev/null @@ -1,20 +0,0 @@ -contentful = require '../../../src' - -megaSort = (a, b)-> - a.title.localeCompare(b.title) - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6', - write: 'posts.json', - sort: megaSort - } - ] - ) - ] diff --git a/old_test/fixtures/sort/index.jade b/old_test/fixtures/sort/index.jade deleted file mode 100644 index 4769500..0000000 --- a/old_test/fixtures/sort/index.jade +++ /dev/null @@ -1,5 +0,0 @@ -ul - - for p in contentful.blog_posts - li - h1= p.title - p= p.body diff --git a/old_test/fixtures/sort/package.json b/old_test/fixtures/sort/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/sort/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/sort/posts_expected.json b/old_test/fixtures/sort/posts_expected.json deleted file mode 100644 index 2801a26..0000000 --- a/old_test/fixtures/sort/posts_expected.json +++ /dev/null @@ -1 +0,0 @@ -[{"title":"Title A","body":"Nothing interesting"},{"title":"Title B","body":"Something else"},{"title":"Title C","body":"Rich Boy selling crick"}] \ No newline at end of file diff --git a/old_test/fixtures/transform/about.jade b/old_test/fixtures/transform/about.jade deleted file mode 100644 index 8240f0e..0000000 --- a/old_test/fixtures/transform/about.jade +++ /dev/null @@ -1 +0,0 @@ -h1 wow diff --git a/old_test/fixtures/transform/app.coffee b/old_test/fixtures/transform/app.coffee deleted file mode 100644 index 68f04ec..0000000 --- a/old_test/fixtures/transform/app.coffee +++ /dev/null @@ -1,21 +0,0 @@ -contentful = require '../../../src' - -megaTransform = (entry)-> - delete entry.body - entry - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6', - write: 'posts.json', - transform: megaTransform - } - ] - ) - ] diff --git a/old_test/fixtures/transform/index.jade b/old_test/fixtures/transform/index.jade deleted file mode 100644 index 4769500..0000000 --- a/old_test/fixtures/transform/index.jade +++ /dev/null @@ -1,5 +0,0 @@ -ul - - for p in contentful.blog_posts - li - h1= p.title - p= p.body diff --git a/old_test/fixtures/transform/package.json b/old_test/fixtures/transform/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/transform/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/fixtures/transform/posts_expected.json b/old_test/fixtures/transform/posts_expected.json deleted file mode 100644 index 719b928..0000000 --- a/old_test/fixtures/transform/posts_expected.json +++ /dev/null @@ -1 +0,0 @@ -[{"title":"Title C"},{"title":"Title B"},{"title":"Title A"}] \ No newline at end of file diff --git a/old_test/fixtures/write/about.jade b/old_test/fixtures/write/about.jade deleted file mode 100644 index 8240f0e..0000000 --- a/old_test/fixtures/write/about.jade +++ /dev/null @@ -1 +0,0 @@ -h1 wow diff --git a/old_test/fixtures/write/app.coffee b/old_test/fixtures/write/app.coffee deleted file mode 100644 index 4bd0770..0000000 --- a/old_test/fixtures/write/app.coffee +++ /dev/null @@ -1,16 +0,0 @@ -contentful = require '../../../src' - -module.exports = - ignores: ["**/_*", "**/.DS_Store"] - extensions: [ - contentful( - access_token: 'YOUR_ACCESS_TOKEN' - space_id: 'aqzq2qya2jm4' - content_types: [ - { - id: '6BYT1gNiIEyIw8Og8aQAO6', - write: 'posts.json' - } - ] - ) - ] diff --git a/old_test/fixtures/write/index.jade b/old_test/fixtures/write/index.jade deleted file mode 100644 index 4769500..0000000 --- a/old_test/fixtures/write/index.jade +++ /dev/null @@ -1,5 +0,0 @@ -ul - - for p in contentful.blog_posts - li - h1= p.title - p= p.body diff --git a/old_test/fixtures/write/package.json b/old_test/fixtures/write/package.json deleted file mode 100644 index 2d0ae2c..0000000 --- a/old_test/fixtures/write/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test", - "dependencies": { - "jade": "*" - } -} diff --git a/old_test/mocha.opts b/old_test/mocha.opts deleted file mode 100644 index 9ee4f1d..0000000 --- a/old_test/mocha.opts +++ /dev/null @@ -1,3 +0,0 @@ ---reporter spec ---compilers js:babel-register,coffee:coffee-script/register ---timeout 90000 diff --git a/old_test/test.js b/old_test/test.js deleted file mode 100644 index 66ebbf9..0000000 --- a/old_test/test.js +++ /dev/null @@ -1,624 +0,0 @@ -/* - NOTE: these tests have been (mostly) compiled by coffee-script. - We require minimal ES6 syntax so we can take advantage - of the way modules are structured in `lib/`. - These tests are temporary and will eventually be translated - to the AVA test runner pending an update to support Babel 6.0. - */ - -import 'babel-polyfill' -import chai from 'chai' -import chai_promise from 'chai-as-promised' -import mockery from 'mockery' -import path from 'path' -import RootsUtil from 'roots-util' -import Roots from 'roots' -import slugify from 'underscore.string/slugify' -import W from 'when' -import _ from 'lodash' - -let _path = path.join(__dirname, './fixtures') -let h = new RootsUtil.Helpers({ base: _path }) - -chai.should() -chai.use(chai_promise) - -function compile_fixture (fixture_name, done) { - this['public'] = path.join(fixture_name, 'public') - return h.project.compile(Roots, fixture_name) -} - -function mock_contentful (opts) { - if (opts == null) { - opts = {} - } - mockery.enable({ - warnOnUnregistered: false, - useCleanCache: true - }) - opts = _.defaults(opts, { - entries: [ - { - sys: { - sys: 'data' - }, - fields: { - title: 'Default Title', - body: 'Default Body' - } - } - ], - content_type: { - name: 'Blog Post', - displayField: 'title' - } - }) - return mockery.registerMock('contentful', { - createClient: function () { - return { - contentType: function () { - return W.resolve(opts.content_type) - }, - entries: function () { - return W.resolve(opts.entries) - } - } - } - }) -} - -function unmock_contentful () { - mockery.deregisterAll() - return mockery.disable() -} - -before(function (done) { - return h.project.install_dependencies('*', done) -}) - -after(function () { - return h.project.remove_folders('**/public') -}) - -describe('config', function () { - before(function () { - this.title = 'Gatorade' - this.body = 'Yung Lean' - return mock_contentful({ - entries: [ - { - fields: { - title: this.title, - body: this.body - } - } - ] - }) - }) - it('should throw an error when missing an access token', function () { - return function () { - return compile_fixture.call(this, 'missing_token') - }.should['throw']() - }) - it('should throw an error without content type id', function () { - return compile_fixture.call(this, 'missing_config').should.be.rejected - }) - it('allows the content type name to be set through a k/v object config', function (done) { - return compile_fixture.call(this, 'alt-content-type-config')['with'](this).then(function () { - var p - p = path.join(this['public'], 'index.html') - h.file.contains(p, this.title).should.be['true'] - return h.file.contains(p, this.body).should.be['true'] - }).then(function () { - return done() - })['catch'](done) - }) - return after(function () { - return unmock_contentful() - }) -}) - -describe('contentful content type fields', function () { - before(function () { - return mock_contentful({ - entries: [ - { - fields: { - sys: 'test' - } - } - ] - }) - }) - it('should throw an error if `sys` is a field name', function () { - return compile_fixture.call(this, 'basic').should.be.rejected - }) - return after(function () { - return unmock_contentful() - }) -}) - -describe('basic compile', function () { - var project = new Roots(path.join(_path, 'basic')) - var util = new RootsUtil(project) - before(function (done) { - this.title = 'Throw Some Ds' - this.body = 'Rich Boy selling crick' - mock_contentful({ - entries: [ - { - fields: { - title: this.title, - body: this.body - } - } - ] - }) - util.write('../about.jade', 'h1 wow') - return compile_fixture.call(this, 'basic').then(function () { - return done() - })['catch'](done) - }) - it('compiles basic project', function () { - var p - p = path.join(this['public'], 'index.html') - return h.file.exists(p).should.be.ok - }) - it('has contentful data available in views', function () { - var p - p = path.join(this['public'], 'index.html') - h.file.contains(p, this.title).should.be['true'] - return h.file.contains(p, this.body).should.be['true'] - }) - it('compiles multiple times including watch', async function () { - var index = path.join(this['public'], 'index.html') - var about = path.join(this['public'], 'about.html') - - console.log('starting watcher') - var watcher = await project.watch() - - console.log('compile 1') - h.file.contains(index, this.title).should.be['true'] - h.file.contains(index, this.body).should.be['true'] - - console.log('modifying fixture') - await util.write('../about.jade', 'h1 Chuck some Ds\nh2 Wealthy lad peddling crick') - - console.log('verifying changes') - h.file.contains(about, 'Chuck some Ds').should.be['true'] - h.file.contains(about, 'Wealthy lad peddling crick').should.be['true'] - - console.log('closing watcher') - watcher.close() - console.log('watcher closed') - - // var watcher = null - // var second_compile = false - // project.on('done', async function () { - // var p = path.join(this['public'], 'index.html') - // if (!second_compile) { - // console.log('compile 1') - // h.file.contains(p, this.title).should.be['true'] - // h.file.contains(p, this.body).should.be['true'] - // second_compile = true - // } else { - // console.log('compile 2') - // h.file.contains(p, 'Chuck some Ds').should.be['true'] - // h.file.contains(p, 'Wealthy lad peddling crick').should.be['true'] - // console.log(watcher) - // if (watcher != null) { - // await watcher.close() - // console.log('watcher closed') - // } - // } - // }.bind(this)) - // watcher = await project.watch() - // console.log('watcher opened') - // console.log(watcher) - // await util.write('../about.jade', 'h1 Chuck some Ds\nh2 Wealthy lad peddling crick') - }) - return after(function () { - util.write('../about.jade', 'h1 wow') - return unmock_contentful() - }) -}) - -describe('write as json', function () { - before(function (done) { - this.title = 'Throw Some Ds' - this.body = 'Rich Boy selling crick' - mock_contentful({ - entries: [ - { - fields: { - title: this.title, - body: this.body - } - } - ] - }) - return compile_fixture.call(this, 'write').then(function () { - return done() - })['catch'](done) - }) - it('compiles project', function () { - var p - p = path.join(this['public'], 'index.html') - return h.file.exists(p).should.be.ok - }) - it('has written data as json', function () { - var p - p = path.join(this['public'], 'posts.json') - h.file.exists(p).should.be.ok - h.file.contains(p, this.title).should.be['true'] - return h.file.contains(p, this.body).should.be['true'] - }) - return after(function () { - return unmock_contentful() - }) -}) - -describe('data manipulation', function () { - describe('sort', function () { - before(function (done) { - var index - this.titles = ['Title C', 'Title B', 'Title A'] - this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting'] - this.entries = function () { - var j, results - results = [] - for (index = j = 0; j <= 2; index = ++j) { - results.push({ - fields: { - title: this.titles[index], - body: this.bodies[index] - } - }) - } - return results - }.call(this) - mock_contentful({ - entries: this.entries - }) - return compile_fixture.call(this, 'sort').then(function () { - return done() - })['catch'](done) - }) - it('compiles project', function () { - var p - p = path.join(this['public'], 'index.html') - return h.file.exists(p).should.be.ok - }) - it('orders data correctly for the project', function () { - var body, j, len, p, ref, results - p = path.join(this['public'], 'index.html') - h.file.contains_match(p, '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$').should.be['true'] - ref = this.bodies - results = [] - for (j = 0, len = ref.length; j < len; j++) { - body = ref[j] - results.push(h.file.contains(p, body).should.be['true']) - } - return results - }) - it('has written data as json', function () { - var p - p = path.join(this['public'], 'posts.json') - h.file.exists(p).should.be.ok - return h.file.matches_file(p, 'sort/posts_expected.json').should.be['true'] - }) - return after(function () { - return unmock_contentful() - }) - }) - return describe('transform', function () { - before(function (done) { - var index - this.titles = ['Title C', 'Title B', 'Title A'] - this.bodies = ['Rich Boy selling crick', 'Something else', 'Nothing interesting'] - this.entries = function () { - var j, results - results = [] - for (index = j = 0; j <= 2; index = ++j) { - results.push({ - fields: { - title: this.titles[index], - body: this.bodies[index] - } - }) - } - return results - }.call(this) - mock_contentful({ - entries: this.entries - }) - return compile_fixture.call(this, 'transform').then(function () { - return done() - })['catch'](done) - }) - it('compiles project', function () { - var p - p = path.join(this['public'], 'index.html') - return h.file.exists(p).should.be.ok - }) - it('does not reorder data', function () { - var p - p = path.join(this['public'], 'index.html') - return h.file.contains_match(p, '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$').should.be['true'] - }) - it('has manipulated data correctly for the project', function () { - var body, j, len, p, ref, results - p = path.join(this['public'], 'index.html') - ref = this.bodies - results = [] - for (j = 0, len = ref.length; j < len; j++) { - body = ref[j] - results.push(h.file.contains(p, body).should.be['false']) - } - return results - }) - it('has written data as json', function () { - var p - p = path.join(this['public'], 'posts.json') - h.file.exists(p).should.be.ok - return h.file.matches_file(p, 'transform/posts_expected.json').should.be['true'] - }) - return after(function () { - return unmock_contentful() - }) - }) -}) - -describe('custom name for view helper local', function () { - before(function (done) { - this.title = 'Throw Some Ds' - this.body = 'Rich Boy selling crack' - mock_contentful({ - entries: [ - { - fields: { - title: this.title, - body: this.body - } - } - ] - }) - return compile_fixture.call(this, 'custom_name').then(function () { - return done() - })['catch'](done) - }) - it('has contentful data available in views under a custom name', function () { - var p - p = path.join(this['public'], 'index.html') - h.file.contains(p, this.title).should.be['true'] - return h.file.contains(p, this.body).should.be['true'] - }) - return after(function () { - return unmock_contentful() - }) -}) - -describe('single entry views', function () { - describe('default path function', function () { - before(function (done) { - this.title = 'Real Talk' - this.body = "I'm not about to sit up here, and argue about who's to blame." - mock_contentful({ - entries: [ - { - fields: { - title: this.title, - body: this.body - } - } - ], - content_type: { - name: 'Blog Post', - displayField: 'title' - } - }) - return compile_fixture.call(this, 'single_entry').then(function () { - return done() - })['catch'](done) - }) - it('compiles a single entry file based off the slugified display field', function () { - var p - p = path.join(this['public'], 'blog_posts/' + slugify(this.title) + '.html') - h.file.exists(p).should.be.ok - h.file.contains(p, this.title).should.be['true'] - return h.file.contains(p, this.body).should.be['true'] - }) - it('has access to other roots locals inside the single entry view', function () { - var p - p = path.join(this['public'], 'blog_posts/' + slugify(this.title) + '.html') - return h.file.contains(p, 'such local').should.be['true'] - }) - it('sets a _url attribute to allow links to each entry', function () { - var p - p = path.join(this['public'], 'index.html') - return h.file.contains(p, '/blog_posts/real-talk.html').should.be['true'] - }) - return after(function () { - return unmock_contentful() - }) - }) - describe('should clear entry locals between each single view compile', function () { - before(function (done) { - this.title = 'Wow such doge' - this.body = 'such amaze' - this.title_2 = 'Totes McGotes' - this.body_2 = null - mock_contentful({ - entries: [ - { - fields: { - title: this.title, - body: this.body - } - }, { - fields: { - title: this.title_2 - } - } - ], - content_type: { - name: 'Blog Post', - displayField: 'title' - } - }) - return compile_fixture.call(this, 'single_entry').then(function () { - return done() - })['catch'](done) - }) - after(function () { - return unmock_contentful() - }) - return it("should not have first entry's content in second entries single view", function () { - var p - p = path.join(this['public'], 'blog_posts/' + slugify(this.title_2) + '.html') - return h.file.contains(p, this.body).should.not.be['true'] - }) - }) - describe('custom path function', function () { - before(function (done) { - this.title = 'Real Talk' - this.body = "I'm not about to sit up here, and argue about who's to blame." - this.category = 'greatest_hits' - mock_contentful({ - entries: [ - { - fields: { - title: this.title, - body: this.body, - category: this.category - } - } - ], - content_type: { - name: 'Blog Post', - displayField: 'title' - } - }) - return compile_fixture.call(this, 'single_entry_custom').then(function () { - return done() - })['catch'](done) - }) - it('compiles a single entry file using custom path', function () { - var output, p - output = 'blogging/' + this.category + '/' + slugify(this.title) + '.html' - p = path.join(this['public'], output) - h.file.exists(p).should.be.ok - h.file.contains(p, this.title).should.be['true'] - return h.file.contains(p, this.body).should.be['true'] - }) - return after(function () { - return unmock_contentful() - }) - }) - describe('custom multi-path function', function () { - before(function (done) { - this.title = ['Real Talk', 'Fake Talk'] - this.body = ["I'm not about to sit up here, and argue about who's to blame.", "I'm about to sit up here, and not argue about who's not to blame."] - mock_contentful({ - entries: [ - { - fields: { - title: this.title[0], - body: this.body[0] - } - }, { - fields: { - title: this.title[1], - body: this.body[1] - } - } - ], - content_type: { - name: 'Blog Post', - displayField: 'title' - } - }) - return compile_fixture.call(this, 'single_entry_multi').then(function () { - return done() - })['catch'](done) - }) - it('compiles a single entry to multiple files', function () { - var i, j, lang, len, output, p, ref, results - ref = ['en', 'fr'] - results = [] - for (j = 0, len = ref.length; j < len; j++) { - lang = ref[j] - results.push(function () { - var k, len1, ref1, results1 - ref1 = [0, 1] - results1 = [] - for (k = 0, len1 = ref1.length; k < len1; k++) { - i = ref1[k] - output = '/' + lang + '/' + slugify(this.title[i]) + '.html' - p = path.join(this['public'], output) - h.file.exists(p).should.be.ok - h.file.contains(p, this.title[i]).should.be['true'] - h.file.contains(p, this.body[i]).should.be['true'] - results1.push(h.file.contains(p, '

' + output + '

').should.be['true']) - } - return results1 - }.call(this)) - } - return results - }) - it("sets _urls attribute to all of the entry's compiled files", function () { - var i, j, lang, len, p, ref, results - p = path.join(this['public'], 'index.html') - ref = ['en', 'fr'] - results = [] - for (j = 0, len = ref.length; j < len; j++) { - lang = ref[j] - results.push(function () { - var k, len1, ref1, results1 - ref1 = [0, 1] - results1 = [] - for (k = 0, len1 = ref1.length; k < len1; k++) { - i = ref1[k] - results1.push(h.file.contains(p, '/' + lang + '/' + slugify(this.title[i]) + '.html').should.be['true']) - } - return results1 - }.call(this)) - } - return results - }) - return after(function () { - return unmock_contentful() - }) - }) - return describe('image view helper function', function () { - before(function (done) { - this.img_path = 'http://dogesay.com/wow.jpg' - mock_contentful({ - entries: [ - { - fields: { - image: { - fields: { - file: { - url: this.img_path - } - } - } - } - } - ] - }) - return compile_fixture.call(this, 'image_view_helper').then(function () { - return done() - })['catch'](done) - }) - it('adds query string params to the image', function () { - var p - p = path.join(this['public'], 'index.html') - return h.file.contains(p, this.img_path + '?w=100&h=100').should.be['true'] - }) - return after(function () { - return unmock_contentful() - }) - }) -}) diff --git a/package.json b/package.json index ad350f7..5fe76ac 100644 --- a/package.json +++ b/package.json @@ -61,27 +61,22 @@ }, "scripts": { "build": "babel src -d lib", - "precoverage": "nyc npm test", - "coverage": "nyc report --reporter=text-lcov | coveralls", + "coverage": "nyc report --reporter=lcov", + "coveralls": "coveralls < coverage/lcov.info", + "debug-test": "npm run test -- --serial --verbose --fail-fast", "lint": "standard --verbose | snazzy", "postpublish": "git push --follow-tags", + "posttest": "babel-node ./test/_teardown.js", "prebuild": "npm test", "precommit": "npm run lint -s", + "precoverage": "nyc npm test", "precoveralls": "npm run coverage", "prerelease": "npm run build", - "pretest": "npm run lint -s", + "pretest": "npm run lint -s && babel-node ./test/_setup.js", "release": "npm publish", "test": "nyc ava --require babel-core/register --require coffee-script/register" }, "standard": { - "parser": "babel-eslint", - "globals": [ - "describe", - "it", - "before", - "after", - "beforeAll", - "afterAll" - ] + "parser": "babel-eslint" } } From cf06e220a7ef351016f08e638f02cc025131e688 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 6 Jan 2016 18:58:58 +0200 Subject: [PATCH 32/58] update make tasks --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index a6586d7..844d2c5 100644 --- a/Makefile +++ b/Makefile @@ -12,3 +12,9 @@ release: lint: npm run lint + +debug-test: + npm run debug-test + +test:: + npm test From 79b5fdd62633692df5ef0ff7a483e34cce569794 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 6 Jan 2016 19:39:24 +0200 Subject: [PATCH 33/58] document lifecycle changes, add request debugging to debug-test task --- contributing.md | 36 +++++++++++++++++++++--------------- package.json | 2 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/contributing.md b/contributing.md index 0486c33..faa686f 100644 --- a/contributing.md +++ b/contributing.md @@ -1,5 +1,5 @@ -# Contributing to Roots-contentful +# Contributing to `roots-contentful` Hello there! First of all, thanks for being interested in this project and helping out. We all think you are awesome, and by contributing to open source projects, you are making the world a better place. That being said, there are a few ways to make the process of contributing code to this project smoother, detailed below: @@ -14,9 +14,9 @@ If you are opening an issue about a bug, make sure that you include clear steps - Run `npm install` - Put in work -### `pre-push`, `pre-commit` Linting +### `pre-commit` Linting -Provided dependencies are installed, `git push` and `git commit` will +Provided dependencies are installed, `git commit` will not work unless this project passes a linting check. ### Build Commands @@ -26,32 +26,35 @@ not work unless this project passes a linting check. #### Testing -This project is constantly evolving, and to ensure that things are secure and working for everyone, we need to have tests. If you are adding a new feature, please make sure to add a test for it. The test suite for this project uses [mocha](http://visionmedia.github.io/mocha/) and [chai](http://chaijs.com/) +This project is constantly evolving, and to ensure that things are secure and working for everyone, we need to have tests. If you are adding a new feature, please make sure to add a test for it. The test suite for this project uses [AVA](https://github.com/sindresorhus/ava). To lint the source: -``` +```shell $ make lint ``` To lint the source and run the tests: -```bash -$ npm test -# or, for optional request logging: -$ NODE_DEBUG=request npm test +```shell +$ make test ``` +By default, tests will run concurrently/in parallel. When debugging, this can sometimes lead to unwanted behavior. For this reason, there is a `debug-test` command that will fail as soon as the first test fails, run tests serially, enable more verbose output and also log any HTTP requests: + +```shell +$ make debug-test +``` To create a coverage report: -``` +```shell $ make coverage ``` To feed a coverage report to coveralls: -``` +```shell $ make coveralls ``` @@ -62,7 +65,7 @@ $ make coveralls Building involves compiling the ES2016 syntax down to regular ES5 using [Babel](http://babeljs.io). This command will run the tests - on success it will then compile the contents of `src/` into `lib/`. -``` +```shell $ make build ``` @@ -70,16 +73,19 @@ $ make build This command will lint the project files, run the tests, build the project, publish the build to NPM and then perform a `git push --follow-tags`. -``` +```shell $ make release ``` A typical publish workflow might look something like this: -``` -$ git checkout master +```shell +$ git checkout Fix/bug-fix +# add some code... $ git add . $ git commit -m "fixed a bug" +$ git checkout master +$ git merge Fix/bug-fix $ npm version patch $ make release ``` diff --git a/package.json b/package.json index 5fe76ac..a67f178 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "build": "babel src -d lib", "coverage": "nyc report --reporter=lcov", "coveralls": "coveralls < coverage/lcov.info", - "debug-test": "npm run test -- --serial --verbose --fail-fast", + "debug-test": "NODE_DEBUG=request npm run test -- --serial --verbose --fail-fast", "lint": "standard --verbose | snazzy", "postpublish": "git push --follow-tags", "posttest": "babel-node ./test/_teardown.js", From 70ec7a527108cdee46de7e3d12010f02d8a4d6e5 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 6 Jan 2016 20:39:46 +0200 Subject: [PATCH 34/58] begin test performance improvements --- .gitignore | 1 + package.json | 7 ++- test/_helpers.js | 62 ++++++++++++++++++- test/_setup.js | 62 +++---------------- test/_teardown.js | 14 +++++ test/basic-compile.js | 11 +--- test/config.js | 11 +--- test/contentful-content-type-fields.js | 10 +-- test/custom-name-for-view-helper-local.js | 11 +--- test/data-manipulation-sort.js | 11 +--- test/data-manipulation-transform.js | 11 +--- ...clear-entry-locals-between-each-compile.js | 11 +--- ...-entry-views-custom-multi-path-function.js | 11 +--- ...single-entry-views-custom-path-function.js | 11 +--- ...ingle-entry-views-default-path-function.js | 11 +--- ...-entry-views-image-view-helper-function.js | 11 +--- test/write-as-json.js | 11 +--- 17 files changed, 121 insertions(+), 156 deletions(-) create mode 100644 test/_teardown.js diff --git a/.gitignore b/.gitignore index d1412c4..885f017 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ lib-cov .lock-wscript build/Release .nyc_output +test/fixtures/*/public diff --git a/package.json b/package.json index a67f178..80aba09 100644 --- a/package.json +++ b/package.json @@ -66,17 +66,18 @@ "debug-test": "NODE_DEBUG=request npm run test -- --serial --verbose --fail-fast", "lint": "standard --verbose | snazzy", "postpublish": "git push --follow-tags", - "posttest": "babel-node ./test/_teardown.js", + "posttest": "node test/_teardown.js", "prebuild": "npm test", "precommit": "npm run lint -s", "precoverage": "nyc npm test", "precoveralls": "npm run coverage", "prerelease": "npm run build", - "pretest": "npm run lint -s && babel-node ./test/_setup.js", + "pretest": "npm run lint -s && node test/_setup.js", "release": "npm publish", "test": "nyc ava --require babel-core/register --require coffee-script/register" }, "standard": { - "parser": "babel-eslint" + "parser": "babel-eslint", + "ignore": ["test/_setup.js", "test/_teardown.js"] } } diff --git a/test/_helpers.js b/test/_helpers.js index f68328f..be28901 100644 --- a/test/_helpers.js +++ b/test/_helpers.js @@ -1,8 +1,66 @@ import path from 'path' +import mockery from 'mockery' +import Roots from 'roots' import RootsUtil from 'roots-util' +import ava from 'ava' -let helpers = new RootsUtil.Helpers({ +// polyfill array includes because of +// https://github.com/sindresorhus/ava/issues/263 +/* eslint-disable */ +Array.prototype.includes = do { + typeof Array.prototype.includes === 'function' + ? Array.prototype.includes + : function includes (needle) { + return this.indexOf(needle) > -1 + } +} +/* eslint-enable */ + +export const test = ava + +export const helpers = new RootsUtil.Helpers({ base: path.join(__dirname, './fixtures') }) -export default helpers +export async function compile_fixture (name) { + this.public_dir = `${name}/public` + return await helpers.project.compile(Roots, name) +} + +export function unmock_contentful () { + mockery.deregisterAll() + return mockery.disable() +} + +export function mock_contentful (opts = {}) { + mockery.enable({ + warnOnUnregistered: false, + useCleanCache: true + }) + opts = { + entries: [{ + sys: { sys: 'data' }, + fields: { + title: 'Default Title', + body: 'Default Body' + } + }], + content_type: { + name: 'Blog Post', + displayField: 'title' + }, + ...opts + } + return mockery.registerMock('contentful', { + createClient () { + return { + contentType () { + return Promise.resolve(opts.content_type) + }, + entries () { + return Promise.resolve(opts.entries) + } + } + } + }) +} diff --git a/test/_setup.js b/test/_setup.js index b823774..0fc4989 100644 --- a/test/_setup.js +++ b/test/_setup.js @@ -1,58 +1,10 @@ -import mockery from 'mockery' -import Roots from 'roots' -import helpers from './_helpers' +require('babel-core/register') -// polyfill array includes because of -// https://github.com/sindresorhus/ava/issues/263 -/* eslint-disable */ -Array.prototype.includes = do { - typeof Array.prototype.includes === 'function' - ? Array.prototype.includes - : function includes (needle) { - return this.indexOf(needle) > -1 - } -} -/* eslint-enable */ +var helpers = require('./_helpers').helpers -export async function compile_fixture (name) { - this.public_dir = `${name}/public` - return await helpers.project.compile(Roots, name) -} +console.log('setting up...') -export function unmock_contentful () { - mockery.deregisterAll() - return mockery.disable() -} - -export function mock_contentful (opts = {}) { - mockery.enable({ - warnOnUnregistered: false, - useCleanCache: true - }) - opts = { - entries: [{ - sys: { sys: 'data' }, - fields: { - title: 'Default Title', - body: 'Default Body' - } - }], - content_type: { - name: 'Blog Post', - displayField: 'title' - }, - ...opts - } - return mockery.registerMock('contentful', { - createClient () { - return { - contentType () { - return Promise.resolve(opts.content_type) - }, - entries () { - return Promise.resolve(opts.entries) - } - } - } - }) -} +helpers.project.install_dependencies('*', function () { + console.log('done with setup') + process.exit(0) +}) diff --git a/test/_teardown.js b/test/_teardown.js new file mode 100644 index 0000000..1f5d39c --- /dev/null +++ b/test/_teardown.js @@ -0,0 +1,14 @@ +require('babel-core/register') + +var helpers = require('./_helpers').helpers + +console.log('performing cleanup...') + +helpers.project.remove_folders('**/public') + .then(function () { + console.log('done with cleanup') + process.exit(0) + }) + .catch(function (e) { + throw new Error(e.message) + }) diff --git a/test/basic-compile.js b/test/basic-compile.js index 8363a54..df50f87 100644 --- a/test/basic-compile.js +++ b/test/basic-compile.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let title = 'Throw Some Ds' let body = 'Rich Boy selling crick' @@ -36,5 +32,4 @@ test('has contentful data available in views', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/config.js b/test/config.js index 23aea03..d7f404f 100644 --- a/test/config.js +++ b/test/config.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let title = 'Gatorade' let body = 'Yung Lean' @@ -40,5 +36,4 @@ test('allows the content type name to be set through a k/v object config', async test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/contentful-content-type-fields.js b/test/contentful-content-type-fields.js index cdd9507..d92225c 100644 --- a/test/contentful-content-type-fields.js +++ b/test/contentful-content-type-fields.js @@ -1,17 +1,12 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { mock_contentful({ entries: [{ @@ -26,5 +21,4 @@ test('should throw an error if `sys` is a field name', async t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/custom-name-for-view-helper-local.js b/test/custom-name-for-view-helper-local.js index 07a5894..559b5c3 100644 --- a/test/custom-name-for-view-helper-local.js +++ b/test/custom-name-for-view-helper-local.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let title = 'Throw Some Ds' let body = 'Rich Boy selling crack' @@ -32,5 +28,4 @@ test('has contentful data available in views under a custom name', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/data-manipulation-sort.js b/test/data-manipulation-sort.js index 39c035f..2be0084 100644 --- a/test/data-manipulation-sort.js +++ b/test/data-manipulation-sort.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { ctx.titles = ['Title C', 'Title B', 'Title A'] ctx.bodies = [ @@ -51,5 +47,4 @@ test('has written data as json', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/data-manipulation-transform.js b/test/data-manipulation-transform.js index 4c50f00..ca85026 100644 --- a/test/data-manipulation-transform.js +++ b/test/data-manipulation-transform.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { ctx.titles = ['Title C', 'Title B', 'Title A'] ctx.bodies = [ @@ -54,5 +50,4 @@ test('has written data as json', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/single-entry-views-clear-entry-locals-between-each-compile.js b/test/single-entry-views-clear-entry-locals-between-each-compile.js index fa49cba..41b8b35 100644 --- a/test/single-entry-views-clear-entry-locals-between-each-compile.js +++ b/test/single-entry-views-clear-entry-locals-between-each-compile.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let title = 'Wow such doge' let body = 'such amaze' @@ -35,5 +31,4 @@ test("should not have first entry's content in second entry's single view", t => test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/single-entry-views-custom-multi-path-function.js b/test/single-entry-views-custom-multi-path-function.js index 111ea3b..65da26b 100644 --- a/test/single-entry-views-custom-multi-path-function.js +++ b/test/single-entry-views-custom-multi-path-function.js @@ -1,19 +1,15 @@ import path from 'path' -import test from 'ava' import slugify from 'underscore.string/slugify' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let titles = ['Real Talk', 'Fake Talk'] let bodies = [ @@ -56,5 +52,4 @@ test("sets _urls attribute to all of the entry's compiled files", t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/single-entry-views-custom-path-function.js b/test/single-entry-views-custom-path-function.js index 7a4b76d..fd08dff 100644 --- a/test/single-entry-views-custom-path-function.js +++ b/test/single-entry-views-custom-path-function.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let title = 'Real Talk' let body = "I'm not about to sit up here, and argue about who's to blame." @@ -36,5 +32,4 @@ test('compiles a single entry file using custom path', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/single-entry-views-default-path-function.js b/test/single-entry-views-default-path-function.js index df672f8..db485d0 100644 --- a/test/single-entry-views-default-path-function.js +++ b/test/single-entry-views-default-path-function.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let title = 'Real Talk' let body = "I'm not about to sit up here, and argue about who's to blame." @@ -44,5 +40,4 @@ test('sets a _url attribute to allow links to each entry', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/single-entry-views-image-view-helper-function.js b/test/single-entry-views-image-view-helper-function.js index a44f6a1..800803b 100644 --- a/test/single-entry-views-image-view-helper-function.js +++ b/test/single-entry-views-image-view-helper-function.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = { img_path: 'http://dogesay.com/wow.jpg' } -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { mock_contentful({ entries: [{ @@ -32,5 +28,4 @@ test('adds query string params to the image', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) diff --git a/test/write-as-json.js b/test/write-as-json.js index be44093..ec175ae 100644 --- a/test/write-as-json.js +++ b/test/write-as-json.js @@ -1,17 +1,13 @@ -import test from 'ava' -import helpers from './_helpers' import { + test, + helpers, mock_contentful, unmock_contentful, compile_fixture -} from './_setup' +} from './_helpers' let ctx = {} -test.cb.before(t => { - helpers.project.install_dependencies('*', t.end) -}) - test.before(async t => { let title = 'Throw Some Ds' let body = 'Rich Boy selling crick' @@ -38,5 +34,4 @@ test('has written data as json', t => { test.after(async t => { unmock_contentful() - await helpers.project.remove_folders('**/public') }) From 343a6f62531635cf8cfa059d7835586a7a4ffbd4 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 6 Jan 2016 21:22:59 +0200 Subject: [PATCH 35/58] improve test performance --- test/_helpers.js | 3 --- test/_setup.js | 3 --- test/_teardown.js | 12 ++---------- test/basic-compile.js | 2 +- test/config.js | 2 +- test/contentful-content-type-fields.js | 2 +- test/custom-name-for-view-helper-local.js | 2 +- test/data-manipulation-sort.js | 2 +- test/data-manipulation-transform.js | 2 +- ...-views-clear-entry-locals-between-each-compile.js | 2 +- .../single-entry-views-custom-multi-path-function.js | 2 +- test/single-entry-views-custom-path-function.js | 2 +- test/single-entry-views-default-path-function.js | 2 +- .../single-entry-views-image-view-helper-function.js | 2 +- test/write-as-json.js | 2 +- 15 files changed, 14 insertions(+), 28 deletions(-) diff --git a/test/_helpers.js b/test/_helpers.js index be28901..c60f972 100644 --- a/test/_helpers.js +++ b/test/_helpers.js @@ -2,7 +2,6 @@ import path from 'path' import mockery from 'mockery' import Roots from 'roots' import RootsUtil from 'roots-util' -import ava from 'ava' // polyfill array includes because of // https://github.com/sindresorhus/ava/issues/263 @@ -16,8 +15,6 @@ Array.prototype.includes = do { } /* eslint-enable */ -export const test = ava - export const helpers = new RootsUtil.Helpers({ base: path.join(__dirname, './fixtures') }) diff --git a/test/_setup.js b/test/_setup.js index 0fc4989..ad06ce6 100644 --- a/test/_setup.js +++ b/test/_setup.js @@ -1,9 +1,6 @@ require('babel-core/register') - var helpers = require('./_helpers').helpers - console.log('setting up...') - helpers.project.install_dependencies('*', function () { console.log('done with setup') process.exit(0) diff --git a/test/_teardown.js b/test/_teardown.js index 1f5d39c..ce5bc7e 100644 --- a/test/_teardown.js +++ b/test/_teardown.js @@ -1,14 +1,6 @@ require('babel-core/register') - var helpers = require('./_helpers').helpers - console.log('performing cleanup...') - helpers.project.remove_folders('**/public') - .then(function () { - console.log('done with cleanup') - process.exit(0) - }) - .catch(function (e) { - throw new Error(e.message) - }) +console.log('done with cleanup') +process.exit(0) diff --git a/test/basic-compile.js b/test/basic-compile.js index df50f87..3311955 100644 --- a/test/basic-compile.js +++ b/test/basic-compile.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/config.js b/test/config.js index d7f404f..9a96309 100644 --- a/test/config.js +++ b/test/config.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/contentful-content-type-fields.js b/test/contentful-content-type-fields.js index d92225c..dcfbbcc 100644 --- a/test/contentful-content-type-fields.js +++ b/test/contentful-content-type-fields.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, mock_contentful, unmock_contentful, compile_fixture diff --git a/test/custom-name-for-view-helper-local.js b/test/custom-name-for-view-helper-local.js index 559b5c3..43ba1b0 100644 --- a/test/custom-name-for-view-helper-local.js +++ b/test/custom-name-for-view-helper-local.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/data-manipulation-sort.js b/test/data-manipulation-sort.js index 2be0084..db5af12 100644 --- a/test/data-manipulation-sort.js +++ b/test/data-manipulation-sort.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/data-manipulation-transform.js b/test/data-manipulation-transform.js index ca85026..72bfaef 100644 --- a/test/data-manipulation-transform.js +++ b/test/data-manipulation-transform.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/single-entry-views-clear-entry-locals-between-each-compile.js b/test/single-entry-views-clear-entry-locals-between-each-compile.js index 41b8b35..5630de9 100644 --- a/test/single-entry-views-clear-entry-locals-between-each-compile.js +++ b/test/single-entry-views-clear-entry-locals-between-each-compile.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/single-entry-views-custom-multi-path-function.js b/test/single-entry-views-custom-multi-path-function.js index 65da26b..78bcb83 100644 --- a/test/single-entry-views-custom-multi-path-function.js +++ b/test/single-entry-views-custom-multi-path-function.js @@ -1,7 +1,7 @@ import path from 'path' import slugify from 'underscore.string/slugify' +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/single-entry-views-custom-path-function.js b/test/single-entry-views-custom-path-function.js index fd08dff..3e38221 100644 --- a/test/single-entry-views-custom-path-function.js +++ b/test/single-entry-views-custom-path-function.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/single-entry-views-default-path-function.js b/test/single-entry-views-default-path-function.js index db485d0..10953dc 100644 --- a/test/single-entry-views-default-path-function.js +++ b/test/single-entry-views-default-path-function.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/single-entry-views-image-view-helper-function.js b/test/single-entry-views-image-view-helper-function.js index 800803b..e093eab 100644 --- a/test/single-entry-views-image-view-helper-function.js +++ b/test/single-entry-views-image-view-helper-function.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, diff --git a/test/write-as-json.js b/test/write-as-json.js index ec175ae..46c4136 100644 --- a/test/write-as-json.js +++ b/test/write-as-json.js @@ -1,5 +1,5 @@ +import test from 'ava' import { - test, helpers, mock_contentful, unmock_contentful, From e83f3af6264cbe6b649eb73e72237a7adde1f23d Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 7 Jan 2016 14:00:39 +0200 Subject: [PATCH 36/58] assert error messages --- test/config.js | 10 ++++++++-- test/contentful-content-type-fields.js | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/test/config.js b/test/config.js index 9a96309..e2d2fbd 100644 --- a/test/config.js +++ b/test/config.js @@ -20,11 +20,17 @@ test.before(async t => { }) test('should throw an error when missing an access token', async t => { - t.throws(ctx::compile_fixture('missing_token')) + t.throws( + ctx::compile_fixture('missing_token'), + 'Missing required options for roots-contentful. Please ensure `access_token` and `space_id` are present.' + ) }) test('should throw an error without content type id', async t => { - t.throws(ctx::compile_fixture('missing_config')) + t.throws( + ctx::compile_fixture('missing_config'), + 'One or more of your content types is missing an `id` value' + ) }) test('allows the content type name to be set through a k/v object config', async t => { diff --git a/test/contentful-content-type-fields.js b/test/contentful-content-type-fields.js index dcfbbcc..9d5c2ff 100644 --- a/test/contentful-content-type-fields.js +++ b/test/contentful-content-type-fields.js @@ -16,7 +16,10 @@ test.before(async t => { }) test('should throw an error if `sys` is a field name', async t => { - t.throws(ctx::compile_fixture('basic')) + t.throws( + ctx::compile_fixture('basic'), + 'One of your content types has `sys` as a field. This is reserved for storing Contentful system metadata, please rename this field to a different value.' + ) }) test.after(async t => { From 83097c835bf52318ed079942f1d0b13226d909a1 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 7 Jan 2016 14:17:20 +0200 Subject: [PATCH 37/58] fix flaky coverage commands --- Makefile | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 844d2c5..7d37720 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ build: npm run build -coverage: +coverage:: npm run coverage coveralls: diff --git a/package.json b/package.json index 80aba09..7e9df0f 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "posttest": "node test/_teardown.js", "prebuild": "npm test", "precommit": "npm run lint -s", - "precoverage": "nyc npm test", + "precoverage": "npm run test", "precoveralls": "npm run coverage", "prerelease": "npm run build", "pretest": "npm run lint -s && node test/_setup.js", From 528f64db9e15cc05eedd52d6cf268febade72360 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 7 Jan 2016 15:17:40 +0200 Subject: [PATCH 38/58] re-use error message variables in tests --- test/config.js | 11 +++-------- test/contentful-content-type-fields.js | 6 ++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/test/config.js b/test/config.js index e2d2fbd..88d6ea6 100644 --- a/test/config.js +++ b/test/config.js @@ -1,4 +1,5 @@ import test from 'ava' +import errors from '../src/errors' import { helpers, mock_contentful, @@ -20,17 +21,11 @@ test.before(async t => { }) test('should throw an error when missing an access token', async t => { - t.throws( - ctx::compile_fixture('missing_token'), - 'Missing required options for roots-contentful. Please ensure `access_token` and `space_id` are present.' - ) + t.throws(ctx::compile_fixture('missing_token'), errors.no_token) }) test('should throw an error without content type id', async t => { - t.throws( - ctx::compile_fixture('missing_config'), - 'One or more of your content types is missing an `id` value' - ) + t.throws(ctx::compile_fixture('missing_config'), errors.no_type_id) }) test('allows the content type name to be set through a k/v object config', async t => { diff --git a/test/contentful-content-type-fields.js b/test/contentful-content-type-fields.js index 9d5c2ff..2bfbcc6 100644 --- a/test/contentful-content-type-fields.js +++ b/test/contentful-content-type-fields.js @@ -1,4 +1,5 @@ import test from 'ava' +import errors from '../src/errors' import { mock_contentful, unmock_contentful, @@ -16,10 +17,7 @@ test.before(async t => { }) test('should throw an error if `sys` is a field name', async t => { - t.throws( - ctx::compile_fixture('basic'), - 'One of your content types has `sys` as a field. This is reserved for storing Contentful system metadata, please rename this field to a different value.' - ) + t.throws(ctx::compile_fixture('basic'), errors.sys_conflict) }) test.after(async t => { From 0f13a3d068b5c0d09ee6907fd5615477c19abe2c Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 7 Jan 2016 20:08:49 +0200 Subject: [PATCH 39/58] update ava, end class props with semicolon, ensure tests don't modify the same fixture --- package.json | 2 +- src/extension.js | 2 +- test/_setup.js | 2 +- test/_teardown.js | 2 +- test/{basic-compile.js => basic/compile.js} | 4 ++-- .../content-type-fields.js} | 6 +++--- .../custom-locals.js} | 4 ++-- test/config.js | 8 ++++---- .../sort.js} | 6 +++--- .../transform.js} | 6 +++--- .../{basic => basic--compile}/about.jade | 0 .../{basic => basic--compile}/app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../basic--content-type-fields/about.jade | 1 + .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../about.jade | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../about.jade | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../posts_expected.json | 0 .../about.jade | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../posts_expected.json | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../views/_blog_post.jade | 0 .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../views/_blog_post.jade | 0 .../app.coffee | 20 +++++++++++++++++++ .../index.jade | 4 ++++ .../package.json | 0 .../views/_blog_post.jade | 3 +++ .../app.coffee | 18 +++++++++++++++++ .../index.jade | 0 .../package.json | 6 ++++++ .../app.coffee | 0 .../index.jade | 0 .../package.json | 0 .../views/_blog_post.jade | 0 .../{write => write--as-json}/about.jade | 0 .../{write => write--as-json}/app.coffee | 0 test/fixtures/write--as-json/index.jade | 5 +++++ test/fixtures/write--as-json/package.json | 6 ++++++ test/{_helpers.js => helpers/index.js} | 2 +- .../clear-locals-between-compiles.js} | 4 ++-- .../custom-path-function.js} | 4 ++-- .../default-path-function.js} | 4 ++-- .../image-view-helper.js} | 4 ++-- .../multi-path-function.js} | 4 ++-- test/{write-as-json.js => write.js} | 4 ++-- 71 files changed, 97 insertions(+), 34 deletions(-) rename test/{basic-compile.js => basic/compile.js} (91%) rename test/{contentful-content-type-fields.js => basic/content-type-fields.js} (70%) rename test/{custom-name-for-view-helper-local.js => basic/custom-locals.js} (89%) rename test/{data-manipulation-sort.js => data-manipulation/sort.js} (86%) rename test/{data-manipulation-transform.js => data-manipulation/transform.js} (86%) rename test/fixtures/{basic => basic--compile}/about.jade (100%) rename test/fixtures/{basic => basic--compile}/app.coffee (100%) rename test/fixtures/{alt-content-type-config => basic--compile}/index.jade (100%) rename test/fixtures/{alt-content-type-config => basic--compile}/package.json (100%) create mode 100644 test/fixtures/basic--content-type-fields/about.jade rename test/fixtures/{image_view_helper => basic--content-type-fields}/app.coffee (100%) rename test/fixtures/{basic => basic--content-type-fields}/index.jade (100%) rename test/fixtures/{basic => basic--content-type-fields}/package.json (100%) rename test/fixtures/{custom_name => basic--custom-locals}/about.jade (100%) rename test/fixtures/{custom_name => basic--custom-locals}/app.coffee (100%) rename test/fixtures/{custom_name => basic--custom-locals}/index.jade (100%) rename test/fixtures/{custom_name => basic--custom-locals}/package.json (100%) rename test/fixtures/{alt-content-type-config => config--alternative-type}/app.coffee (100%) rename test/fixtures/{missing_config => config--alternative-type}/index.jade (100%) rename test/fixtures/{image_view_helper => config--alternative-type}/package.json (100%) rename test/fixtures/{missing_config => config--missing-config}/app.coffee (100%) rename test/fixtures/{sort => config--missing-config}/index.jade (100%) rename test/fixtures/{missing_config => config--missing-config}/package.json (100%) rename test/fixtures/{missing_token => config--missing-token}/app.coffee (100%) rename test/fixtures/{missing_token => config--missing-token}/index.jade (100%) rename test/fixtures/{missing_token => config--missing-token}/package.json (100%) rename test/fixtures/{sort => data-manipulation--sort}/about.jade (100%) rename test/fixtures/{sort => data-manipulation--sort}/app.coffee (100%) rename test/fixtures/{transform => data-manipulation--sort}/index.jade (100%) rename test/fixtures/{single_entry => data-manipulation--sort}/package.json (100%) rename test/fixtures/{sort => data-manipulation--sort}/posts_expected.json (100%) rename test/fixtures/{transform => data-manipulation--transform}/about.jade (100%) rename test/fixtures/{transform => data-manipulation--transform}/app.coffee (100%) rename test/fixtures/{write => data-manipulation--transform}/index.jade (100%) rename test/fixtures/{sort => data-manipulation--transform}/package.json (100%) rename test/fixtures/{transform => data-manipulation--transform}/posts_expected.json (100%) rename test/fixtures/{single_entry => single-entry--clear-locals-between-compiles}/app.coffee (100%) rename test/fixtures/{single_entry => single-entry--clear-locals-between-compiles}/index.jade (100%) rename test/fixtures/{transform => single-entry--clear-locals-between-compiles}/package.json (100%) rename test/fixtures/{single_entry => single-entry--clear-locals-between-compiles}/views/_blog_post.jade (100%) rename test/fixtures/{single_entry_custom => single-entry--custom-path-function}/app.coffee (100%) rename test/fixtures/{single_entry_custom => single-entry--custom-path-function}/index.jade (100%) rename test/fixtures/{single_entry_custom => single-entry--custom-path-function}/package.json (100%) rename test/fixtures/{single_entry_custom => single-entry--custom-path-function}/views/_blog_post.jade (100%) create mode 100644 test/fixtures/single-entry--default-path-function/app.coffee create mode 100644 test/fixtures/single-entry--default-path-function/index.jade rename test/fixtures/{write => single-entry--default-path-function}/package.json (100%) create mode 100644 test/fixtures/single-entry--default-path-function/views/_blog_post.jade create mode 100644 test/fixtures/single-entry--image-view-helper/app.coffee rename test/fixtures/{image_view_helper => single-entry--image-view-helper}/index.jade (100%) create mode 100644 test/fixtures/single-entry--image-view-helper/package.json rename test/fixtures/{single_entry_multi => single-entry--multi-path-function}/app.coffee (100%) rename test/fixtures/{single_entry_multi => single-entry--multi-path-function}/index.jade (100%) rename test/fixtures/{single_entry_multi => single-entry--multi-path-function}/package.json (100%) rename test/fixtures/{single_entry_multi => single-entry--multi-path-function}/views/_blog_post.jade (100%) rename test/fixtures/{write => write--as-json}/about.jade (100%) rename test/fixtures/{write => write--as-json}/app.coffee (100%) create mode 100644 test/fixtures/write--as-json/index.jade create mode 100644 test/fixtures/write--as-json/package.json rename test/{_helpers.js => helpers/index.js} (96%) rename test/{single-entry-views-clear-entry-locals-between-each-compile.js => single-entry/clear-locals-between-compiles.js} (88%) rename test/{single-entry-views-custom-path-function.js => single-entry/custom-path-function.js} (90%) rename test/{single-entry-views-default-path-function.js => single-entry/default-path-function.js} (92%) rename test/{single-entry-views-image-view-helper-function.js => single-entry/image-view-helper.js} (87%) rename test/{single-entry-views-custom-multi-path-function.js => single-entry/multi-path-function.js} (94%) rename test/{write-as-json.js => write.js} (91%) diff --git a/package.json b/package.json index 7e9df0f..f89c442 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "underscore.string": "^3.2.2" }, "devDependencies": { - "ava": "sindresorhus/ava#65ae07c", + "ava": "^0.9.1", "babel-cli": "^6.3.15", "babel-core": "^6.3.26", "babel-eslint": "^4.1.6", diff --git a/src/extension.js b/src/extension.js index e4e66de..91f5492 100644 --- a/src/extension.js +++ b/src/extension.js @@ -21,7 +21,7 @@ export default class RootsContentful { cache: true, /* user-provided */ ...RootsContentful.opts - } + }; /** * @constructs RootsContentful diff --git a/test/_setup.js b/test/_setup.js index ad06ce6..25940be 100644 --- a/test/_setup.js +++ b/test/_setup.js @@ -1,5 +1,5 @@ require('babel-core/register') -var helpers = require('./_helpers').helpers +var helpers = require('./helpers').helpers console.log('setting up...') helpers.project.install_dependencies('*', function () { console.log('done with setup') diff --git a/test/_teardown.js b/test/_teardown.js index ce5bc7e..4de7aaa 100644 --- a/test/_teardown.js +++ b/test/_teardown.js @@ -1,5 +1,5 @@ require('babel-core/register') -var helpers = require('./_helpers').helpers +var helpers = require('./helpers').helpers console.log('performing cleanup...') helpers.project.remove_folders('**/public') console.log('done with cleanup') diff --git a/test/basic-compile.js b/test/basic/compile.js similarity index 91% rename from test/basic-compile.js rename to test/basic/compile.js index 3311955..13c09e1 100644 --- a/test/basic-compile.js +++ b/test/basic/compile.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -17,7 +17,7 @@ test.before(async t => { fields: { title, body } }] }) - await ctx::compile_fixture('basic') + await ctx::compile_fixture('basic--compile') ctx.index_path = `${ctx.public_dir}/index.html` }) diff --git a/test/contentful-content-type-fields.js b/test/basic/content-type-fields.js similarity index 70% rename from test/contentful-content-type-fields.js rename to test/basic/content-type-fields.js index 2bfbcc6..ec0e0ea 100644 --- a/test/contentful-content-type-fields.js +++ b/test/basic/content-type-fields.js @@ -1,10 +1,10 @@ import test from 'ava' -import errors from '../src/errors' +import errors from '../../src/errors' import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -17,7 +17,7 @@ test.before(async t => { }) test('should throw an error if `sys` is a field name', async t => { - t.throws(ctx::compile_fixture('basic'), errors.sys_conflict) + t.throws(ctx::compile_fixture('basic--content-type-fields'), errors.sys_conflict) }) test.after(async t => { diff --git a/test/custom-name-for-view-helper-local.js b/test/basic/custom-locals.js similarity index 89% rename from test/custom-name-for-view-helper-local.js rename to test/basic/custom-locals.js index 43ba1b0..081dc5a 100644 --- a/test/custom-name-for-view-helper-local.js +++ b/test/basic/custom-locals.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -17,7 +17,7 @@ test.before(async t => { fields: { title, body } }] }) - await ctx::compile_fixture('custom_name') + await ctx::compile_fixture('basic--custom-locals') ctx.index_path = `${ctx.public_dir}/index.html` }) diff --git a/test/config.js b/test/config.js index 88d6ea6..56ddc63 100644 --- a/test/config.js +++ b/test/config.js @@ -5,7 +5,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from './helpers' let ctx = {} @@ -21,15 +21,15 @@ test.before(async t => { }) test('should throw an error when missing an access token', async t => { - t.throws(ctx::compile_fixture('missing_token'), errors.no_token) + t.throws(ctx::compile_fixture('config--missing-token'), errors.no_token) }) test('should throw an error without content type id', async t => { - t.throws(ctx::compile_fixture('missing_config'), errors.no_type_id) + t.throws(ctx::compile_fixture('config--missing-config'), errors.no_type_id) }) test('allows the content type name to be set through a k/v object config', async t => { - await ctx::compile_fixture('alt-content-type-config') + await ctx::compile_fixture('config--alternative-type') ctx.index_path = `${ctx.public_dir}/index.html` t.true(helpers.file.contains(ctx.index_path, ctx.title)) t.true(helpers.file.contains(ctx.index_path, ctx.body)) diff --git a/test/data-manipulation-sort.js b/test/data-manipulation/sort.js similarity index 86% rename from test/data-manipulation-sort.js rename to test/data-manipulation/sort.js index db5af12..79ee362 100644 --- a/test/data-manipulation-sort.js +++ b/test/data-manipulation/sort.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -19,7 +19,7 @@ test.before(async t => { fields: { title, body: ctx.bodies[i] } })) mock_contentful({ entries: ctx.entries }) - await ctx::compile_fixture('sort') + await ctx::compile_fixture('data-manipulation--sort') ctx.index_path = `${ctx.public_dir}/index.html` ctx.posts_path = `${ctx.public_dir}/posts.json` }) @@ -42,7 +42,7 @@ test('orders data correctly for the project', t => { test('has written data as json', t => { t.ok(helpers.file.exists(ctx.posts_path)) - t.true(helpers.file.matches_file(ctx.posts_path, 'sort/posts_expected.json')) + t.true(helpers.file.matches_file(ctx.posts_path, 'data-manipulation--sort/posts_expected.json')) }) test.after(async t => { diff --git a/test/data-manipulation-transform.js b/test/data-manipulation/transform.js similarity index 86% rename from test/data-manipulation-transform.js rename to test/data-manipulation/transform.js index 72bfaef..16cd5fe 100644 --- a/test/data-manipulation-transform.js +++ b/test/data-manipulation/transform.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -19,7 +19,7 @@ test.before(async t => { fields: { title, body: ctx.bodies[i] } })) mock_contentful({ entries: ctx.entries }) - await ctx::compile_fixture('transform') + await ctx::compile_fixture('data-manipulation--transform') ctx.index_path = `${ctx.public_dir}/index.html` ctx.posts_path = `${ctx.public_dir}/posts.json` }) @@ -45,7 +45,7 @@ test('has manipulated data correctly for the project', t => { test('has written data as json', t => { t.ok(helpers.file.exists(ctx.posts_path)) - t.true(helpers.file.matches_file(ctx.posts_path, 'transform/posts_expected.json')) + t.true(helpers.file.matches_file(ctx.posts_path, 'data-manipulation--transform/posts_expected.json')) }) test.after(async t => { diff --git a/test/fixtures/basic/about.jade b/test/fixtures/basic--compile/about.jade similarity index 100% rename from test/fixtures/basic/about.jade rename to test/fixtures/basic--compile/about.jade diff --git a/test/fixtures/basic/app.coffee b/test/fixtures/basic--compile/app.coffee similarity index 100% rename from test/fixtures/basic/app.coffee rename to test/fixtures/basic--compile/app.coffee diff --git a/test/fixtures/alt-content-type-config/index.jade b/test/fixtures/basic--compile/index.jade similarity index 100% rename from test/fixtures/alt-content-type-config/index.jade rename to test/fixtures/basic--compile/index.jade diff --git a/test/fixtures/alt-content-type-config/package.json b/test/fixtures/basic--compile/package.json similarity index 100% rename from test/fixtures/alt-content-type-config/package.json rename to test/fixtures/basic--compile/package.json diff --git a/test/fixtures/basic--content-type-fields/about.jade b/test/fixtures/basic--content-type-fields/about.jade new file mode 100644 index 0000000..2763786 --- /dev/null +++ b/test/fixtures/basic--content-type-fields/about.jade @@ -0,0 +1 @@ +h1 wow \ No newline at end of file diff --git a/test/fixtures/image_view_helper/app.coffee b/test/fixtures/basic--content-type-fields/app.coffee similarity index 100% rename from test/fixtures/image_view_helper/app.coffee rename to test/fixtures/basic--content-type-fields/app.coffee diff --git a/test/fixtures/basic/index.jade b/test/fixtures/basic--content-type-fields/index.jade similarity index 100% rename from test/fixtures/basic/index.jade rename to test/fixtures/basic--content-type-fields/index.jade diff --git a/test/fixtures/basic/package.json b/test/fixtures/basic--content-type-fields/package.json similarity index 100% rename from test/fixtures/basic/package.json rename to test/fixtures/basic--content-type-fields/package.json diff --git a/test/fixtures/custom_name/about.jade b/test/fixtures/basic--custom-locals/about.jade similarity index 100% rename from test/fixtures/custom_name/about.jade rename to test/fixtures/basic--custom-locals/about.jade diff --git a/test/fixtures/custom_name/app.coffee b/test/fixtures/basic--custom-locals/app.coffee similarity index 100% rename from test/fixtures/custom_name/app.coffee rename to test/fixtures/basic--custom-locals/app.coffee diff --git a/test/fixtures/custom_name/index.jade b/test/fixtures/basic--custom-locals/index.jade similarity index 100% rename from test/fixtures/custom_name/index.jade rename to test/fixtures/basic--custom-locals/index.jade diff --git a/test/fixtures/custom_name/package.json b/test/fixtures/basic--custom-locals/package.json similarity index 100% rename from test/fixtures/custom_name/package.json rename to test/fixtures/basic--custom-locals/package.json diff --git a/test/fixtures/alt-content-type-config/app.coffee b/test/fixtures/config--alternative-type/app.coffee similarity index 100% rename from test/fixtures/alt-content-type-config/app.coffee rename to test/fixtures/config--alternative-type/app.coffee diff --git a/test/fixtures/missing_config/index.jade b/test/fixtures/config--alternative-type/index.jade similarity index 100% rename from test/fixtures/missing_config/index.jade rename to test/fixtures/config--alternative-type/index.jade diff --git a/test/fixtures/image_view_helper/package.json b/test/fixtures/config--alternative-type/package.json similarity index 100% rename from test/fixtures/image_view_helper/package.json rename to test/fixtures/config--alternative-type/package.json diff --git a/test/fixtures/missing_config/app.coffee b/test/fixtures/config--missing-config/app.coffee similarity index 100% rename from test/fixtures/missing_config/app.coffee rename to test/fixtures/config--missing-config/app.coffee diff --git a/test/fixtures/sort/index.jade b/test/fixtures/config--missing-config/index.jade similarity index 100% rename from test/fixtures/sort/index.jade rename to test/fixtures/config--missing-config/index.jade diff --git a/test/fixtures/missing_config/package.json b/test/fixtures/config--missing-config/package.json similarity index 100% rename from test/fixtures/missing_config/package.json rename to test/fixtures/config--missing-config/package.json diff --git a/test/fixtures/missing_token/app.coffee b/test/fixtures/config--missing-token/app.coffee similarity index 100% rename from test/fixtures/missing_token/app.coffee rename to test/fixtures/config--missing-token/app.coffee diff --git a/test/fixtures/missing_token/index.jade b/test/fixtures/config--missing-token/index.jade similarity index 100% rename from test/fixtures/missing_token/index.jade rename to test/fixtures/config--missing-token/index.jade diff --git a/test/fixtures/missing_token/package.json b/test/fixtures/config--missing-token/package.json similarity index 100% rename from test/fixtures/missing_token/package.json rename to test/fixtures/config--missing-token/package.json diff --git a/test/fixtures/sort/about.jade b/test/fixtures/data-manipulation--sort/about.jade similarity index 100% rename from test/fixtures/sort/about.jade rename to test/fixtures/data-manipulation--sort/about.jade diff --git a/test/fixtures/sort/app.coffee b/test/fixtures/data-manipulation--sort/app.coffee similarity index 100% rename from test/fixtures/sort/app.coffee rename to test/fixtures/data-manipulation--sort/app.coffee diff --git a/test/fixtures/transform/index.jade b/test/fixtures/data-manipulation--sort/index.jade similarity index 100% rename from test/fixtures/transform/index.jade rename to test/fixtures/data-manipulation--sort/index.jade diff --git a/test/fixtures/single_entry/package.json b/test/fixtures/data-manipulation--sort/package.json similarity index 100% rename from test/fixtures/single_entry/package.json rename to test/fixtures/data-manipulation--sort/package.json diff --git a/test/fixtures/sort/posts_expected.json b/test/fixtures/data-manipulation--sort/posts_expected.json similarity index 100% rename from test/fixtures/sort/posts_expected.json rename to test/fixtures/data-manipulation--sort/posts_expected.json diff --git a/test/fixtures/transform/about.jade b/test/fixtures/data-manipulation--transform/about.jade similarity index 100% rename from test/fixtures/transform/about.jade rename to test/fixtures/data-manipulation--transform/about.jade diff --git a/test/fixtures/transform/app.coffee b/test/fixtures/data-manipulation--transform/app.coffee similarity index 100% rename from test/fixtures/transform/app.coffee rename to test/fixtures/data-manipulation--transform/app.coffee diff --git a/test/fixtures/write/index.jade b/test/fixtures/data-manipulation--transform/index.jade similarity index 100% rename from test/fixtures/write/index.jade rename to test/fixtures/data-manipulation--transform/index.jade diff --git a/test/fixtures/sort/package.json b/test/fixtures/data-manipulation--transform/package.json similarity index 100% rename from test/fixtures/sort/package.json rename to test/fixtures/data-manipulation--transform/package.json diff --git a/test/fixtures/transform/posts_expected.json b/test/fixtures/data-manipulation--transform/posts_expected.json similarity index 100% rename from test/fixtures/transform/posts_expected.json rename to test/fixtures/data-manipulation--transform/posts_expected.json diff --git a/test/fixtures/single_entry/app.coffee b/test/fixtures/single-entry--clear-locals-between-compiles/app.coffee similarity index 100% rename from test/fixtures/single_entry/app.coffee rename to test/fixtures/single-entry--clear-locals-between-compiles/app.coffee diff --git a/test/fixtures/single_entry/index.jade b/test/fixtures/single-entry--clear-locals-between-compiles/index.jade similarity index 100% rename from test/fixtures/single_entry/index.jade rename to test/fixtures/single-entry--clear-locals-between-compiles/index.jade diff --git a/test/fixtures/transform/package.json b/test/fixtures/single-entry--clear-locals-between-compiles/package.json similarity index 100% rename from test/fixtures/transform/package.json rename to test/fixtures/single-entry--clear-locals-between-compiles/package.json diff --git a/test/fixtures/single_entry/views/_blog_post.jade b/test/fixtures/single-entry--clear-locals-between-compiles/views/_blog_post.jade similarity index 100% rename from test/fixtures/single_entry/views/_blog_post.jade rename to test/fixtures/single-entry--clear-locals-between-compiles/views/_blog_post.jade diff --git a/test/fixtures/single_entry_custom/app.coffee b/test/fixtures/single-entry--custom-path-function/app.coffee similarity index 100% rename from test/fixtures/single_entry_custom/app.coffee rename to test/fixtures/single-entry--custom-path-function/app.coffee diff --git a/test/fixtures/single_entry_custom/index.jade b/test/fixtures/single-entry--custom-path-function/index.jade similarity index 100% rename from test/fixtures/single_entry_custom/index.jade rename to test/fixtures/single-entry--custom-path-function/index.jade diff --git a/test/fixtures/single_entry_custom/package.json b/test/fixtures/single-entry--custom-path-function/package.json similarity index 100% rename from test/fixtures/single_entry_custom/package.json rename to test/fixtures/single-entry--custom-path-function/package.json diff --git a/test/fixtures/single_entry_custom/views/_blog_post.jade b/test/fixtures/single-entry--custom-path-function/views/_blog_post.jade similarity index 100% rename from test/fixtures/single_entry_custom/views/_blog_post.jade rename to test/fixtures/single-entry--custom-path-function/views/_blog_post.jade diff --git a/test/fixtures/single-entry--default-path-function/app.coffee b/test/fixtures/single-entry--default-path-function/app.coffee new file mode 100644 index 0000000..b9d39a8 --- /dev/null +++ b/test/fixtures/single-entry--default-path-function/app.coffee @@ -0,0 +1,20 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + name: 'blog_posts' + template: 'views/_blog_post.jade' + } + ] + ) + ] + + locals: + wow: 'such local' diff --git a/test/fixtures/single-entry--default-path-function/index.jade b/test/fixtures/single-entry--default-path-function/index.jade new file mode 100644 index 0000000..b881d53 --- /dev/null +++ b/test/fixtures/single-entry--default-path-function/index.jade @@ -0,0 +1,4 @@ +- for p in contentful.blog_posts + h1= p.title + p= p.body + a(href= p._url) Link diff --git a/test/fixtures/write/package.json b/test/fixtures/single-entry--default-path-function/package.json similarity index 100% rename from test/fixtures/write/package.json rename to test/fixtures/single-entry--default-path-function/package.json diff --git a/test/fixtures/single-entry--default-path-function/views/_blog_post.jade b/test/fixtures/single-entry--default-path-function/views/_blog_post.jade new file mode 100644 index 0000000..78a5b6c --- /dev/null +++ b/test/fixtures/single-entry--default-path-function/views/_blog_post.jade @@ -0,0 +1,3 @@ +h1= entry.title +p= entry.body +p= wow diff --git a/test/fixtures/single-entry--image-view-helper/app.coffee b/test/fixtures/single-entry--image-view-helper/app.coffee new file mode 100644 index 0000000..c2c261c --- /dev/null +++ b/test/fixtures/single-entry--image-view-helper/app.coffee @@ -0,0 +1,18 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + { + id: '7CDlVsacqQc88cmIEGYWMa' + } + ] + ) + ] diff --git a/test/fixtures/image_view_helper/index.jade b/test/fixtures/single-entry--image-view-helper/index.jade similarity index 100% rename from test/fixtures/image_view_helper/index.jade rename to test/fixtures/single-entry--image-view-helper/index.jade diff --git a/test/fixtures/single-entry--image-view-helper/package.json b/test/fixtures/single-entry--image-view-helper/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/single-entry--image-view-helper/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/single_entry_multi/app.coffee b/test/fixtures/single-entry--multi-path-function/app.coffee similarity index 100% rename from test/fixtures/single_entry_multi/app.coffee rename to test/fixtures/single-entry--multi-path-function/app.coffee diff --git a/test/fixtures/single_entry_multi/index.jade b/test/fixtures/single-entry--multi-path-function/index.jade similarity index 100% rename from test/fixtures/single_entry_multi/index.jade rename to test/fixtures/single-entry--multi-path-function/index.jade diff --git a/test/fixtures/single_entry_multi/package.json b/test/fixtures/single-entry--multi-path-function/package.json similarity index 100% rename from test/fixtures/single_entry_multi/package.json rename to test/fixtures/single-entry--multi-path-function/package.json diff --git a/test/fixtures/single_entry_multi/views/_blog_post.jade b/test/fixtures/single-entry--multi-path-function/views/_blog_post.jade similarity index 100% rename from test/fixtures/single_entry_multi/views/_blog_post.jade rename to test/fixtures/single-entry--multi-path-function/views/_blog_post.jade diff --git a/test/fixtures/write/about.jade b/test/fixtures/write--as-json/about.jade similarity index 100% rename from test/fixtures/write/about.jade rename to test/fixtures/write--as-json/about.jade diff --git a/test/fixtures/write/app.coffee b/test/fixtures/write--as-json/app.coffee similarity index 100% rename from test/fixtures/write/app.coffee rename to test/fixtures/write--as-json/app.coffee diff --git a/test/fixtures/write--as-json/index.jade b/test/fixtures/write--as-json/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/write--as-json/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/write--as-json/package.json b/test/fixtures/write--as-json/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/write--as-json/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/_helpers.js b/test/helpers/index.js similarity index 96% rename from test/_helpers.js rename to test/helpers/index.js index c60f972..2f9b392 100644 --- a/test/_helpers.js +++ b/test/helpers/index.js @@ -16,7 +16,7 @@ Array.prototype.includes = do { /* eslint-enable */ export const helpers = new RootsUtil.Helpers({ - base: path.join(__dirname, './fixtures') + base: path.join(__dirname, '../fixtures') }) export async function compile_fixture (name) { diff --git a/test/single-entry-views-clear-entry-locals-between-each-compile.js b/test/single-entry/clear-locals-between-compiles.js similarity index 88% rename from test/single-entry-views-clear-entry-locals-between-each-compile.js rename to test/single-entry/clear-locals-between-compiles.js index 5630de9..ae0cb98 100644 --- a/test/single-entry-views-clear-entry-locals-between-each-compile.js +++ b/test/single-entry/clear-locals-between-compiles.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -20,7 +20,7 @@ test.before(async t => { ], content_type: { name: 'Blog Post', displayField: 'title' } }) - await ctx::compile_fixture('single_entry') + await ctx::compile_fixture('single-entry--clear-locals-between-compiles') ctx.index_path = `${ctx.public_dir}/index.html` ctx.post_path = `${ctx.public_dir}/blog_posts/totes-mcgotes.html` }) diff --git a/test/single-entry-views-custom-path-function.js b/test/single-entry/custom-path-function.js similarity index 90% rename from test/single-entry-views-custom-path-function.js rename to test/single-entry/custom-path-function.js index 3e38221..fa2d7ab 100644 --- a/test/single-entry-views-custom-path-function.js +++ b/test/single-entry/custom-path-function.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -19,7 +19,7 @@ test.before(async t => { }], content_type: { name: 'Blog Post', displayField: 'title' } }) - await ctx::compile_fixture('single_entry_custom') + await ctx::compile_fixture('single-entry--custom-path-function') ctx.index_path = `${ctx.public_dir}/index.html` ctx.post_path = `${ctx.public_dir}/blogging/${ctx.category}/real-talk.html` }) diff --git a/test/single-entry-views-default-path-function.js b/test/single-entry/default-path-function.js similarity index 92% rename from test/single-entry-views-default-path-function.js rename to test/single-entry/default-path-function.js index 10953dc..5d48297 100644 --- a/test/single-entry-views-default-path-function.js +++ b/test/single-entry/default-path-function.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -18,7 +18,7 @@ test.before(async t => { }], content_type: { name: 'Blog Post', displayField: 'title' } }) - await ctx::compile_fixture('single_entry') + await ctx::compile_fixture('single-entry--default-path-function') ctx.index_path = `${ctx.public_dir}/index.html` ctx.post_path = `${ctx.public_dir}/blog_posts/real-talk.html` }) diff --git a/test/single-entry-views-image-view-helper-function.js b/test/single-entry/image-view-helper.js similarity index 87% rename from test/single-entry-views-image-view-helper-function.js rename to test/single-entry/image-view-helper.js index e093eab..18d934a 100644 --- a/test/single-entry-views-image-view-helper-function.js +++ b/test/single-entry/image-view-helper.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = { img_path: 'http://dogesay.com/wow.jpg' } @@ -18,7 +18,7 @@ test.before(async t => { } }] }) - await ctx::compile_fixture('image_view_helper') + await ctx::compile_fixture('single-entry--image-view-helper') ctx.index_path = `${ctx.public_dir}/index.html` }) diff --git a/test/single-entry-views-custom-multi-path-function.js b/test/single-entry/multi-path-function.js similarity index 94% rename from test/single-entry-views-custom-multi-path-function.js rename to test/single-entry/multi-path-function.js index 78bcb83..941b20e 100644 --- a/test/single-entry-views-custom-multi-path-function.js +++ b/test/single-entry/multi-path-function.js @@ -6,7 +6,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from '../helpers' let ctx = {} @@ -23,7 +23,7 @@ test.before(async t => { })), content_type: { name: 'Blog Post', displayField: 'title' } }) - await ctx::compile_fixture('single_entry_multi') + await ctx::compile_fixture('single-entry--multi-path-function') ctx.index_path = `${ctx.public_dir}/index.html` }) diff --git a/test/write-as-json.js b/test/write.js similarity index 91% rename from test/write-as-json.js rename to test/write.js index 46c4136..348db78 100644 --- a/test/write-as-json.js +++ b/test/write.js @@ -4,7 +4,7 @@ import { mock_contentful, unmock_contentful, compile_fixture -} from './_helpers' +} from './helpers' let ctx = {} @@ -17,7 +17,7 @@ test.before(async t => { fields: { title, body } }] }) - await ctx::compile_fixture('write') + await ctx::compile_fixture('write--as-json') ctx.index_path = `${ctx.public_dir}/index.html` ctx.posts_path = `${ctx.public_dir}/posts.json` }) From 33d2e69330320682f6102dbe2d8277f6a48bf29a Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Fri, 8 Jan 2016 17:46:34 +0200 Subject: [PATCH 40/58] add missing test for asset helper else path --- .../index.jade | 5 ++++- test/single-entry/image-view-helper.js | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/test/fixtures/single-entry--image-view-helper/index.jade b/test/fixtures/single-entry--image-view-helper/index.jade index 3eee69d..a33a043 100644 --- a/test/fixtures/single-entry--image-view-helper/index.jade +++ b/test/fixtures/single-entry--image-view-helper/index.jade @@ -1,4 +1,7 @@ ul - for p in contentful.blog_posts li - img(src!= asset(p.image, {w: 100, h: 100})) + if p.image.fields.file.url == 'http://dogesay.com/wow-query.jpg' + img(src!= asset(p.image, {w: 100, h: 100})) + else + img(src!= asset(p.image)) diff --git a/test/single-entry/image-view-helper.js b/test/single-entry/image-view-helper.js index 18d934a..445aa2e 100644 --- a/test/single-entry/image-view-helper.js +++ b/test/single-entry/image-view-helper.js @@ -6,14 +6,23 @@ import { compile_fixture } from '../helpers' -let ctx = { img_path: 'http://dogesay.com/wow.jpg' } +let ctx = { + query_img_path: 'http://dogesay.com/wow-query.jpg', + regular_img_path: 'http://dogesay.com/wow.jpg' +} test.before(async t => { mock_contentful({ entries: [{ fields: { image: { - fields: { file: { url: ctx.img_path } } + fields: { file: { url: ctx.query_img_path } } + } + } + }, { + fields: { + image: { + fields: { file: { url: ctx.regular_img_path } } } } }] @@ -22,8 +31,12 @@ test.before(async t => { ctx.index_path = `${ctx.public_dir}/index.html` }) +test('renders out image path', t => { + t.true(helpers.file.contains(ctx.index_path, `${ctx.regular_img_path}`)) +}) + test('adds query string params to the image', t => { - t.true(helpers.file.contains(ctx.index_path, `${ctx.img_path}?w=100&h=100`)) + t.true(helpers.file.contains(ctx.index_path, `${ctx.query_img_path}?w=100&h=100`)) }) test.after(async t => { From 8b8ed730131c7fd7b2fc10b3fec5fd6c94ea8980 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Fri, 8 Jan 2016 17:52:50 +0200 Subject: [PATCH 41/58] remove redundant branch from asset helper --- src/extension.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.js b/src/extension.js index 91f5492..448d49d 100644 --- a/src/extension.js +++ b/src/extension.js @@ -280,7 +280,7 @@ async function write_entries (types) { * @param {Object} params - Query string params to append to the URL * @return {String} - URL string for the asset */ -function asset_view_helper (asset = {}, params) { +function asset_view_helper (asset, params) { asset = { fields: { file: {} }, ...asset } let url = asset.fields.file.url if (params) { From a86e2196391f355521b42ce7360219754c2149cd Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 12 Jan 2016 13:35:04 +0200 Subject: [PATCH 42/58] update dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f89c442..5d2cc6e 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,16 @@ }, "dependencies": { "babel-runtime": "^6.3.13", - "contentful": "^1.1.5", + "contentful": "^2.1.0", "pluralize": "^1.2.1", - "roots-util": "0.1.x", + "roots-util": "0.2.x", "underscore.string": "^3.2.2" }, "devDependencies": { "ava": "^0.9.1", "babel-cli": "^6.3.15", "babel-core": "^6.3.26", - "babel-eslint": "^4.1.6", + "babel-eslint": "^5.0.0-beta6", "babel-plugin-add-module-exports": "^0.1.2", "babel-plugin-transform-runtime": "^6.3.13", "babel-preset-es2015-node5": "^1.1.1", From af02cf3c9f5bd020f3e77fc1668777e4e29c427e Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Tue, 12 Jan 2016 15:22:18 +0200 Subject: [PATCH 43/58] use new roots-util async opts in tests --- test/basic/compile.js | 11 ++++--- test/basic/custom-locals.js | 7 +++-- test/config.js | 5 +-- test/data-manipulation/sort.js | 24 +++++++------- test/data-manipulation/transform.js | 26 +++++++++------- test/helpers/index.js | 2 ++ .../clear-locals-between-compiles.js | 5 +-- test/single-entry/custom-path-function.js | 9 +++--- test/single-entry/default-path-function.js | 19 ++++++------ test/single-entry/image-view-helper.js | 9 +++--- test/single-entry/multi-path-function.js | 31 ++++++++++--------- test/write.js | 13 ++++---- 12 files changed, 88 insertions(+), 73 deletions(-) diff --git a/test/basic/compile.js b/test/basic/compile.js index 13c09e1..f1f68d2 100644 --- a/test/basic/compile.js +++ b/test/basic/compile.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -21,13 +22,13 @@ test.before(async t => { ctx.index_path = `${ctx.public_dir}/index.html` }) -test('compiles basic project', t => { - t.ok(helpers.file.exists(ctx.index_path)) +test('compiles basic project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) }) -test('has contentful data available in views', t => { - t.true(helpers.file.contains(ctx.index_path, ctx.title)) - t.true(helpers.file.contains(ctx.index_path, ctx.body)) +test('has contentful data available in views', async t => { + t.true(await helpers.file.contains(ctx.index_path, ctx.title, { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.body, { async })) }) test.after(async t => { diff --git a/test/basic/custom-locals.js b/test/basic/custom-locals.js index 081dc5a..55e9e79 100644 --- a/test/basic/custom-locals.js +++ b/test/basic/custom-locals.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -21,9 +22,9 @@ test.before(async t => { ctx.index_path = `${ctx.public_dir}/index.html` }) -test('has contentful data available in views under a custom name', t => { - t.true(helpers.file.contains(ctx.index_path, ctx.title)) - t.true(helpers.file.contains(ctx.index_path, ctx.body)) +test('has contentful data available in views under a custom name', async t => { + t.true(await helpers.file.contains(ctx.index_path, ctx.title, { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.body, { async })) }) test.after(async t => { diff --git a/test/config.js b/test/config.js index 56ddc63..dd08d90 100644 --- a/test/config.js +++ b/test/config.js @@ -1,6 +1,7 @@ import test from 'ava' import errors from '../src/errors' import { + async, helpers, mock_contentful, unmock_contentful, @@ -31,8 +32,8 @@ test('should throw an error without content type id', async t => { test('allows the content type name to be set through a k/v object config', async t => { await ctx::compile_fixture('config--alternative-type') ctx.index_path = `${ctx.public_dir}/index.html` - t.true(helpers.file.contains(ctx.index_path, ctx.title)) - t.true(helpers.file.contains(ctx.index_path, ctx.body)) + t.true(await helpers.file.contains(ctx.index_path, ctx.title, { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.body, { async })) }) test.after(async t => { diff --git a/test/data-manipulation/sort.js b/test/data-manipulation/sort.js index 79ee362..e38baf2 100644 --- a/test/data-manipulation/sort.js +++ b/test/data-manipulation/sort.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -24,25 +25,26 @@ test.before(async t => { ctx.posts_path = `${ctx.public_dir}/posts.json` }) -test('compiles project', t => { - t.ok(helpers.file.exists(ctx.index_path)) +test('compiles project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) }) -test('orders data correctly for the project', t => { +test('orders data correctly for the project', async t => { t.plan(4) // titles should be order A before B before C - t.true(helpers.file.contains_match( + t.true(await helpers.file.contains_match( ctx.index_path, - '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$' + '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$', + { async } )) - ctx.bodies.forEach(body => { - t.true(helpers.file.contains(ctx.index_path, body)) - }) + for (let body of ctx.bodies) { + t.true(await helpers.file.contains(ctx.index_path, body, { async })) + } }) -test('has written data as json', t => { - t.ok(helpers.file.exists(ctx.posts_path)) - t.true(helpers.file.matches_file(ctx.posts_path, 'data-manipulation--sort/posts_expected.json')) +test('has written data as json', async t => { + t.ok(await helpers.file.exists(ctx.posts_path, { async })) + t.true(await helpers.file.matches_file(ctx.posts_path, 'data-manipulation--sort/posts_expected.json', { async })) }) test.after(async t => { diff --git a/test/data-manipulation/transform.js b/test/data-manipulation/transform.js index 16cd5fe..2bb5b2d 100644 --- a/test/data-manipulation/transform.js +++ b/test/data-manipulation/transform.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -24,28 +25,29 @@ test.before(async t => { ctx.posts_path = `${ctx.public_dir}/posts.json` }) -test('compiles project', t => { - t.ok(helpers.file.exists(ctx.index_path)) +test('compiles project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) }) -test('does not reorder data', t => { +test('does not reorder data', async t => { // titles should be order A before B before C - t.true(helpers.file.contains_match( + t.true(await helpers.file.contains_match( ctx.index_path, - '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$' + '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$', + { async } )) }) -test('has manipulated data correctly for the project', t => { +test('has manipulated data correctly for the project', async t => { t.plan(3) - ctx.bodies.forEach(body => { - t.false(helpers.file.contains(ctx.index_path, body)) - }) + for (let body of ctx.bodies) { + t.false(await helpers.file.contains(ctx.index_path, body, { async })) + } }) -test('has written data as json', t => { - t.ok(helpers.file.exists(ctx.posts_path)) - t.true(helpers.file.matches_file(ctx.posts_path, 'data-manipulation--transform/posts_expected.json')) +test('has written data as json', async t => { + t.ok(await helpers.file.exists(ctx.posts_path, { async })) + t.true(await helpers.file.matches_file(ctx.posts_path, 'data-manipulation--transform/posts_expected.json', { async })) }) test.after(async t => { diff --git a/test/helpers/index.js b/test/helpers/index.js index 2f9b392..6702a0e 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -15,6 +15,8 @@ Array.prototype.includes = do { } /* eslint-enable */ +export const async = true + export const helpers = new RootsUtil.Helpers({ base: path.join(__dirname, '../fixtures') }) diff --git a/test/single-entry/clear-locals-between-compiles.js b/test/single-entry/clear-locals-between-compiles.js index ae0cb98..dd2f906 100644 --- a/test/single-entry/clear-locals-between-compiles.js +++ b/test/single-entry/clear-locals-between-compiles.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -25,8 +26,8 @@ test.before(async t => { ctx.post_path = `${ctx.public_dir}/blog_posts/totes-mcgotes.html` }) -test("should not have first entry's content in second entry's single view", t => { - t.false(helpers.file.contains(ctx.post_path, ctx.body)) +test("should not have first entry's content in second entry's single view", async t => { + t.false(await helpers.file.contains(ctx.post_path, ctx.body, { async })) }) test.after(async t => { diff --git a/test/single-entry/custom-path-function.js b/test/single-entry/custom-path-function.js index fa2d7ab..a61f231 100644 --- a/test/single-entry/custom-path-function.js +++ b/test/single-entry/custom-path-function.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -24,10 +25,10 @@ test.before(async t => { ctx.post_path = `${ctx.public_dir}/blogging/${ctx.category}/real-talk.html` }) -test('compiles a single entry file using custom path', t => { - t.ok(helpers.file.exists(ctx.post_path)) - t.true(helpers.file.contains(ctx.post_path, ctx.title)) - t.true(helpers.file.contains(ctx.post_path, ctx.body)) +test('compiles a single entry file using custom path', async t => { + t.ok(await helpers.file.exists(ctx.post_path, { async })) + t.true(await helpers.file.contains(ctx.post_path, ctx.title, { async })) + t.true(await helpers.file.contains(ctx.post_path, ctx.body, { async })) }) test.after(async t => { diff --git a/test/single-entry/default-path-function.js b/test/single-entry/default-path-function.js index 5d48297..a9c748a 100644 --- a/test/single-entry/default-path-function.js +++ b/test/single-entry/default-path-function.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -23,19 +24,19 @@ test.before(async t => { ctx.post_path = `${ctx.public_dir}/blog_posts/real-talk.html` }) -test('compiles a single entry file based off the slugified display field', t => { - t.ok(helpers.file.exists(ctx.post_path)) - t.true(helpers.file.contains(ctx.post_path, ctx.title)) - t.true(helpers.file.contains(ctx.post_path, ctx.body)) +test('compiles a single entry file based off the slugified display field', async t => { + t.ok(await helpers.file.exists(ctx.post_path, { async })) + t.true(await helpers.file.contains(ctx.post_path, ctx.title, { async })) + t.true(await helpers.file.contains(ctx.post_path, ctx.body, { async })) }) -test('has access to other roots locals inside the single entry view', t => { - t.true(helpers.file.contains(ctx.post_path, 'such local')) +test('has access to other roots locals inside the single entry view', async t => { + t.true(await helpers.file.contains(ctx.post_path, 'such local', { async })) }) -test('sets a _url attribute to allow links to each entry', t => { - t.ok(helpers.file.exists(ctx.index_path)) - t.true(helpers.file.contains(ctx.index_path, '/blog_posts/real-talk.html')) +test('sets a _url attribute to allow links to each entry', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) + t.true(await helpers.file.contains(ctx.index_path, '/blog_posts/real-talk.html', { async })) }) test.after(async t => { diff --git a/test/single-entry/image-view-helper.js b/test/single-entry/image-view-helper.js index 445aa2e..c7383a6 100644 --- a/test/single-entry/image-view-helper.js +++ b/test/single-entry/image-view-helper.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -31,12 +32,12 @@ test.before(async t => { ctx.index_path = `${ctx.public_dir}/index.html` }) -test('renders out image path', t => { - t.true(helpers.file.contains(ctx.index_path, `${ctx.regular_img_path}`)) +test('renders out image path', async t => { + t.true(await helpers.file.contains(ctx.index_path, `${ctx.regular_img_path}`, { async })) }) -test('adds query string params to the image', t => { - t.true(helpers.file.contains(ctx.index_path, `${ctx.query_img_path}?w=100&h=100`)) +test('adds query string params to the image', async t => { + t.true(await helpers.file.contains(ctx.index_path, `${ctx.query_img_path}?w=100&h=100`, { async })) }) test.after(async t => { diff --git a/test/single-entry/multi-path-function.js b/test/single-entry/multi-path-function.js index 941b20e..25e4d21 100644 --- a/test/single-entry/multi-path-function.js +++ b/test/single-entry/multi-path-function.js @@ -2,6 +2,7 @@ import path from 'path' import slugify from 'underscore.string/slugify' import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -27,27 +28,27 @@ test.before(async t => { ctx.index_path = `${ctx.public_dir}/index.html` }) -test('compiles a single entry to multiple files', t => { +test('compiles a single entry to multiple files', async t => { t.plan(16) - ctx.langs.forEach(lang => { - ctx.titles.forEach((title, i) => { + for (let lang of ctx.langs) { + for (let title of ctx.titles) { const output = `/${lang}/${slugify(title)}.html` const post_path = path.join(ctx.public_dir, output) - t.ok(helpers.file.exists(post_path)) - t.true(helpers.file.contains(post_path, title)) - t.true(helpers.file.contains(post_path, ctx.bodies[i])) - t.true(helpers.file.contains(post_path, `

${output}

`)) - }) - }) + t.ok(await helpers.file.exists(post_path, { async })) + t.true(await helpers.file.contains(post_path, title, { async })) + t.true(await helpers.file.contains(post_path, ctx.bodies[ctx.titles.indexOf(title)], { async })) + t.true(await helpers.file.contains(post_path, `

${output}

`, { async })) + } + } }) -test("sets _urls attribute to all of the entry's compiled files", t => { +test("sets _urls attribute to all of the entry's compiled files", async t => { t.plan(4) - ctx.langs.forEach(lang => { - ctx.titles.forEach(title => { - t.true(helpers.file.contains(ctx.index_path, `/${lang}/${slugify(title)}.html`)) - }) - }) + for (let lang of ctx.langs) { + for (let title of ctx.titles) { + t.true(await helpers.file.contains(ctx.index_path, `/${lang}/${slugify(title)}.html`, { async })) + } + } }) test.after(async t => { diff --git a/test/write.js b/test/write.js index 348db78..9d3a281 100644 --- a/test/write.js +++ b/test/write.js @@ -1,5 +1,6 @@ import test from 'ava' import { + async, helpers, mock_contentful, unmock_contentful, @@ -22,14 +23,14 @@ test.before(async t => { ctx.posts_path = `${ctx.public_dir}/posts.json` }) -test('compiles project', t => { - t.ok(helpers.file.exists(ctx.index_path)) +test('compiles project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) }) -test('has written data as json', t => { - t.ok(helpers.file.exists(ctx.posts_path)) - t.true(helpers.file.contains(ctx.posts_path, ctx.title)) - t.true(helpers.file.contains(ctx.posts_path, ctx.body)) +test('has written data as json', async t => { + t.ok(await helpers.file.exists(ctx.posts_path, { async })) + t.true(await helpers.file.contains(ctx.posts_path, ctx.title, { async })) + t.true(await helpers.file.contains(ctx.posts_path, ctx.body, { async })) }) test.after(async t => { From 37bfd6c5c9d30e515217bb67d3384a9a43c454a4 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 20 Jan 2016 14:49:17 +0200 Subject: [PATCH 44/58] enhance CI by caching deps --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3a2448c..79517c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,6 @@ node_js: after_script: - npm run coveralls sudo: false +cache: + directories: + - node_modules From 877d935f2a54a8220d778237d7c74fdaa7adfe89 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 13 Jan 2016 20:58:41 +0200 Subject: [PATCH 45/58] add locales support --- package.json | 6 +- readme.md | 46 ++++++-- src/extension.js | 125 +++++++++++++++++---- src/util/exists.js | 3 + src/util/is-undefined.js | 3 + test/fixtures/locales--multi/app.coffee | 16 +++ test/fixtures/locales--multi/index.jade | 5 + test/fixtures/locales--multi/package.json | 6 + test/fixtures/locales--prefix/app.coffee | 19 ++++ test/fixtures/locales--prefix/klingon.jade | 5 + test/fixtures/locales--prefix/package.json | 6 + test/fixtures/locales--prefix/spanish.jade | 5 + test/fixtures/locales--scope/about.jade | 1 + test/fixtures/locales--scope/app.coffee | 17 +++ test/fixtures/locales--scope/index.jade | 5 + test/fixtures/locales--scope/package.json | 6 + test/fixtures/locales--setup/about.jade | 1 + test/fixtures/locales--setup/app.coffee | 16 +++ test/fixtures/locales--setup/index.jade | 5 + test/fixtures/locales--setup/package.json | 6 + test/fixtures/locales--single/about.jade | 1 + test/fixtures/locales--single/app.coffee | 16 +++ test/fixtures/locales--single/index.jade | 5 + test/fixtures/locales--single/package.json | 6 + test/helpers/index.js | 27 ++++- test/locales/multi.js | 56 +++++++++ test/locales/prefix.js | 60 ++++++++++ test/locales/scope.js | 57 ++++++++++ test/locales/setup.js | 59 ++++++++++ test/locales/single.js | 55 +++++++++ 30 files changed, 611 insertions(+), 33 deletions(-) create mode 100644 src/util/exists.js create mode 100644 src/util/is-undefined.js create mode 100644 test/fixtures/locales--multi/app.coffee create mode 100644 test/fixtures/locales--multi/index.jade create mode 100644 test/fixtures/locales--multi/package.json create mode 100644 test/fixtures/locales--prefix/app.coffee create mode 100644 test/fixtures/locales--prefix/klingon.jade create mode 100644 test/fixtures/locales--prefix/package.json create mode 100644 test/fixtures/locales--prefix/spanish.jade create mode 100644 test/fixtures/locales--scope/about.jade create mode 100644 test/fixtures/locales--scope/app.coffee create mode 100644 test/fixtures/locales--scope/index.jade create mode 100644 test/fixtures/locales--scope/package.json create mode 100644 test/fixtures/locales--setup/about.jade create mode 100644 test/fixtures/locales--setup/app.coffee create mode 100644 test/fixtures/locales--setup/index.jade create mode 100644 test/fixtures/locales--setup/package.json create mode 100644 test/fixtures/locales--single/about.jade create mode 100644 test/fixtures/locales--single/app.coffee create mode 100644 test/fixtures/locales--single/index.jade create mode 100644 test/fixtures/locales--single/package.json create mode 100644 test/locales/multi.js create mode 100644 test/locales/prefix.js create mode 100644 test/locales/scope.js create mode 100644 test/locales/setup.js create mode 100644 test/locales/single.js diff --git a/package.json b/package.json index 5d2cc6e..acc8311 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dependencies": { "babel-runtime": "^6.3.13", "contentful": "^2.1.0", + "deepcopy": "^0.6.1", "pluralize": "^1.2.1", "roots-util": "0.2.x", "underscore.string": "^3.2.2" @@ -78,6 +79,9 @@ }, "standard": { "parser": "babel-eslint", - "ignore": ["test/_setup.js", "test/_teardown.js"] + "ignore": [ + "test/_setup.js", + "test/_teardown.js" + ] } } diff --git a/readme.md b/readme.md index 31366dc..75a1d62 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,9 @@ module.exports = contentful access_token: 'YOUR_ACCESS_TOKEN' space_id: 'xxxxxx' + locale: 'tlh' + locales_prefix: + tlh: 'klingon_' content_types: blog_posts: id: 'xxxxxx' @@ -76,15 +79,17 @@ If a `template` option is defined for a Content Type in `app.coffee`, roots will Contentful's [documentation](https://www.contentful.com/developers/documentation/content-delivery-api/#getting-entry) shows the API response when fetching an entry. Your content fields are nested in a `fields` key on the `entry` object. As a convenience, the entry object roots-contentful makes available in your views will have the `fields` key's value set one level higher on the object. System metadata remains accessible on the `sys` key and roots-contentful will raise an error if you have a field named `sys`. Inside your views, the entry object will have this structure: -```json -"entry": { - "title": "Wow. Such title. Much viral", - "author": "The Doge of Venice" - # ... the rest of the fields - "sys": { - "type": "Entry", - "id": "cat" - # ... +```js +{ + "entry": { + "title": "Wow. Such title. Much viral", + "author": "The Doge of Venice" + // ... the rest of the fields + "sys": { + "type": "Entry", + "id": "cat" + # ... + } } } ``` @@ -109,6 +114,29 @@ Required. The space ID containing the content you wish to retrieve. Optional. (Boolean) Allows you use the Contentful Preview API. Also able to be accessed by setting the environment variable `CONTENTFUL_ENV` to `"develop"` (preview api) or `"production"` (default cdn). +#### locales +Locales allow you to request your content in a different language, `tlh` is Klingon. + +##### Global locale +Optional. (String or Array) Defines locale for all content_types. +String: `'tlh'` +Array: `['en-es', 'tlh']` +Wildcard: `'*'` - grabs all locales from contentful + +##### content_type specific locale +Optional. (String) Define content_types locale, will override global locale. Add `locale: 'tlh'` to any of your content types and you'll retrieve that post in the specific locale. + +#### locales_prefix +Optional. (Object) Defines the prefix given to a group of locales. + +``` +locales_prefix: + 'tlh': 'klingon_' + 'en-es': 'spanish_' +``` + +Lets say you have 3 global locales defined, `['tlh', 'en-us', 'en-es']`, and above is our defined locales_prefix. Since we did not declare `'en-us'` you can access it with `contentful.en_us_blog_posts`. Similarly you can access each preix according to what you've set in the object above. `'tlh'` would be accessible by `contentful.klingon_blog_posts` and `en-es` by `contentful.spanish_blog_posts`. + #### content_types An object whose key-value pairs correspond to a Contentful Content Types. Each diff --git a/src/extension.js b/src/extension.js index 448d49d..73e5168 100644 --- a/src/extension.js +++ b/src/extension.js @@ -2,12 +2,15 @@ import path from 'path' import querystring from 'querystring' import contentful from 'contentful' import pluralize from 'pluralize' +import deepcopy from 'deepcopy' import slugify from 'underscore.string/slugify' import underscored from 'underscore.string/underscored' import RootsUtil from 'roots-util' import errors from './errors' import hosts from './hosts' import is_plain_object from './util/is-plain-object' +import exists from './util/exists' +import isUndefined from './util/is-undefined' let client = null // init contentful client @@ -55,13 +58,13 @@ export default class RootsContentful { * @return {Promise} an array for the sorted contentful data */ async setup () { - const { opts: { cache, content_types } } = this + const { opts, opts: { cache } } = this let locals = this.roots.config.locals.contentful // return cached locals if possible if (cache && Object.keys(locals).length) { return locals } - let configuration = await configure_content(content_types) + let configuration = await configure_content(opts) let content = await get_all_content(configuration) await set_urls(content) let entries = await transform_entries(content) @@ -80,23 +83,83 @@ export default class RootsContentful { * @param {Array} types - content_types set in app.coffee extension config * @return {Promise} - returns an array of configured content types */ -async function configure_content (types) { - if (is_plain_object(types)) { - types = convert_types_to_array(types) +async function configure_content (opts) { + let types = opts.content_types + let locales = opts.locale + let locale_prefixes = opts.locales_prefix + let global_locale + + async function isWildcard () { + if (locales === '*') { + locales = await fetch_all_locales() + } } - return types.map(async type => { - const { id, name, filters, template, path } = type - if (!id) throw new Error(errors.no_type_id) - type.filters = filters || {} - if (!name || (template && !path)) { - let content_type = await client.contentType(id) - type.name = name || pluralize(underscored(content_type.name)) - if (template) { - type.path = path || (entry => `${name}/${slugify(entry[content_type.displayField])}`) + + function reconfigureObj () { + if (is_plain_object(types)) { + types = convert_types_to_array(types) + return types + } + } + + function localesArray () { + if (Array.isArray(locales)) { + for (let locale of locales) { + for (let type of types) { + let ref + if (type.locale == null) { + let tmp = deepcopy(type) + tmp.locale = locale + tmp.prefix = (ref = locale_prefixes != null ? locale_prefixes[locale] : void 0) != null ? ref : (locale.replace(/-/, '_')) + '_' + types.push(tmp) + } else if (type.prefix == null) { + type.prefix = (ref = locale_prefixes != null ? locale_prefixes[locale] : void 0) != null ? ref : (locale.replace(/-/, '_')) + '_' + } + } } + types = types.filter(type => type.locale != null) + return types + } else if (typeof locales === 'string') { + global_locale = true + return global_locale } - return type - }) + } + + await isWildcard() + reconfigureObj() + localesArray() + let localized_types = [] + + for (let type of types) { + if (!type.id) { + throw new Error(errors.no_type_id) + } + if (type.filters == null) { + type.filters = {} + } + if (!type.name || (type.template && !type.path)) { + let content_type = await client.contentType(type.id) + if (type.name == null) { + type.name = pluralize(underscored(content_type.name)).toLowerCase() + } + if (!isUndefined(locale_prefixes)) { + type.name = type.prefix + type.name + } + if (type.template || (locale_prefixes != null)) { + if (type.path == null) { + type.path = entry => `${type.name}/${slugify(entry[content_type.displayField])}` + } + } + } else if (!isUndefined(locale_prefixes)) { + type.name = type.prefix + type.name + } + if (exists(global_locale)) { + type.locale || (type.locale = opts.locale) + } + localized_types.push(Promise.resolve(type)) + } + + return await Promise.all(localized_types) } /** @@ -106,10 +169,11 @@ async function configure_content (types) { * @return {Promise} - returns an array of content types */ function convert_types_to_array (types) { - return Object.keys(types).reduce((results, key) => { + types = Object.keys(types).reduce((results, key) => { results.push({ ...types[key], name: key }) return results }, []) + return types } /** @@ -131,15 +195,26 @@ async function get_all_content (types) { * @param {Object} type - content type object * @return {Promise} - returns response from Contentful API */ -async function fetch_content ({ id, filters }) { +async function fetch_content ({ id, filters, locale }) { let entries = await client.entries({ ...filters, content_type: id, - include: 10 + include: 10, + locale }) return entries } +/** + * Fetch all locales in space + * Used when `*` is used in opts.locales + * @return {Array} locales + */ +async function fetch_all_locales () { + let res = await client.space() + return res.locales.map(locale => locale.code) +} + /** * Formats raw response from Contentful * @param {Object} content - entries API response for a content type @@ -160,6 +235,9 @@ function format_entry (entry) { throw new Error(errors.sys_conflict) } let formatted = { ...entry, ...entry.fields } + if (formatted.sys != null) { + delete formatted.sys + } delete formatted.fields return formatted } @@ -194,10 +272,15 @@ async function set_urls (types) { * @return {Promise} - promise for when complete */ async function set_locals (types) { + let contentful = this.roots.config.locals.contentful types = await Promise.all(types) return types.map(({ name, content }) => { - this.roots.config.locals.contentful[name] = content - return content + if (contentful[name]) { + contentful[name].push(content[0]) + } else { + contentful[name] = content + } + return contentful[name] }) } diff --git a/src/util/exists.js b/src/util/exists.js new file mode 100644 index 0000000..7c6b41d --- /dev/null +++ b/src/util/exists.js @@ -0,0 +1,3 @@ +export default function exists (thing) { + return typeof thing !== 'undefined' && thing !== null +} diff --git a/src/util/is-undefined.js b/src/util/is-undefined.js new file mode 100644 index 0000000..699841a --- /dev/null +++ b/src/util/is-undefined.js @@ -0,0 +1,3 @@ +export default function isUndefined (thing) { + return thing === void 0 +} diff --git a/test/fixtures/locales--multi/app.coffee b/test/fixtures/locales--multi/app.coffee new file mode 100644 index 0000000..b5f936b --- /dev/null +++ b/test/fixtures/locales--multi/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['en-es', 'tlh'] + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locales--multi/index.jade b/test/fixtures/locales--multi/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locales--multi/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locales--multi/package.json b/test/fixtures/locales--multi/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locales--multi/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locales--prefix/app.coffee b/test/fixtures/locales--prefix/app.coffee new file mode 100644 index 0000000..ac0fba9 --- /dev/null +++ b/test/fixtures/locales--prefix/app.coffee @@ -0,0 +1,19 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['en-es', 'tlh'] + locales_prefix: { + 'tlh': 'klingon_' + } + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locales--prefix/klingon.jade b/test/fixtures/locales--prefix/klingon.jade new file mode 100644 index 0000000..9c7fe36 --- /dev/null +++ b/test/fixtures/locales--prefix/klingon.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.klingon_blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locales--prefix/package.json b/test/fixtures/locales--prefix/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locales--prefix/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locales--prefix/spanish.jade b/test/fixtures/locales--prefix/spanish.jade new file mode 100644 index 0000000..620ac70 --- /dev/null +++ b/test/fixtures/locales--prefix/spanish.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.en_es_blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locales--scope/about.jade b/test/fixtures/locales--scope/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/locales--scope/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/locales--scope/app.coffee b/test/fixtures/locales--scope/app.coffee new file mode 100644 index 0000000..3e0eb91 --- /dev/null +++ b/test/fixtures/locales--scope/app.coffee @@ -0,0 +1,17 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['tlh'] + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + locale: 'en-es' + } + ] + ) + ] diff --git a/test/fixtures/locales--scope/index.jade b/test/fixtures/locales--scope/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locales--scope/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locales--scope/package.json b/test/fixtures/locales--scope/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locales--scope/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locales--setup/about.jade b/test/fixtures/locales--setup/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/locales--setup/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/locales--setup/app.coffee b/test/fixtures/locales--setup/app.coffee new file mode 100644 index 0000000..e59b2a7 --- /dev/null +++ b/test/fixtures/locales--setup/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: '*' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locales--setup/index.jade b/test/fixtures/locales--setup/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locales--setup/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locales--setup/package.json b/test/fixtures/locales--setup/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locales--setup/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locales--single/about.jade b/test/fixtures/locales--single/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/locales--single/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/locales--single/app.coffee b/test/fixtures/locales--single/app.coffee new file mode 100644 index 0000000..49f595c --- /dev/null +++ b/test/fixtures/locales--single/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['tlh'] + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locales--single/index.jade b/test/fixtures/locales--single/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locales--single/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locales--single/package.json b/test/fixtures/locales--single/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locales--single/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/helpers/index.js b/test/helpers/index.js index 6702a0e..6ece651 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -39,11 +39,26 @@ export function mock_contentful (opts = {}) { opts = { entries: [{ sys: { sys: 'data' }, + locale: 'Default Locale', fields: { title: 'Default Title', body: 'Default Body' } }], + space: { + sys: { + type: 'Space', + id: 'cfexampleapi' + }, + name: 'Contentful Example API', + locales: [{ + code: 'en-US', + name: 'English' + }, { + code: 'tlh', + name: 'Klingon' + }] + }, content_type: { name: 'Blog Post', displayField: 'title' @@ -56,8 +71,16 @@ export function mock_contentful (opts = {}) { contentType () { return Promise.resolve(opts.content_type) }, - entries () { - return Promise.resolve(opts.entries) + space () { + return Promise.resolve(opts.space) + }, + entries (req) { + if (req.locale == null) { + return Promise.resolve(opts.entries) + } + return Promise.resolve(opts.entries.filter( + entry => entry.sys.locale === req.locale + )) } } } diff --git a/test/locales/multi.js b/test/locales/multi.js new file mode 100644 index 0000000..40195ef --- /dev/null +++ b/test/locales/multi.js @@ -0,0 +1,56 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +test.before(async t => { + let title = ['Throw Some Ds', "'op Ds chuH", "arrojar algo de Ds'"] + let body = [ + 'Rich boy selling crack', + "mIp loDHom ngev pe'vIl vaj pumDI' qoghlIj", + 'Niño rico venta de crack' + ] + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title: title[0], body: body[0] }, + sys: { locale: 'en-US' } + }, { + fields: { title: title[1], body: body[1] }, + sys: { locale: 'tlh' } + }, { + fields: { title: title[2], body: body[2] }, + sys: { locale: 'en-es' } + }], + space: { + locales: [{ + code: 'en-US', + name: 'English' + }, { + code: 'tlh', + name: 'Klingon' + }, { + code: 'en-es', + name: 'Spanish' + }] + } + }) + await ctx::compile_fixture('locales--multi') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('should render an array of global locales', async t => { + t.false(await helpers.file.contains(ctx.index_path, ctx.title[0], { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.title[1], { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.title[2], { async })) +}) + +test.after(async t => { + unmock_contentful() +}) diff --git a/test/locales/prefix.js b/test/locales/prefix.js new file mode 100644 index 0000000..ab1e13e --- /dev/null +++ b/test/locales/prefix.js @@ -0,0 +1,60 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +test.before(async t => { + let title = ['Throw Some Ds', "'op Ds chuH", "arrojar algo de Ds'"] + let body = [ + 'Rich boy selling crack', + "mIp loDHom ngev pe'vIl vaj pumDI' qoghlIj", + 'Niño rico venta de crack' + ] + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title: title[0], body: body[0] }, + sys: { locale: 'en-US' } + }, { + fields: { title: title[1], body: body[1] }, + sys: { locale: 'tlh' } + }, { + fields: { title: title[2], body: body[2] }, + sys: { locale: 'en-es' } + }], + space: { + locales: [{ + code: 'en-US', + name: 'English' + }, { + code: 'tlh', + name: 'Klingon' + }, { + code: 'en-es', + name: 'Spanish' + }] + } + }) + await ctx::compile_fixture('locales--prefix') + ctx.klingon = `${ctx.public_dir}/klingon.html` + ctx.spanish = `${ctx.public_dir}/spanish.html` +}) + +test('should render the content type locale, not the global', async t => { + t.true(await helpers.file.contains(ctx.klingon, ctx.title[1], { async })) + t.true(await helpers.file.contains(ctx.klingon, ctx.body[1], { async })) + t.false(await helpers.file.contains(ctx.klingon, ctx.body[2], { async })) + t.true(await helpers.file.contains(ctx.spanish, ctx.title[2], { async })) + t.true(await helpers.file.contains(ctx.spanish, ctx.body[2], { async })) + t.false(await helpers.file.contains(ctx.spanish, ctx.body[1], { async })) +}) + +test.after(async t => { + unmock_contentful() +}) diff --git a/test/locales/scope.js b/test/locales/scope.js new file mode 100644 index 0000000..64adc73 --- /dev/null +++ b/test/locales/scope.js @@ -0,0 +1,57 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +test.before(async t => { + let title = ['Throw Some Ds', "'op Ds chuH", "arrojar algo de Ds'"] + let body = [ + 'Rich boy selling crack', + "mIp loDHom ngev pe'vIl vaj pumDI' qoghlIj", + 'Niño rico venta de crack' + ] + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title: title[0], body: body[0] }, + sys: { locale: 'en-US' } + }, { + fields: { title: title[1], body: body[1] }, + sys: { locale: 'tlh' } + }, { + fields: { title: title[2], body: body[2] }, + sys: { locale: 'en-es' } + }], + space: { + locales: [{ + code: 'en-US', + name: 'English' + }, { + code: 'tlh', + name: 'Klingon' + }, { + code: 'en-es', + name: 'Spanish' + }] + } + }) + await ctx::compile_fixture('locales--scope') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('should render the content type locale, not the global', async t => { + t.false(await helpers.file.contains(ctx.index_path, ctx.title[1], { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.title[2], { async })) + t.false(await helpers.file.contains(ctx.index_path, ctx.body[1], { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.body[2], { async })) +}) + +test.after(async t => { + unmock_contentful() +}) diff --git a/test/locales/setup.js b/test/locales/setup.js new file mode 100644 index 0000000..6be4970 --- /dev/null +++ b/test/locales/setup.js @@ -0,0 +1,59 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +test.before(async t => { + let title = ['Throw Some Ds', "'op Ds chuH", "arrojar algo de Ds'"] + let body = [ + 'Rich boy selling crack', + "mIp loDHom ngev pe'vIl vaj pumDI' qoghlIj", + 'Niño rico venta de crack' + ] + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title: title[0], body: body[0] }, + sys: { locale: 'en-US' } + }, { + fields: { title: title[1], body: body[1] }, + sys: { locale: 'tlh' } + }, { + fields: { title: title[2], body: body[2] }, + sys: { locale: 'en-es' } + }], + space: { + locales: [{ + code: 'en-US', + name: 'English' + }, { + code: 'tlh', + name: 'Klingon' + }, { + code: 'en-es', + name: 'Spanish' + }] + } + }) + await ctx::compile_fixture('locales--setup') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('should fetch all locales from * wildcard', async t => { + t.plan(6) + for (let title of ctx.title) { + let body = ctx.body[ctx.title.indexOf(title)] + t.true(await helpers.file.contains(ctx.index_path, title, { async })) + t.true(await helpers.file.contains(ctx.index_path, body, { async })) + } +}) + +test.after(async t => { + unmock_contentful() +}) diff --git a/test/locales/single.js b/test/locales/single.js new file mode 100644 index 0000000..80c98f2 --- /dev/null +++ b/test/locales/single.js @@ -0,0 +1,55 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +test.before(async t => { + let title = ['Throw Some Ds', "'op Ds chuH", "arrojar algo de Ds'"] + let body = [ + 'Rich boy selling crack', + "mIp loDHom ngev pe'vIl vaj pumDI' qoghlIj", + 'Niño rico venta de crack' + ] + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title: title[0], body: body[0] }, + sys: { locale: 'en-US' } + }, { + fields: { title: title[1], body: body[1] }, + sys: { locale: 'tlh' } + }, { + fields: { title: title[2], body: body[2] }, + sys: { locale: 'en-es' } + }], + space: { + locales: [{ + code: 'en-US', + name: 'English' + }, { + code: 'tlh', + name: 'Klingon' + }, { + code: 'en-es', + name: 'Spanish' + }] + } + }) + await ctx::compile_fixture('locales--single') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('should render a single global', async t => { + t.false(await helpers.file.contains(ctx.index_path, ctx.title[0], { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.title[1], { async })) +}) + +test.after(async t => { + unmock_contentful() +}) From 77991a73074ffc7682917940d43a48a7412e69d0 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 12:10:58 +0200 Subject: [PATCH 46/58] performance improvements --- src/extension.js | 65 +++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/src/extension.js b/src/extension.js index 73e5168..8559633 100644 --- a/src/extension.js +++ b/src/extension.js @@ -87,49 +87,42 @@ async function configure_content (opts) { let types = opts.content_types let locales = opts.locale let locale_prefixes = opts.locales_prefix + let localized_types = [] + let _types = [] let global_locale - async function isWildcard () { - if (locales === '*') { - locales = await fetch_all_locales() - } + if (locales === '*') { + locales = await fetch_all_locales() } - function reconfigureObj () { - if (is_plain_object(types)) { - types = convert_types_to_array(types) - return types - } + if (is_plain_object(types)) { + types = convert_types_to_array(types) } - function localesArray () { - if (Array.isArray(locales)) { - for (let locale of locales) { - for (let type of types) { - let ref - if (type.locale == null) { - let tmp = deepcopy(type) - tmp.locale = locale - tmp.prefix = (ref = locale_prefixes != null ? locale_prefixes[locale] : void 0) != null ? ref : (locale.replace(/-/, '_')) + '_' - types.push(tmp) - } else if (type.prefix == null) { - type.prefix = (ref = locale_prefixes != null ? locale_prefixes[locale] : void 0) != null ? ref : (locale.replace(/-/, '_')) + '_' - } + if (Array.isArray(locales)) { + for (let locale of locales) { + let existing_prefix = locale_prefixes != null + ? locale_prefixes[locale] + : null + let prefix = existing_prefix || `${underscored(locale)}_` + + for (let type of types) { + if (type.locale == null) { + let tmp = deepcopy(type) + tmp.locale = locale + tmp.prefix = prefix + _types.push(tmp) + } else if (type.prefix == null) { + type.prefix = prefix + _types.push(type) } } - types = types.filter(type => type.locale != null) - return types - } else if (typeof locales === 'string') { - global_locale = true - return global_locale } + types = _types + } else if (typeof locales === 'string') { + global_locale = true } - await isWildcard() - reconfigureObj() - localesArray() - let localized_types = [] - for (let type of types) { if (!type.id) { throw new Error(errors.no_type_id) @@ -183,11 +176,11 @@ function convert_types_to_array (types) { */ async function get_all_content (types) { types = await Promise.all(types) - return types.map(async type => { - let content = await fetch_content(type) + for (const type of types) { + const content = await fetch_content(type) type.content = await format_content(content) - return type - }) + } + return types } /** From 4d060746c4333dfe2ab506b2888e61a8e5215b88 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 16:16:00 +0200 Subject: [PATCH 47/58] make tests temporarily serial, upgrade deps --- package.json | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index acc8311..f9a8433 100644 --- a/package.json +++ b/package.json @@ -3,32 +3,41 @@ "description": "An extension for using roots with the API-driven Contentful CMS", "version": "0.0.9", "author": "Carrot Creative", + "ava": { + "serial": true, + "verbose": true, + "require": [ + "babel-core/register", + "coffee-script/register" + ] + }, "bugs": { "url": "https://github.com/carrot/roots-contentful/issues" }, "dependencies": { + "ava": "^0.9.2", "babel-runtime": "^6.3.13", - "contentful": "^2.1.0", + "contentful": "^2.1.1", "deepcopy": "^0.6.1", "pluralize": "^1.2.1", "roots-util": "0.2.x", - "underscore.string": "^3.2.2" + "underscore.string": "^3.2.3" }, "devDependencies": { - "ava": "^0.9.1", - "babel-cli": "^6.3.15", - "babel-core": "^6.3.26", + "ava": "^0.10.0", + "babel-cli": "^6.4.5", + "babel-core": "^6.4.5", "babel-eslint": "^5.0.0-beta6", - "babel-plugin-add-module-exports": "^0.1.2", - "babel-plugin-transform-runtime": "^6.3.13", - "babel-preset-es2015-node5": "^1.1.1", + "babel-plugin-add-module-exports": "^0.1.3-alpha", + "babel-plugin-transform-runtime": "^6.4.3", + "babel-preset-es2015-node5": "^1.1.2", "babel-preset-stage-0": "^6.3.13", "babel-runtime": "^6.3.19", "coffee-script": "^1.10.0", "coveralls": "^2.11.6", "husky": "^0.10.2", "mockery": "1.4.x", - "nyc": "^5.2.0", + "nyc": "^5.3.0", "roots": "3.1.0", "snazzy": "^2.0.1", "standard": "^5.4.1" @@ -64,7 +73,7 @@ "build": "babel src -d lib", "coverage": "nyc report --reporter=lcov", "coveralls": "coveralls < coverage/lcov.info", - "debug-test": "NODE_DEBUG=request npm run test -- --serial --verbose --fail-fast", + "debug-test": "npm run test -- --fail-fast", "lint": "standard --verbose | snazzy", "postpublish": "git push --follow-tags", "posttest": "node test/_teardown.js", @@ -75,7 +84,7 @@ "prerelease": "npm run build", "pretest": "npm run lint -s && node test/_setup.js", "release": "npm publish", - "test": "nyc ava --require babel-core/register --require coffee-script/register" + "test": "nyc ava --verbose --serial --require babel-core/register --require coffee-script/register" }, "standard": { "parser": "babel-eslint", From 6ad710a2f152c5c201e0b3c7a54c1d9f43289aaf Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 16:39:30 +0200 Subject: [PATCH 48/58] update inline documentation --- src/extension.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/extension.js b/src/extension.js index 8559633..f2db971 100644 --- a/src/extension.js +++ b/src/extension.js @@ -80,39 +80,53 @@ export default class RootsContentful { /** * Configures content types set in app.coffee. Sets default values if * optional config options are missing. - * @param {Array} types - content_types set in app.coffee extension config + * @param {Object} opts - app.coffee extension config * @return {Promise} - returns an array of configured content types */ async function configure_content (opts) { let types = opts.content_types let locales = opts.locale let locale_prefixes = opts.locales_prefix - let localized_types = [] + // consumes types after adding locale and prefixes to types let _types = [] + // consumes types after adding type paths to types + // & locale prefixes to type names + let localized_types = [] let global_locale + // if locales is wildcard, fetch & set locales if (locales === '*') { locales = await fetch_all_locales() } + // converts type config to an array if + // it is specified as an object if (is_plain_object(types)) { types = convert_types_to_array(types) } + // update types to contain locale data + // and prefixes (null checks === ಠ_ಠ) if (Array.isArray(locales)) { for (let locale of locales) { + // if locale_prefixes is defined... let existing_prefix = locale_prefixes != null + // set prefix as locale_prefixes[locale] + // if it exists else... ? locale_prefixes[locale] : null + // ...set prefix as underscored locale let prefix = existing_prefix || `${underscored(locale)}_` for (let type of types) { + // type's locale overrides global locale if (type.locale == null) { let tmp = deepcopy(type) tmp.locale = locale tmp.prefix = prefix _types.push(tmp) } else if (type.prefix == null) { + // set prefix, only if it isn't set type.prefix = prefix _types.push(type) } @@ -123,6 +137,9 @@ async function configure_content (opts) { global_locale = true } + // validate type ids, set type paths + // and type names, possibly including + // type locale prefixes in type names for (let type of types) { if (!type.id) { throw new Error(errors.no_type_id) From 668887ab5957fcd44fec54c605819d276480dd8d Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 16:45:19 +0200 Subject: [PATCH 49/58] remove redundant test command from travis build pipeline --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f9a8433..06c4479 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "prebuild": "npm test", "precommit": "npm run lint -s", "precoverage": "npm run test", - "precoveralls": "npm run coverage", + "precoveralls": "nyc report --reporter=text-lcov", "prerelease": "npm run build", "pretest": "npm run lint -s && node test/_setup.js", "release": "npm publish", From e93577e2385b0538ddd93ada7b9eb24d45a64260 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 17:13:08 +0200 Subject: [PATCH 50/58] ensure context, switch instrumentation to src --- .istanbul.yml | 2 +- src/extension.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.istanbul.yml b/.istanbul.yml index 7178a27..7435884 100644 --- a/.istanbul.yml +++ b/.istanbul.yml @@ -1,2 +1,2 @@ instrumentation: - root: lib + root: src diff --git a/src/extension.js b/src/extension.js index f2db971..617a58b 100644 --- a/src/extension.js +++ b/src/extension.js @@ -64,11 +64,11 @@ export default class RootsContentful { if (cache && Object.keys(locals).length) { return locals } - let configuration = await configure_content(opts) - let content = await get_all_content(configuration) - await set_urls(content) - let entries = await transform_entries(content) - let sorted = await sort_entries(entries) + let configuration = await this::configure_content(opts) + let content = await this::get_all_content(configuration) + await this::set_urls(content) + let entries = await this::transform_entries(content) + let sorted = await this::sort_entries(entries) await this::set_locals(sorted) await this::compile_entries(sorted) await this::write_entries(sorted) From 57ef89d8b1350251e5498fd04ca6b36ccf71d09f Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 21:39:41 +0200 Subject: [PATCH 51/58] fix broken coverage --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 06c4479..3e3438c 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "scripts": { "build": "babel src -d lib", "coverage": "nyc report --reporter=lcov", - "coveralls": "coveralls < coverage/lcov.info", + "coveralls": "nyc report --reporter=text-lcov | coveralls", "debug-test": "npm run test -- --fail-fast", "lint": "standard --verbose | snazzy", "postpublish": "git push --follow-tags", @@ -80,7 +80,6 @@ "prebuild": "npm test", "precommit": "npm run lint -s", "precoverage": "npm run test", - "precoveralls": "nyc report --reporter=text-lcov", "prerelease": "npm run build", "pretest": "npm run lint -s && node test/_setup.js", "release": "npm publish", From fef68e1900bd97f991be4cb3b06b13c3b30a1469 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 21:55:37 +0200 Subject: [PATCH 52/58] fix context references in compile_entries --- src/extension.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extension.js b/src/extension.js index 617a58b..09efb0e 100644 --- a/src/extension.js +++ b/src/extension.js @@ -342,10 +342,10 @@ async function compile_entries (types) { return compiler.extensions.includes(path.extname(tpl_path).substring(1)) }) return entry._urls.map(_url => { - this.roots.config.locals.entry = { ...entry, _url } + locals.entry = { ...entry, _url } return compiler.renderFile(tpl_path, locals) .then(compiled => { - this.roots.config.locals.entry = null + locals.entry = null return util.write(_url, compiled.result) }) }) From 61a7fb551f40790d1c5b5b5172d9f71870f8a4b6 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 22:22:22 +0200 Subject: [PATCH 53/58] back-version dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e3438c..150b881 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "babel-cli": "^6.4.5", "babel-core": "^6.4.5", "babel-eslint": "^5.0.0-beta6", - "babel-plugin-add-module-exports": "^0.1.3-alpha", + "babel-plugin-add-module-exports": "^0.1.2", "babel-plugin-transform-runtime": "^6.4.3", "babel-preset-es2015-node5": "^1.1.2", "babel-preset-stage-0": "^6.3.13", From 0c2172fdcddb7ffd4b51fc24838fb8faa70e31e8 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Thu, 21 Jan 2016 22:50:50 +0200 Subject: [PATCH 54/58] improve oneline util so it passes coverage threshold --- src/errors.js | 15 +++++++++------ src/util/oneline.js | 6 ++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/errors.js b/src/errors.js index 099f203..0a67019 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,16 +1,19 @@ import oneline from './util/oneline' export default { - no_token: oneline` + no_token: oneline(` Missing required options for roots-contentful. Please ensure \`access_token\` and \`space_id\` - are present.`, - no_type_id: oneline` + are present. + `), + no_type_id: oneline(` One or more of your content types is missing an - \`id\` value`, - sys_conflict: oneline` + \`id\` value + `), + sys_conflict: oneline(` One of your content types has \`sys\` as a field. This is reserved for storing Contentful system metadata, please rename this field to a different - value.` + value. + `) } diff --git a/src/util/oneline.js b/src/util/oneline.js index 8bcf27e..90496a2 100644 --- a/src/util/oneline.js +++ b/src/util/oneline.js @@ -1,7 +1,5 @@ -function oneline (template, ...expressions) { - return template.reduce((accumulator, part, i) => { - return accumulator + expressions[i - 1] + part - }).replace(/(?:\s+)/g, ' ').trim() +function oneline (str) { + return str.replace(/(?:\s+)/g, ' ').trim() } export default oneline From a8e1a0ff27b6115b0f7ee71d2c0b779b82cc66d0 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Mon, 25 Jan 2016 15:52:06 +0200 Subject: [PATCH 55/58] add test for single entry _path helper --- .../single-entry--path-helper/app.coffee | 20 +++++++++++ .../single-entry--path-helper/index.jade | 4 +++ .../single-entry--path-helper/package.json | 6 ++++ .../views/_blog_post.jade | 4 +++ test/single-entry/path-helper.js | 33 +++++++++++++++++++ 5 files changed, 67 insertions(+) create mode 100644 test/fixtures/single-entry--path-helper/app.coffee create mode 100644 test/fixtures/single-entry--path-helper/index.jade create mode 100644 test/fixtures/single-entry--path-helper/package.json create mode 100644 test/fixtures/single-entry--path-helper/views/_blog_post.jade create mode 100644 test/single-entry/path-helper.js diff --git a/test/fixtures/single-entry--path-helper/app.coffee b/test/fixtures/single-entry--path-helper/app.coffee new file mode 100644 index 0000000..b9d39a8 --- /dev/null +++ b/test/fixtures/single-entry--path-helper/app.coffee @@ -0,0 +1,20 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + name: 'blog_posts' + template: 'views/_blog_post.jade' + } + ] + ) + ] + + locals: + wow: 'such local' diff --git a/test/fixtures/single-entry--path-helper/index.jade b/test/fixtures/single-entry--path-helper/index.jade new file mode 100644 index 0000000..b881d53 --- /dev/null +++ b/test/fixtures/single-entry--path-helper/index.jade @@ -0,0 +1,4 @@ +- for p in contentful.blog_posts + h1= p.title + p= p.body + a(href= p._url) Link diff --git a/test/fixtures/single-entry--path-helper/package.json b/test/fixtures/single-entry--path-helper/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/single-entry--path-helper/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/single-entry--path-helper/views/_blog_post.jade b/test/fixtures/single-entry--path-helper/views/_blog_post.jade new file mode 100644 index 0000000..0cc6653 --- /dev/null +++ b/test/fixtures/single-entry--path-helper/views/_blog_post.jade @@ -0,0 +1,4 @@ +h1= entry.title +p= entry.body +p= wow +p= _path diff --git a/test/single-entry/path-helper.js b/test/single-entry/path-helper.js new file mode 100644 index 0000000..8937704 --- /dev/null +++ b/test/single-entry/path-helper.js @@ -0,0 +1,33 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +test.before(async t => { + let title = 'Wow such doge' + let body = 'such amaze' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [ + { fields: { title, body } } + ], + content_type: { name: 'Blog Post', displayField: 'title' } + }) + await ctx::compile_fixture('single-entry--path-helper') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.post_path = `${ctx.public_dir}/blog_posts/wow-such-doge.html` +}) + +test('should expose _path helper in entries', async t => { + t.true(await helpers.file.contains(ctx.post_path, '/blog_posts/wow-such-doge.html', { async })) +}) + +test.after(async t => { + unmock_contentful() +}) From 474b0a8e6f666f34775301dbd3252e5c7bbf46bd Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Mon, 25 Jan 2016 15:59:16 +0200 Subject: [PATCH 56/58] alias _url local as _path in single entries --- src/extension.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extension.js b/src/extension.js index 09efb0e..3b98f97 100644 --- a/src/extension.js +++ b/src/extension.js @@ -343,9 +343,11 @@ async function compile_entries (types) { }) return entry._urls.map(_url => { locals.entry = { ...entry, _url } + locals._path = _url // alias _url as _path in locals return compiler.renderFile(tpl_path, locals) .then(compiled => { locals.entry = null + locals._path = null return util.write(_url, compiled.result) }) }) From 10fc87ac9bfe56824a93ea31cf45e8db67fbea34 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 27 Jan 2016 15:41:35 +0200 Subject: [PATCH 57/58] add test for caching --- package.json | 3 +- test/basic/compile-cache.js | 88 +++++++++++++++++++ test/fixtures/basic--compile-cache/about.jade | 1 + test/fixtures/basic--compile-cache/app.coffee | 18 ++++ test/fixtures/basic--compile-cache/index.jade | 5 ++ .../basic--compile-cache/package.json | 6 ++ test/fixtures/basic--compile-cache/temp.jade | 1 + test/helpers/index.js | 17 ++++ 8 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 test/basic/compile-cache.js create mode 100644 test/fixtures/basic--compile-cache/about.jade create mode 100644 test/fixtures/basic--compile-cache/app.coffee create mode 100644 test/fixtures/basic--compile-cache/index.jade create mode 100644 test/fixtures/basic--compile-cache/package.json create mode 100644 test/fixtures/basic--compile-cache/temp.jade diff --git a/package.json b/package.json index 150b881..ace7d63 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,11 @@ "babel-runtime": "^6.3.19", "coffee-script": "^1.10.0", "coveralls": "^2.11.6", + "del": "^2.2.0", "husky": "^0.10.2", "mockery": "1.4.x", "nyc": "^5.3.0", + "performance-now": "^0.2.0", "roots": "3.1.0", "snazzy": "^2.0.1", "standard": "^5.4.1" @@ -79,7 +81,6 @@ "posttest": "node test/_teardown.js", "prebuild": "npm test", "precommit": "npm run lint -s", - "precoverage": "npm run test", "prerelease": "npm run build", "pretest": "npm run lint -s && node test/_setup.js", "release": "npm publish", diff --git a/test/basic/compile-cache.js b/test/basic/compile-cache.js new file mode 100644 index 0000000..dec11df --- /dev/null +++ b/test/basic/compile-cache.js @@ -0,0 +1,88 @@ +import fs from 'fs' +import path from 'path' +import test from 'ava' +import del from 'del' +import now from 'performance-now' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + watch_fixture +} from '../helpers' + +const performance = { now } + +async function write_file (_path, content) { + _path = path.join('..', 'fixtures', 'basic--compile-cache', _path) + return await new Promise((resolve, reject) => { + fs.writeFile(_path, content, 'utf8', (error) => { + if (error) { + reject(error) + } else { + resolve(_path) + } + }) + }) +} + +let ctx = {} + +test.before(async t => { + let title = 'Throw Some Ds' + let body = 'Rich Boy selling crick' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) + // watch the fixture directory for changes + ctx.watch = await ctx::watch_fixture('basic--compile-cache') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.temp_file = `${ctx.public_dir}/temp.html` +}) + +// measure performance +test.cb.before(t => { + Promise.resolve(ctx.watch.project) + .then(project => { + project.once('compile', () => { + ctx.first_compile_ms = performance.now() - ctx.first_compile_ms + }) + ctx.first_compile_ms = performance.now() + // write a file to trigger the watcher's compile step + // for the first time + return write_file('temp.jade', 'h1 foo') + .then(path => ctx.temp_file_src = path) + .then(() => project) + }) + .then(project => { + project.once('compile', () => { + ctx.second_compile_ms = performance.now() - ctx.second_compile_ms + t.end() + }) + ctx.second_compile_ms = performance.now() + // delete a file to trigger the watcher's + // compile step a second time + return del(ctx.tmp_file_src, { force: true }) + }) + .catch(t.end) +}) + +test('second compile should be quicker than the first', t => { + t.true(ctx.second_compile_ms < ctx.first_compile_ms) +}) + +test('has contentful data in views', async t => { + t.true(await helpers.file.contains(ctx.index_path, ctx.title, { async })) + t.true(await helpers.file.contains(ctx.index_path, ctx.body, { async })) +}) + +test.after(async t => { + ctx.watch.watcher.close() + if (await helpers.file.exists(ctx.temp_file_src)) { + await del(ctx.tmp_file_src, { force: true }).catch(t.fail) + } + unmock_contentful() +}) diff --git a/test/fixtures/basic--compile-cache/about.jade b/test/fixtures/basic--compile-cache/about.jade new file mode 100644 index 0000000..2763786 --- /dev/null +++ b/test/fixtures/basic--compile-cache/about.jade @@ -0,0 +1 @@ +h1 wow \ No newline at end of file diff --git a/test/fixtures/basic--compile-cache/app.coffee b/test/fixtures/basic--compile-cache/app.coffee new file mode 100644 index 0000000..c2c261c --- /dev/null +++ b/test/fixtures/basic--compile-cache/app.coffee @@ -0,0 +1,18 @@ +contentful = require '../../../src' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + { + id: '7CDlVsacqQc88cmIEGYWMa' + } + ] + ) + ] diff --git a/test/fixtures/basic--compile-cache/index.jade b/test/fixtures/basic--compile-cache/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/basic--compile-cache/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/basic--compile-cache/package.json b/test/fixtures/basic--compile-cache/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/basic--compile-cache/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/basic--compile-cache/temp.jade b/test/fixtures/basic--compile-cache/temp.jade new file mode 100644 index 0000000..fdc543e --- /dev/null +++ b/test/fixtures/basic--compile-cache/temp.jade @@ -0,0 +1 @@ +h1 foo \ No newline at end of file diff --git a/test/helpers/index.js b/test/helpers/index.js index 6ece651..06adc06 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -2,6 +2,7 @@ import path from 'path' import mockery from 'mockery' import Roots from 'roots' import RootsUtil from 'roots-util' +import {EventEmitter} from 'events' // polyfill array includes because of // https://github.com/sindresorhus/ava/issues/263 @@ -26,6 +27,22 @@ export async function compile_fixture (name) { return await helpers.project.compile(Roots, name) } +export async function watch_fixture (name) { + this.public_dir = `${name}/public` + let project = new EventEmitter() + return await new Promise(async (resolve, reject) => { + const watcher = new Roots(path.join(__dirname, '../fixtures', name)) + watcher.on('error', reject) + watcher.on('done', () => { + project.emit('compile') + }) + resolve({ + watcher: await watcher.watch(), + project + }) + }) +} + export function unmock_contentful () { mockery.deregisterAll() return mockery.disable() From 995662d2f3fdc09abc3391cd3e21ceebd4013339 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Wed, 27 Jan 2016 20:07:04 +0200 Subject: [PATCH 58/58] allow overriding of include option --- readme.md | 5 +++++ src/extension.js | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 75a1d62..b0f95e0 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,7 @@ module.exports = contentful access_token: 'YOUR_ACCESS_TOKEN' space_id: 'xxxxxx' + include: 2 locale: 'tlh' locales_prefix: tlh: 'klingon_' @@ -110,6 +111,10 @@ Required. Your Contentful Delivery access token (API key). Required. The space ID containing the content you wish to retrieve. +#### include + +Optional. Sets the level of deepness to which related entries are linked. Maximum is 10. Defaults to 2, as opposed to Contentful's default of 1. + #### preview Optional. (Boolean) Allows you use the Contentful Preview API. Also able to be accessed by setting the environment variable `CONTENTFUL_ENV` to `"develop"` (preview api) or `"production"` (default cdn). diff --git a/src/extension.js b/src/extension.js index 3b98f97..a222f34 100644 --- a/src/extension.js +++ b/src/extension.js @@ -22,6 +22,7 @@ export default class RootsContentful { opts = { /* defaults */ cache: true, + include: 2, /* user-provided */ ...RootsContentful.opts }; @@ -194,7 +195,7 @@ function convert_types_to_array (types) { async function get_all_content (types) { types = await Promise.all(types) for (const type of types) { - const content = await fetch_content(type) + const content = await this::fetch_content(type) type.content = await format_content(content) } return types @@ -209,7 +210,7 @@ async function fetch_content ({ id, filters, locale }) { let entries = await client.entries({ ...filters, content_type: id, - include: 10, + include: this.opts.include, locale }) return entries