diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..1b2a49b --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": ["es2015-node5", "stage-0"], + "plugins": ["add-module-exports", "transform-runtime"], + "sourceMaps": "inline" +} diff --git a/.gitignore b/.gitignore index d851475..885f017 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,16 @@ .DS_Store node_modules coverage -src +lib +logs +*.log +npm-debug.log* +pids +*.pid +*.seed +lib-cov +.grunt +.lock-wscript +build/Release +.nyc_output +test/fixtures/*/public diff --git a/.istanbul.yml b/.istanbul.yml new file mode 100644 index 0000000..7435884 --- /dev/null +++ b/.istanbul.yml @@ -0,0 +1,2 @@ +instrumentation: + root: src diff --git a/.npmignore b/.npmignore index 1e0d924..556ae53 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,9 @@ test src .travis.yml +.istanbul.yml +.editorconfig +.babelrc +wallaby.js Makefile +.nyc_output diff --git a/.travis.yml b/.travis.yml index f5b97b7..79517c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: node_js node_js: - - "0.10" + - "5" after_script: - npm run coveralls sudo: false +cache: + directories: + - node_modules diff --git a/Makefile b/Makefile index 1716e30..7d37720 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,20 @@ 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 +coverage:: + npm run coverage 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 + +lint: + npm run lint + +debug-test: + npm run debug-test + +test:: + npm test diff --git a/contributing.md b/contributing.md index 51a9282..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,15 +14,85 @@ If you are opening an issue about a bug, make sure that you include clear steps - Run `npm install` - Put in work -### Testing +### `pre-commit` Linting -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/)/ +Provided dependencies are installed, `git commit` will +not work unless this project passes a linting check. -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. +### Build Commands + +> **Note:** if your environment does not support `make` utilities, +> replace `make` with `npm run` when you type a build command. + +#### 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 [AVA](https://github.com/sindresorhus/ava). + +To lint the source: + +```shell +$ make lint +``` + +To lint the source and run the tests: + +```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 +``` + +#### Building + +> **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 compile the contents of `src/` into `lib/`. + +```shell +$ make build +``` + +#### Publishing to NPM + +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: + +```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 +``` ### 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 diff --git a/lib/index.coffee b/lib/index.coffee deleted file mode 100644 index c4a713f..0000000 --- a/lib/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 07ae4e5..ace7d63 100644 --- a/package.json +++ b/package.json @@ -3,33 +3,67 @@ "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": { - "contentful": "^1.1.5", - "lodash": "^3.10.1", + "ava": "^0.9.2", + "babel-runtime": "^6.3.13", + "contentful": "^2.1.1", + "deepcopy": "^0.6.1", "pluralize": "^1.2.1", - "roots-util": "0.1.x", - "string": "^3.1.3", - "when": "3.7.x" + "roots-util": "0.2.x", + "underscore.string": "^3.2.3" }, "devDependencies": { - "chai": "3.4.1", - "chai-as-promised": "5.1.0", - "coffee-script": "1.10.0", - "coveralls": "2.x", - "istanbul": "0.4.1", - "mocha": "2.3.4", + "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.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", + "del": "^2.2.0", + "husky": "^0.10.2", "mockery": "1.4.x", - "roots": "3.1.0" + "nyc": "^5.3.0", + "performance-now": "^0.2.0", + "roots": "3.1.0", + "snazzy": "^2.0.1", + "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-extension" + "cms", + "content", + "contentful", + "dynamic", + "extension", + "management", + "plugin", + "roots", + "roots-extension", + "system" ], "license": "MIT", "main": "lib", @@ -38,8 +72,25 @@ "url": "https://github.com/carrot/roots-contentful.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" + "build": "babel src -d lib", + "coverage": "nyc report --reporter=lcov", + "coveralls": "nyc report --reporter=text-lcov | coveralls", + "debug-test": "npm run test -- --fail-fast", + "lint": "standard --verbose | snazzy", + "postpublish": "git push --follow-tags", + "posttest": "node test/_teardown.js", + "prebuild": "npm test", + "precommit": "npm run lint -s", + "prerelease": "npm run build", + "pretest": "npm run lint -s && node test/_setup.js", + "release": "npm publish", + "test": "nyc ava --verbose --serial --require babel-core/register --require coffee-script/register" + }, + "standard": { + "parser": "babel-eslint", + "ignore": [ + "test/_setup.js", + "test/_teardown.js" + ] } } diff --git a/readme.md b/readme.md index 471eed4..b0f95e0 100644 --- a/readme.md +++ b/readme.md @@ -1,13 +1,18 @@ # 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. > **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) +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/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) + ### Why Should You Care? We love static sites. They're fast, resilient, simple, and cheap. @@ -30,6 +35,10 @@ module.exports = contentful access_token: 'YOUR_ACCESS_TOKEN' space_id: 'xxxxxx' + include: 2 + locale: 'tlh' + locales_prefix: + tlh: 'klingon_' content_types: blog_posts: id: 'xxxxxx' @@ -71,15 +80,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" + # ... + } } } ``` @@ -100,10 +111,37 @@ 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). +#### 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 @@ -177,6 +215,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) diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 0000000..0a67019 --- /dev/null +++ b/src/errors.js @@ -0,0 +1,19 @@ +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/src/extension.js b/src/extension.js new file mode 100644 index 0000000..a222f34 --- /dev/null +++ b/src/extension.js @@ -0,0 +1,386 @@ +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 + +/** + * @class RootsContentful + */ +export default class RootsContentful { + + opts = { + /* defaults */ + cache: true, + include: 2, + /* 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 + }) + } + + /** + * Performs asynchronous setup tasks required + * for the extension to work + * @return {Promise} an array for the sorted contentful data + */ + async setup () { + 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 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) + return sorted + } + +} + +/** + * Configures content types set in app.coffee. Sets default values if + * optional config options are missing. + * @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 + // 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) + } + } + } + types = _types + } else if (typeof locales === 'string') { + 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) + } + 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) +} + +/** + * 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 convert_types_to_array (types) { + types = Object.keys(types).reduce((results, key) => { + results.push({ ...types[key], name: key }) + return results + }, []) + return types +} + +/** + * 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 + */ +async function get_all_content (types) { + types = await Promise.all(types) + for (const type of types) { + const content = await this::fetch_content(type) + type.content = await format_content(content) + } + return types +} + +/** + * Fetch entries for a single content type object + * @param {Object} type - content type object + * @return {Promise} - returns response from Contentful API + */ +async function fetch_content ({ id, filters, locale }) { + let entries = await client.entries({ + ...filters, + content_type: id, + include: this.opts.include, + 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 + * @return {Promise} - returns formatted content type entries object + */ +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} entry - single entry object from API response + * @return {Promise} - returns formatted entry object + */ +function format_entry (entry) { + if (entry.fields.sys != null) { + throw new Error(errors.sys_conflict) + } + let formatted = { ...entry, ...entry.fields } + if (formatted.sys != null) { + delete formatted.sys + } + delete formatted.fields + return formatted +} + +/** + * 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 + */ +async function set_urls (types) { + types = await Promise.all(types) + return types.map(({ template, content, path }) => { + if (template) { + return content.map(entry => { + let paths = path(entry) + if (typeof paths === 'string') { + paths = [paths] + } + entry._urls = paths.map(path => `/${path}.html`) + entry._url = entry._urls.length === 1 ? entry._urls[0] : null + return entry._url + }) + } + }) +} + +/** + * Builds locals object from types objects with content + * @param {Array} types - populated content type objects + * @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 }) => { + if (contentful[name]) { + contentful[name].push(content[0]) + } else { + contentful[name] = content + } + return contentful[name] + }) +} + +/** + * 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 + */ +async function transform_entries (types) { + types = await Promise.all(types) + return types.map(type => { + const { transform, content } = type + if (transform) { + content.forEach(transform) + } + return type + }) +} + +/** + * Sort every type content with the user provided callback + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +async function sort_entries (types) { + types = await Promise.all(types) + return types.map(type => { + const { sort, content } = type + if (sort) { + type.content = content.sort(sort) + } + return type + }) +} + +/** + * Compiles single entry views for content types + * @param {Array} types - Populated content type objects + * @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(({ 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 => { + 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) + }) + }) + }) + }) +} + +/** + * Writes all data for type with content as json + * @param {Array} types - Populated content type objects + * @return {Promise} - promise for when compilation is finished + */ +async function write_entries (types) { + types = await Promise.all(types) + return types.map(({ write, content }) => { + if (!write) return + return this.util.write(write, JSON.stringify(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/src/hosts.js b/src/hosts.js new file mode 100644 index 0000000..f8a09dd --- /dev/null +++ b/src/hosts.js @@ -0,0 +1,4 @@ +export default { + develop: 'preview.contentful.com', + production: 'cdn.contentful.com' +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..7d8a3cf --- /dev/null +++ b/src/index.js @@ -0,0 +1,27 @@ +import RootsContentful from './extension' +import errors from './errors' + +/** + * 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 +} + +/** + * transfers validated user-settings to + * the extension class + * @param {Object} opts - user-supplied settings + * @return {Function} - the extension class + */ +export default function extension (opts) { + RootsContentful.opts = validate(opts) + return RootsContentful +} 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-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' +} 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/src/util/oneline.js b/src/util/oneline.js new file mode 100644 index 0000000..90496a2 --- /dev/null +++ b/src/util/oneline.js @@ -0,0 +1,5 @@ +function oneline (str) { + return str.replace(/(?:\s+)/g, ' ').trim() +} + +export default oneline diff --git a/test/_setup.js b/test/_setup.js new file mode 100644 index 0000000..25940be --- /dev/null +++ b/test/_setup.js @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000..4de7aaa --- /dev/null +++ b/test/_teardown.js @@ -0,0 +1,6 @@ +require('babel-core/register') +var helpers = require('./helpers').helpers +console.log('performing cleanup...') +helpers.project.remove_folders('**/public') +console.log('done with cleanup') +process.exit(0) 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/basic/compile.js b/test/basic/compile.js new file mode 100644 index 0000000..f1f68d2 --- /dev/null +++ b/test/basic/compile.js @@ -0,0 +1,36 @@ +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' + let body = 'Rich Boy selling crick' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) + await ctx::compile_fixture('basic--compile') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('compiles basic project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) +}) + +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 => { + unmock_contentful() +}) diff --git a/test/basic/content-type-fields.js b/test/basic/content-type-fields.js new file mode 100644 index 0000000..ec0e0ea --- /dev/null +++ b/test/basic/content-type-fields.js @@ -0,0 +1,25 @@ +import test from 'ava' +import errors from '../../src/errors' +import { + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +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--content-type-fields'), errors.sys_conflict) +}) + +test.after(async t => { + unmock_contentful() +}) diff --git a/test/basic/custom-locals.js b/test/basic/custom-locals.js new file mode 100644 index 0000000..55e9e79 --- /dev/null +++ b/test/basic/custom-locals.js @@ -0,0 +1,32 @@ +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' + let body = 'Rich Boy selling crack' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) + await ctx::compile_fixture('basic--custom-locals') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +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 => { + unmock_contentful() +}) diff --git a/test/config.js b/test/config.js new file mode 100644 index 0000000..dd08d90 --- /dev/null +++ b/test/config.js @@ -0,0 +1,41 @@ +import test from 'ava' +import errors from '../src/errors' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from './helpers' + +let ctx = {} + +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('config--missing-token'), errors.no_token) +}) + +test('should throw an error without content type id', async t => { + 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('config--alternative-type') + ctx.index_path = `${ctx.public_dir}/index.html` + 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 => { + unmock_contentful() +}) diff --git a/test/data-manipulation/sort.js b/test/data-manipulation/sort.js new file mode 100644 index 0000000..e38baf2 --- /dev/null +++ b/test/data-manipulation/sort.js @@ -0,0 +1,52 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +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('data-manipulation--sort') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.posts_path = `${ctx.public_dir}/posts.json` +}) + +test('compiles project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) +}) + +test('orders data correctly for the project', async t => { + t.plan(4) + // titles should be order A before B before C + t.true(await helpers.file.contains_match( + ctx.index_path, + '^.*(Title A)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title C).*$', + { async } + )) + for (let body of ctx.bodies) { + t.true(await helpers.file.contains(ctx.index_path, body, { async })) + } +}) + +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 => { + unmock_contentful() +}) diff --git a/test/data-manipulation/transform.js b/test/data-manipulation/transform.js new file mode 100644 index 0000000..2bb5b2d --- /dev/null +++ b/test/data-manipulation/transform.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 => { + 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('data-manipulation--transform') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.posts_path = `${ctx.public_dir}/posts.json` +}) + +test('compiles project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) +}) + +test('does not reorder data', async t => { + // titles should be order A before B before C + t.true(await helpers.file.contains_match( + ctx.index_path, + '^.*(Title C)[/<>\\w\\s]*(Title B)[/<>\\w\\s]*(Title A).*$', + { async } + )) +}) + +test('has manipulated data correctly for the project', async t => { + t.plan(3) + for (let body of ctx.bodies) { + t.false(await helpers.file.contains(ctx.index_path, body, { async })) + } +}) + +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 => { + 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/image_view_helper/app.coffee b/test/fixtures/basic--compile-cache/app.coffee similarity index 89% rename from test/fixtures/image_view_helper/app.coffee rename to test/fixtures/basic--compile-cache/app.coffee index b303f3c..c2c261c 100644 --- a/test/fixtures/image_view_helper/app.coffee +++ b/test/fixtures/basic--compile-cache/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/alt-content-type-config/index.jade b/test/fixtures/basic--compile-cache/index.jade similarity index 100% rename from test/fixtures/alt-content-type-config/index.jade rename to test/fixtures/basic--compile-cache/index.jade diff --git a/test/fixtures/alt-content-type-config/package.json b/test/fixtures/basic--compile-cache/package.json similarity index 100% rename from test/fixtures/alt-content-type-config/package.json rename to test/fixtures/basic--compile-cache/package.json 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/fixtures/basic--compile/about.jade b/test/fixtures/basic--compile/about.jade new file mode 100644 index 0000000..2763786 --- /dev/null +++ b/test/fixtures/basic--compile/about.jade @@ -0,0 +1 @@ +h1 wow \ No newline at end of file diff --git a/test/fixtures/basic/app.coffee b/test/fixtures/basic--compile/app.coffee similarity index 89% rename from test/fixtures/basic/app.coffee rename to test/fixtures/basic--compile/app.coffee index b303f3c..c2c261c 100644 --- a/test/fixtures/basic/app.coffee +++ b/test/fixtures/basic--compile/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/basic/index.jade b/test/fixtures/basic--compile/index.jade similarity index 100% rename from test/fixtures/basic/index.jade rename to test/fixtures/basic--compile/index.jade diff --git a/test/fixtures/basic/package.json b/test/fixtures/basic--compile/package.json similarity index 100% rename from test/fixtures/basic/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/basic--content-type-fields/app.coffee b/test/fixtures/basic--content-type-fields/app.coffee new file mode 100644 index 0000000..c2c261c --- /dev/null +++ b/test/fixtures/basic--content-type-fields/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/missing_config/index.jade b/test/fixtures/basic--content-type-fields/index.jade similarity index 100% rename from test/fixtures/missing_config/index.jade rename to test/fixtures/basic--content-type-fields/index.jade diff --git a/test/fixtures/custom_name/package.json b/test/fixtures/basic--content-type-fields/package.json similarity index 100% rename from test/fixtures/custom_name/package.json rename to test/fixtures/basic--content-type-fields/package.json diff --git a/test/fixtures/basic/about.jade b/test/fixtures/basic--custom-locals/about.jade similarity index 100% rename from test/fixtures/basic/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 90% rename from test/fixtures/custom_name/app.coffee rename to test/fixtures/basic--custom-locals/app.coffee index be6dbd9..af220ac 100644 --- a/test/fixtures/custom_name/app.coffee +++ b/test/fixtures/basic--custom-locals/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] 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/image_view_helper/package.json b/test/fixtures/basic--custom-locals/package.json similarity index 100% rename from test/fixtures/image_view_helper/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 87% rename from test/fixtures/alt-content-type-config/app.coffee rename to test/fixtures/config--alternative-type/app.coffee index 846c93d..c3d2b72 100644 --- a/test/fixtures/alt-content-type-config/app.coffee +++ b/test/fixtures/config--alternative-type/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/sort/index.jade b/test/fixtures/config--alternative-type/index.jade similarity index 100% rename from test/fixtures/sort/index.jade rename to test/fixtures/config--alternative-type/index.jade diff --git a/test/fixtures/missing_config/package.json b/test/fixtures/config--alternative-type/package.json similarity index 100% rename from test/fixtures/missing_config/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 89% rename from test/fixtures/missing_config/app.coffee rename to test/fixtures/config--missing-config/app.coffee index 9428575..2a318b6 100644 --- a/test/fixtures/missing_config/app.coffee +++ b/test/fixtures/config--missing-config/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] diff --git a/test/fixtures/transform/index.jade b/test/fixtures/config--missing-config/index.jade similarity index 100% rename from test/fixtures/transform/index.jade rename to test/fixtures/config--missing-config/index.jade diff --git a/test/fixtures/missing_token/package.json b/test/fixtures/config--missing-config/package.json similarity index 100% rename from test/fixtures/missing_token/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 86% rename from test/fixtures/missing_token/app.coffee rename to test/fixtures/config--missing-token/app.coffee index 96412f9..6b8b34a 100644 --- a/test/fixtures/missing_token/app.coffee +++ b/test/fixtures/config--missing-token/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] 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/sort/package.json b/test/fixtures/config--missing-token/package.json similarity index 100% rename from test/fixtures/sort/package.json rename to test/fixtures/config--missing-token/package.json diff --git a/test/fixtures/custom_name/about.jade b/test/fixtures/data-manipulation--sort/about.jade similarity index 100% rename from test/fixtures/custom_name/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 91% rename from test/fixtures/sort/app.coffee rename to test/fixtures/data-manipulation--sort/app.coffee index 094f066..22f1105 100644 --- a/test/fixtures/sort/app.coffee +++ b/test/fixtures/data-manipulation--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/write/index.jade b/test/fixtures/data-manipulation--sort/index.jade similarity index 100% rename from test/fixtures/write/index.jade rename to test/fixtures/data-manipulation--sort/index.jade diff --git a/test/fixtures/transform/package.json b/test/fixtures/data-manipulation--sort/package.json similarity index 100% rename from test/fixtures/transform/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/sort/about.jade b/test/fixtures/data-manipulation--transform/about.jade similarity index 100% rename from test/fixtures/sort/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 91% rename from test/fixtures/transform/app.coffee rename to test/fixtures/data-manipulation--transform/app.coffee index 415f892..68f04ec 100644 --- a/test/fixtures/transform/app.coffee +++ b/test/fixtures/data-manipulation--transform/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' megaTransform = (entry)-> delete entry.body diff --git a/test/fixtures/data-manipulation--transform/index.jade b/test/fixtures/data-manipulation--transform/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/data-manipulation--transform/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/package.json b/test/fixtures/data-manipulation--transform/package.json similarity index 100% rename from test/fixtures/write/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/image_view_helper/index.jade b/test/fixtures/image_view_helper/index.jade deleted file mode 100644 index 3eee69d..0000000 --- a/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/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/single_entry_multi/package.json b/test/fixtures/locales--multi/package.json similarity index 56% rename from test/fixtures/single_entry_multi/package.json rename to test/fixtures/locales--multi/package.json index c5d3ec0..2d0ae2c 100644 --- a/test/fixtures/single_entry_multi/package.json +++ b/test/fixtures/locales--multi/package.json @@ -1,7 +1,6 @@ { "name": "test", "dependencies": { - "jade": "*", - "string": "*" + "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/transform/about.jade b/test/fixtures/locales--scope/about.jade similarity index 100% rename from test/fixtures/transform/about.jade rename to test/fixtures/locales--scope/about.jade 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/write/about.jade b/test/fixtures/locales--setup/about.jade similarity index 100% rename from test/fixtures/write/about.jade rename to test/fixtures/locales--setup/about.jade 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/single_entry_custom/index.jade b/test/fixtures/locales--single/about.jade similarity index 100% rename from test/fixtures/single_entry_custom/index.jade rename to test/fixtures/locales--single/about.jade 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/fixtures/single_entry/app.coffee b/test/fixtures/single-entry--clear-locals-between-compiles/app.coffee similarity index 85% rename from test/fixtures/single_entry/app.coffee rename to test/fixtures/single-entry--clear-locals-between-compiles/app.coffee index c058599..b9d39a8 100644 --- a/test/fixtures/single_entry/app.coffee +++ b/test/fixtures/single-entry--clear-locals-between-compiles/app.coffee @@ -1,5 +1,4 @@ -S = require 'string' -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] 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/single-entry--clear-locals-between-compiles/package.json b/test/fixtures/single-entry--clear-locals-between-compiles/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/single-entry--clear-locals-between-compiles/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} 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-path-function/app.coffee b/test/fixtures/single-entry--custom-path-function/app.coffee new file mode 100644 index 0000000..a73a4bc --- /dev/null +++ b/test/fixtures/single-entry--custom-path-function/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/test/fixtures/single-entry--custom-path-function/index.jade b/test/fixtures/single-entry--custom-path-function/index.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/single-entry--custom-path-function/index.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/single_entry/package.json b/test/fixtures/single-entry--custom-path-function/package.json similarity index 68% rename from test/fixtures/single_entry/package.json rename to test/fixtures/single-entry--custom-path-function/package.json index c5d3ec0..1a248fc 100644 --- a/test/fixtures/single_entry/package.json +++ b/test/fixtures/single-entry--custom-path-function/package.json @@ -2,6 +2,6 @@ "name": "test", "dependencies": { "jade": "*", - "string": "*" + "underscore.string": "*" } } 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_custom/app.coffee b/test/fixtures/single-entry--default-path-function/app.coffee similarity index 72% rename from test/fixtures/single_entry_custom/app.coffee rename to test/fixtures/single-entry--default-path-function/app.coffee index c5b8d3d..b9d39a8 100644 --- a/test/fixtures/single_entry_custom/app.coffee +++ b/test/fixtures/single-entry--default-path-function/app.coffee @@ -1,5 +1,4 @@ -S = require 'string' -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] @@ -12,7 +11,6 @@ module.exports = id: '6BYT1gNiIEyIw8Og8aQAO6' name: 'blog_posts' template: 'views/_blog_post.jade' - path: (e) -> "blogging/#{e.category}/#{S(e.title).slugify().s}" } ] ) 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/single-entry--default-path-function/package.json b/test/fixtures/single-entry--default-path-function/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/single-entry--default-path-function/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} 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/single-entry--image-view-helper/index.jade b/test/fixtures/single-entry--image-view-helper/index.jade new file mode 100644 index 0000000..a33a043 --- /dev/null +++ b/test/fixtures/single-entry--image-view-helper/index.jade @@ -0,0 +1,7 @@ +ul + - for p in contentful.blog_posts + li + 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/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-path-function/app.coffee b/test/fixtures/single-entry--multi-path-function/app.coffee new file mode 100644 index 0000000..503e364 --- /dev/null +++ b/test/fixtures/single-entry--multi-path-function/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/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_custom/package.json b/test/fixtures/single-entry--multi-path-function/package.json similarity index 68% rename from test/fixtures/single_entry_custom/package.json rename to test/fixtures/single-entry--multi-path-function/package.json index c5d3ec0..1a248fc 100644 --- a/test/fixtures/single_entry_custom/package.json +++ b/test/fixtures/single-entry--multi-path-function/package.json @@ -2,6 +2,6 @@ "name": "test", "dependencies": { "jade": "*", - "string": "*" + "underscore.string": "*" } } 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/single_entry_multi/app.coffee b/test/fixtures/single-entry--path-helper/app.coffee similarity index 71% rename from test/fixtures/single_entry_multi/app.coffee rename to test/fixtures/single-entry--path-helper/app.coffee index 514691c..b9d39a8 100644 --- a/test/fixtures/single_entry_multi/app.coffee +++ b/test/fixtures/single-entry--path-helper/app.coffee @@ -1,5 +1,4 @@ -S = require 'string' -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] @@ -12,7 +11,6 @@ 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']) } ] ) 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/fixtures/write--as-json/about.jade b/test/fixtures/write--as-json/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/write--as-json/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/write/app.coffee b/test/fixtures/write--as-json/app.coffee similarity index 88% rename from test/fixtures/write/app.coffee rename to test/fixtures/write--as-json/app.coffee index 289ef73..4bd0770 100644 --- a/test/fixtures/write/app.coffee +++ b/test/fixtures/write--as-json/app.coffee @@ -1,4 +1,4 @@ -contentful = require '../../..' +contentful = require '../../../src' module.exports = ignores: ["**/_*", "**/.DS_Store"] 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/index.js b/test/helpers/index.js new file mode 100644 index 0000000..06adc06 --- /dev/null +++ b/test/helpers/index.js @@ -0,0 +1,105 @@ +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 +/* 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 async = true + +export const helpers = new RootsUtil.Helpers({ + base: path.join(__dirname, '../fixtures') +}) + +export async function compile_fixture (name) { + this.public_dir = `${name}/public` + 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() +} + +export function mock_contentful (opts = {}) { + mockery.enable({ + warnOnUnregistered: false, + useCleanCache: true + }) + 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' + }, + ...opts + } + return mockery.registerMock('contentful', { + createClient () { + return { + contentType () { + return Promise.resolve(opts.content_type) + }, + 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() +}) diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index abe3301..0000000 --- a/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ ---reporter spec ---compilers coffee:coffee-script/register ---require test/support/helpers ---timeout 30000 diff --git a/test/single-entry/clear-locals-between-compiles.js b/test/single-entry/clear-locals-between-compiles.js new file mode 100644 index 0000000..dd2f906 --- /dev/null +++ b/test/single-entry/clear-locals-between-compiles.js @@ -0,0 +1,35 @@ +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' + 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--clear-locals-between-compiles') + 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", async t => { + t.false(await helpers.file.contains(ctx.post_path, ctx.body, { async })) +}) + +test.after(async t => { + unmock_contentful() +}) diff --git a/test/single-entry/custom-path-function.js b/test/single-entry/custom-path-function.js new file mode 100644 index 0000000..a61f231 --- /dev/null +++ b/test/single-entry/custom-path-function.js @@ -0,0 +1,36 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +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-path-function') + 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', 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 => { + unmock_contentful() +}) diff --git a/test/single-entry/default-path-function.js b/test/single-entry/default-path-function.js new file mode 100644 index 0000000..a9c748a --- /dev/null +++ b/test/single-entry/default-path-function.js @@ -0,0 +1,44 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +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--default-path-function') + 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', 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', 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', 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 => { + unmock_contentful() +}) diff --git a/test/single-entry/image-view-helper.js b/test/single-entry/image-view-helper.js new file mode 100644 index 0000000..c7383a6 --- /dev/null +++ b/test/single-entry/image-view-helper.js @@ -0,0 +1,45 @@ +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +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.query_img_path } } + } + } + }, { + fields: { + image: { + fields: { file: { url: ctx.regular_img_path } } + } + } + }] + }) + await ctx::compile_fixture('single-entry--image-view-helper') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +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', async t => { + t.true(await helpers.file.contains(ctx.index_path, `${ctx.query_img_path}?w=100&h=100`, { async })) +}) + +test.after(async t => { + unmock_contentful() +}) diff --git a/test/single-entry/multi-path-function.js b/test/single-entry/multi-path-function.js new file mode 100644 index 0000000..25e4d21 --- /dev/null +++ b/test/single-entry/multi-path-function.js @@ -0,0 +1,56 @@ +import path from 'path' +import slugify from 'underscore.string/slugify' +import test from 'ava' +import { + async, + helpers, + mock_contentful, + unmock_contentful, + compile_fixture +} from '../helpers' + +let ctx = {} + +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-path-function') + ctx.index_path = `${ctx.public_dir}/index.html` +}) + +test('compiles a single entry to multiple files', async t => { + t.plan(16) + 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(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", async t => { + t.plan(4) + 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 => { + unmock_contentful() +}) 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() +}) 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/write.js b/test/write.js new file mode 100644 index 0000000..9d3a281 --- /dev/null +++ b/test/write.js @@ -0,0 +1,38 @@ +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' + let body = 'Rich Boy selling crick' + ctx = { ...ctx, title, body } + mock_contentful({ + entries: [{ + fields: { title, body } + }] + }) + await ctx::compile_fixture('write--as-json') + ctx.index_path = `${ctx.public_dir}/index.html` + ctx.posts_path = `${ctx.public_dir}/posts.json` +}) + +test('compiles project', async t => { + t.ok(await helpers.file.exists(ctx.index_path, { async })) +}) + +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 => { + unmock_contentful() +}) diff --git a/wallaby.js b/wallaby.js new file mode 100644 index 0000000..57feca2 --- /dev/null +++ b/wallaby.js @@ -0,0 +1,37 @@ +/* + 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: [ + 'src/**/*.js' + ], + + tests: [ + 'test/**/*.js' + ], + + compilers: { + '**/*.js': w.compilers.babel(babelConfig) + }, + + env: { + type: 'node' + }, + + testFramework: 'ava' + } +}