Skip to content

Commit

Permalink
repace sinon with mockery, 💯 tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joshrowley committed Jul 30, 2014
1 parent de82d72 commit 742b06f
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 107 deletions.
135 changes: 62 additions & 73 deletions lib/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -22,82 +22,71 @@ module.exports = (opts) ->
accessToken: opts.access_token
space: opts.space_id

###*
* Configures content types set in app.coffee. Sets default values if
* optional config options are missing.
* @param {Array} types - content_types set in app.coffee extension config
* @return {Promise} - returns an array of configured content types
###

configure_content = (types) ->
W.map types, (t) ->
if not t.id then return W.reject(errors.no_type_id)
if t.name then return W.resolve(t)
t.filters ?= {}
W client.contentType(t.id).then (res) ->
t.name = pluralize(S(res.name).toLowerCase().underscore().s)
return t

###*
* Fetches data from Contentful API, formats the raw data, and constructs
* the locals object
* @param {Array} types - configured content_type objects
* @return {Promise} - returns formatted locals object with all content
###

get_all_content = (types) ->
W.reduce types, (m, t) ->
fetch_content(t)
.then(format_content)
.then((c) -> m[t.name] = c)
.yield(m)
, {}

###*
* 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))

###*
* 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)

content = []

class RootsContentful
constructor: (@roots) ->
@roots.config.locals ||= {}

setup: ->
if content.length
configure_content(opts.content_types)
configure_content(opts.content_types)
.then(get_all_content)
.then (res) -> content = res
else
W.resolve()

compile_hooks: ->
before_pass: (ctx) =>
# once content is loaded, pass contentful data into locals
promise.then (locals) =>
if @roots.config.locals.contentful then return
@roots.config.locals.contentful = locals
.then (res) =>
@roots.config.locals.contentful = res

###*
* 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) ->
W.map types, (t) ->
if not t.id then return W.reject(errors.no_type_id)
if t.name then return W.resolve(t)
t.filters ?= {}
W client.contentType(t.id).then (res) ->
t.name = pluralize(S(res.name).toLowerCase().underscore().s)
return t

###*
* Fetches data from Contentful API, formats the raw data, and constructs
* the locals object
* @param {Array} types - configured content_type objects
* @return {Promise} - returns formatted locals object with all content
###

get_all_content = (types) ->
W.reduce types, (m, t) ->
fetch_content(t)
.then(format_content)
.then((c) -> m[t.name] = c)
.yield(m)
, {}

###*
* 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))

###*
* 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)
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
},
"dependencies": {
"lodash": "~2.4.1",
"minimatch": "0.2.x",
"roots-util": "0.0.4",
"roots-util": "~0.0.5",
"contentful": "~0.1.2",
"when": "~3.4.2",
"string": "~1.9.0",
Expand All @@ -23,7 +22,7 @@
"istanbul": "0.3.x",
"mocha": "1.x",
"roots": "3.x",
"sinon": "1.x"
"mockery": "~1.4.0"
},
"peerDependencies": {
"roots": "3.x"
Expand Down
16 changes: 16 additions & 0 deletions test/fixtures/missing_token/app.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
contentful = require '../../..'

module.exports =
ignores: ["**/_*", "**/.DS_Store"]
extensions: [
contentful(
content_types: [
{
name: 'test'
}
{
id: '7CDlVsacqQc88cmIEGYWMa'
}
]
)
]
1 change: 1 addition & 0 deletions test/fixtures/missing_token/index.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
h1 wow
6 changes: 6 additions & 0 deletions test/fixtures/missing_token/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "test",
"dependencies": {
"jade": "*"
}
}
4 changes: 2 additions & 2 deletions test/support/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var chai = require('chai'),
chai_promise = require('chai-as-promised'),
sinon = require('sinon'),
mockery = require('mockery'),
path = require('path'),
_path = path.join(__dirname, '../fixtures'),
RootsUtil = require('roots-util'),
Expand All @@ -11,7 +11,7 @@ var should = chai.should();
chai.use(chai_promise);

global.should = should;
global.sinon = sinon;
global.mockery = mockery;
global._path = _path;
global.h = h;
global.roots_contentful = roots_contentful;
72 changes: 43 additions & 29 deletions test/test.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
_ = require 'lodash'
path = require 'path'
fs = require 'fs'
W = require 'when'
Expand All @@ -8,21 +9,31 @@ Roots = require 'roots'

compile_fixture = (fixture_name, done) ->
@public = path.join(fixture_name, 'public')
node.call(h.project.compile.bind(h), Roots, fixture_name)

stub_contentful = (opts = {}) ->
contentful = require 'contentful'
sinon.stub(contentful, 'createClient').returns
contentType: -> W.resolve(opts.content_type || { name: 'Blog Post' })
entries: -> W.resolve [
opts.entry || {
sys: {'sys': 'data'},
fields: {
title: 'Default Title'
body: 'Default Body'
}
}
]
h.project.compile(Roots, fixture_name)

mock_contentful = (opts = {}) ->
mockery.enable
warnOnUnregistered: false
useCleanCache: true

opts = _.defaults opts,
entry:
sys:
sys: 'data'
fields:
title: 'Default Title'
body: 'Default Body'
content_type:
name: 'Blog Post'

mockery.registerMock 'contentful',
createClient: ->
contentType: -> W.resolve(opts.content_type)
entries: -> W.resolve [ opts.entry ]

unmock_contentful = ->
mockery.deregisterAll()
mockery.disable()

before (done) ->
h.project.install_dependencies('*', done)
Expand All @@ -33,26 +44,30 @@ after ->
# tests

describe 'config', ->
before -> mock_contentful()

it 'should throw an error when missing an access token', ->
(-> roots_contentful()).should.throw()
(-> compile_fixture.call(@, 'missing_token')).should.throw()

it 'should throw an error without content type id', ->
compile_fixture.call(@, 'missing_config').should.be.rejected

describe 'contentful content type fields', ->
before -> @stub = stub_contentful(entry: {fields: {sys: 'test'}})
after -> unmock_contentful()

describe 'contentful content type fields', ->
before -> mock_contentful(entry: {fields: {sys: 'test'}})

it 'should throw an error if `sys` is a field name', ->
compile_fixture.call(@, 'basic').should.be.rejected
it 'should throw an error if `sys` is a field name', ->
compile_fixture.call(@, 'basic').should.be.rejected

after -> @stub.restore()
after -> unmock_contentful()

describe 'basic compile', ->
before (done) ->
@title = 'Throw Some Ds'
@body = 'Rich Boy selling crick'
@stub = stub_contentful(entry: {fields: {title: @title, body: @body}})
compile_fixture.call(@, 'basic').then(-> done())
mock_contentful(entry: {fields: {title: @title, body: @body}})
compile_fixture.call(@, 'basic').then(-> done()).catch(done)

it 'compiles basic project', ->
p = path.join(@public, 'index.html')
Expand All @@ -63,20 +78,19 @@ describe 'basic compile', ->
h.file.contains(p, @title).should.be.true
h.file.contains(p, @body).should.be.true

after ->
@stub.restore()
after -> unmock_contentful()

describe 'custom name for view helper local', ->
before (done) ->
@title = 'Throw Some Ds'
@body = 'Rich Boy selling crack'
@stub = stub_contentful(entry: {fields: {title: @title, body: @body}})
compile_fixture.call(@, 'custom_name', -> done())
mock_contentful(entry: {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 ->
@stub.restore()
after -> unmock_contentful()

1 comment on commit 742b06f

@kylemac
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dope, 💯

Please sign in to comment.