Merge pull request #115 from bendrucker/unified-tests

Run all the tests via Electron
This commit is contained in:
Yoshua Wuyts
2016-07-05 02:25:42 +02:00
committed by GitHub
10 changed files with 394 additions and 67 deletions
+1
View File
@@ -1,5 +1,6 @@
node_modules/
coverage/
coverage.json
tmp/
npm-debug.log*
.DS_Store
+7
View File
@@ -3,6 +3,13 @@ node_js:
- '6'
sudo: false
language: node_js
addons:
apt:
packages:
- xvfb
before_install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
script: npm run test:cov
after_script: npm i -g codecov.io && cat ./coverage/lcov.info | codecov
env:
+4 -4
View File
@@ -1,6 +1,7 @@
const history = require('sheet-router/history')
const sheetRouter = require('sheet-router')
const document = require('global/document')
const onReady = require('document-ready')
const href = require('sheet-router/href')
const hash = require('sheet-router/hash')
const hashMatch = require('hash-match')
@@ -40,7 +41,7 @@ function choo (opts) {
const state = _store.state({ state: serverState })
const router = createRouter(_defaultRoute, _routes, createSend)
const tree = router(route, state)
return tree.toString()
return tree.outerHTML || tree.toString()
function createSend () {
return function send () {
@@ -61,14 +62,14 @@ function choo (opts) {
_store.model(appInit(startOpts))
const createSend = _store.start(startOpts)
_router = start._router = createRouter(_defaultRoute, _routes, createSend)
const state = _store.state()
const state = _store.state({state: {}})
if (!selector) {
const tree = _router(state.location.pathname, state)
_rootNode = tree
return tree
} else {
document.addEventListener('DOMContentLoaded', function (event) {
onReady(function onReady () {
const oldTree = document.querySelector(selector)
assert.ok(oldTree, 'could not query selector: ' + selector)
const newTree = _router(state.location.pathname, state)
@@ -81,7 +82,6 @@ function choo (opts) {
// (obj, obj, obj, str, fn) -> null
function render (data, state, prev, name, createSend) {
if (opts.onState) opts.onState(data, state, prev, name, createSend)
if (state === prev) return
const newTree = _router(state.location.pathname, state, prev)
_rootNode = yo.update(_rootNode, newTree)
+12 -3
View File
@@ -5,13 +5,13 @@
"main": "index.js",
"scripts": {
"deps": "dependency-check . && dependency-check . --extra --no-dev -i xhr",
"test:electron": "browserify tests/**/*.js -t es2020 -p proxyquire-universal | tape-run",
"test:cov": "browserify tests/**/*.js -t es2020 -p proxyquire-universal -p tape-istanbul/plugin | tape-run | tape-istanbul && istanbul report",
"test:server": "standard && npm run deps && NODE_ENV=test node tests/server/*",
"test:server:cov": "standard && npm run deps && NODE_ENV=test istanbul cover tests/server/*",
"test:browser": "standard && npm run deps && NODE_ENV=test zuul tests/browser/*",
"test:browser:local": "standard && npm run deps && NODE_ENV=test zuul --local 8080 -- tests/browser/*",
"test:cov": "npm run test:server:cov",
"preversion": "if [ ! -z $SKIP_TEST ]; then npm run test:browser; fi",
"test": "npm run test:server"
"test": "npm run test:electron"
},
"repository": "yoshuawuyts/choo",
"keywords": [
@@ -25,6 +25,7 @@
"license": "MIT",
"dependencies": {
"barracks": "^7.0.3",
"document-ready": "~1.0.1",
"global": "^4.3.0",
"hash-match": "^1.0.2",
"sheet-router": "^3.1.0",
@@ -33,20 +34,28 @@
"yo-yo": "^1.2.2"
},
"devDependencies": {
"append-child": "~1.0.0",
"bankai": "^2.0.2",
"browserify": "^13.0.1",
"browserify-istanbul": "^2.0.0",
"bundle-collapser": "^1.2.1",
"dependency-check": "^2.5.1",
"es2020": "^1.0.1",
"geval": "~2.1.1",
"insert-css": "^0.2.0",
"istanbul": "^0.4.4",
"karma-sauce-launcher": "^1.0.0",
"min-document": "~2.18.0",
"proxyquire": "~1.7.10",
"proxyquire-universal": "~1.0.8",
"proxyquireify": "~3.2.0",
"server-router": "^2.1.0",
"sheetify": "^5.0.0",
"standard": "^7.1.0",
"tachyons": "^4.0.0-beta.19",
"tape": "^4.5.1",
"tape-istanbul": "~1.0.2",
"tape-run": "~2.1.4",
"yo-yoify": "^3.1.0",
"zuul": "toddself/zuul"
}
+51 -52
View File
@@ -1,61 +1,60 @@
const tape = require('tape')
const test = require('tape')
const append = require('append-child')
const choo = require('../../')
const view = require('../../html')
tape('should render on the client', function (t) {
t.test('state should not be mutable', function (t) {
t.plan(4)
test('state is immutable', function (t) {
t.plan(4)
const app = choo()
const state = {
foo: 'baz',
beep: 'boop'
}
const app = choo()
const state = {
foo: 'baz',
beep: 'boop'
}
app.model({
state: state,
namespace: 'test',
reducers: {
'no-reducer-mutate': (action, state) => {
return {}
},
'mutate-on-return': (action, state) => {
delete action.type
return action
}
app.model({
state: state,
namespace: 'test',
reducers: {
'no-reducer-mutate': (action, state) => {
return {}
},
effects: {
'triggers-reducers': (action, state, send) => {
send('test:mutate-on-return', {beep: 'barp'})
}
'mutate-on-return': (action, state) => {
delete action.type
return action
}
})
let loop = -1
const asserts = [
(state) => t.deepEqual(state, {foo: 'baz', beep: 'boop'}, 'intial state'),
(state) => t.deepEqual(state, {foo: 'baz', beep: 'boop'}, 'no change in state'),
(state) => t.deepEqual(state, {foo: 'oof', beep: 'boop'}, 'change in state from reducer'),
(state) => t.deepEqual(state, {foo: 'oof', beep: 'barp'}, 'change in state from effect')
]
const triggers = [
(send) => send('test:no-reducer-mutate'),
(send) => send('test:mutate-on-return', {foo: 'oof'}),
(send) => send('test:triggers-reducers')
]
app.router((route) => [
route('/', function (params, state, send) {
++loop
asserts[loop] && asserts[loop](state.test)
setTimeout(() => triggers[loop] && triggers[loop](send), 5)
return view`<div><span class="test">${state.foo}:${state.beep}</span></div>`
})
])
const tree = app.start()
document.body.appendChild(tree)
},
effects: {
'triggers-reducers': (action, state, send, done) => {
send('test:mutate-on-return', {beep: 'barp'}, done)
}
}
})
let loop = -1
const asserts = [
(state) => t.deepEqual(state, {foo: 'baz', beep: 'boop'}, 'intial state'),
(state) => t.deepEqual(state, {foo: 'baz', beep: 'boop'}, 'no change in state'),
(state) => t.deepEqual(state, {foo: 'oof', beep: 'boop'}, 'change in state from reducer'),
(state) => t.deepEqual(state, {foo: 'oof', beep: 'barp'}, 'change in state from effect')
]
const triggers = [
(send) => send('test:no-reducer-mutate'),
(send) => send('test:mutate-on-return', {foo: 'oof'}),
(send) => send('test:triggers-reducers')
]
app.router((route) => [
route('/', function (state, prev, send) {
++loop
asserts[loop] && asserts[loop](state.test)
setTimeout(() => triggers[loop] && triggers[loop](send), 5)
return view`<div><span class="test">${state.foo}:${state.beep}</span></div>`
})
])
const tree = app.start()
t.on('end', append(tree))
})
+46
View File
@@ -0,0 +1,46 @@
const test = require('tape')
const choo = require('../../')
test('freeze (default)', function (t) {
t.plan(2)
const app = choo()
app.model({
state: {
foo: 'bar'
}
})
app.router((route) => [
route('/', function (state, prev, send) {
state.foo = ''
t.equal(state.foo, 'bar', 'cannot modify property')
state.bar = 'baz'
t.equal(state.bar, undefined, 'cannot add property')
})
])
app.start()
})
test('noFreeze', function (t) {
t.plan(2)
const app = choo({noFreeze: true})
app.model({
state: {
foo: 'bar'
}
})
app.router((route) => [
route('/', function (state, prev, send) {
state.foo = ''
t.equal(state.foo, '', 'can modify property')
state.bar = 'baz'
t.equal(state.bar, 'baz', 'can add property')
})
])
app.start()
})
+56
View File
@@ -0,0 +1,56 @@
const test = require('tape')
const append = require('append-child')
const choo = require('../../')
const view = require('../../html')
test('hooks', function (t) {
t.plan(9)
const app = choo({
onError: function (err) {
t.equal(err.message, 'effect error', 'onError: receives err')
},
onAction: function (action, state, name, caller, createSend) {
if (name === 'explodes') return
t.deepEqual(action, {foo: 'bar'}, 'onAction: action data')
t.equal(state.clicks, 0, 'onAction: current state: 0 clicks')
t.equal(name, 'click', 'onAction: action name')
t.equal(caller, '/', 'onAction: caller name')
t.equal(typeof createSend, 'function', 'onAction: createSend fn')
},
onState: function (action, state, prev, createSend) {
t.deepEqual(action, {foo: 'bar'}, 'onState: action data')
t.deepEqual(state.clicks, 1, 'onState: new state: 1 clicks')
t.deepEqual(prev.clicks, 0, 'onState: prev state: 0 clicks')
}
})
app.model({
state: {
clicks: 0
},
reducers: {
click: (action, state) => ({clicks: state.clicks + 1})
},
effects: {
explodes: (action, state, send, done) => {
setTimeout(() => done(new Error('effect error')), 5)
}
}
})
var sent = false
app.router((route) => [
route('/', function (state, prev, send) {
if (!sent) {
send('click', {foo: 'bar'})
send('explodes')
}
sent = true
return view`<span></span>`
})
])
const tree = app.start()
t.on('end', append(tree))
})
+29
View File
@@ -0,0 +1,29 @@
const test = require('tape')
const onReady = require('document-ready')
const append = require('append-child')
const choo = require('../../')
const view = require('../../html')
test('rehydration', function (t) {
t.plan(2)
const app = choo()
app.router((route) => [
route('/', function (state, prev, send) {
return view`<div id="app-root" onclick=${() => send('test')}>Hello world!</span>`
})
])
var node = document.createElement('div')
node.innerHTML = app.toString('/')
node = node.childNodes[0]
t.on('end', append(node))
app.start('#app-root')
onReady(function () {
t.equal(node.innerHTML, 'Hello world!', 'same content')
t.equal(typeof node.onclick, 'function', 'attaches dom listeners')
})
})
+166
View File
@@ -0,0 +1,166 @@
const test = require('tape')
const Event = require('geval/event')
const proxyquire = require('proxyquire')
const append = require('append-child')
const view = require('../../html')
test('routing', function (t) {
t.test('history', function (t) {
t.plan(3)
const history = Event()
const choo = proxyquire('../..', {
'sheet-router/history': history.listen
})
const app = choo()
app.model({
state: {
user: null
},
reducers: {
set: (action, state) => ({user: action.id})
},
effects: {
open: function (action, state, send, done) {
t.deepEqual(action, {id: 1})
send('set', {id: 1}, function (err) {
if (err) return done(err)
history.broadcast('https://foo.com/users/1')
})
}
}
})
app.router('/users', (route) => [
route('/users', parentView, [
route('/:user', childView)
])
])
const tree = app.start()
t.on('end', append(tree))
t.equal(tree.innerHTML.trim(), 'Open')
tree.onclick()
function parentView (state, prev, send) {
return view`
<button onclick=${() => send('open', {id: 1})}>
Open
</button>
`
}
function childView (state, prev, send) {
t.equal(state.user, 1)
return view`<button>${state.user}</button>`
}
})
t.test('hash', function (t) {
t.plan(1)
const hash = Event()
const choo = proxyquire('../..', {
'sheet-router/hash': hash.listen
})
const app = choo()
app.model({
state: {
user: null
},
reducers: {
set: (action, state) => ({user: action.id})
},
effects: {
open: function (action, state, send, done) {
send('set', {id: 1}, function (err) {
if (err) return done(err)
hash.broadcast('#users/1')
})
}
}
})
app.router('/users', (route) => [
route('/users', parentView, [
route('/:user', childView)
])
])
const tree = app.start({hash: true})
t.on('end', append(tree))
tree.onclick()
function parentView (state, prev, send) {
return view`
<button onclick=${() => send('open', {id: 1})}>
Open
</button>
`
}
function childView (state, prev, send) {
t.equal(state.user, 1)
return view`<button>${state.user}</button>`
}
})
t.test('disabling history', function (t) {
t.plan(1)
const choo = proxyquire('../..', {
'sheet-router/history': () => t.fail('history listener attached')
})
const app = choo()
app.router('/', (route) => [
route('/', function () {
t.pass('rendered')
})
])
app.start({history: false})
})
t.test('disabling href', function (t) {
t.plan(1)
const choo = proxyquire('../..', {
'sheet-router/href': () => t.fail('href listener attached')
})
const app = choo()
app.router('/', (route) => [
route('/', function () {
t.pass('rendered')
})
])
app.start({href: false})
})
t.test('viewless nesting', function (t) {
t.plan(1)
const choo = require('../..')
const app = choo()
app.router('/users/123', (route) => [
route('/users', [
route('/:user', function (state) {
t.deepEqual(state.params, {user: '123'})
})
])
])
app.start()
})
})
+22 -8
View File
@@ -1,9 +1,10 @@
const tape = require('tape')
const test = require('tape')
const minDocument = require('min-document')
const choo = require('../../')
const view = require('../../html')
tape('should render on the server', function (t) {
t.test('should render a static response', function (t) {
test('server', function (t) {
t.test('renders a static html response', function (t) {
t.plan(1)
const app = choo()
@@ -16,7 +17,20 @@ tape('should render on the server', function (t) {
t.equal(html, expected, 'strings are equal')
})
t.test('should accept a state object', function (t) {
t.test('renders without a real DOM', function (t) {
t.plan(1)
const app = choo()
app.router((route) => [
route('/', () => minDocument.createElement('div'))
])
const html = app.toString('/')
const expected = '<div></div>'
t.equal(html, expected, 'strings are equal')
})
t.test('receives a state object', function (t) {
t.plan(1)
const app = choo()
@@ -31,7 +45,7 @@ tape('should render on the server', function (t) {
t.equal(html, expected, 'strings are equal')
})
t.test('should extend flat existing models', function (t) {
t.test('extends flat existing models', function (t) {
t.plan(1)
const app = choo()
@@ -48,7 +62,7 @@ tape('should render on the server', function (t) {
t.equal(html, expected, 'strings are equal')
})
t.test('should extend namespaced existing models', function (t) {
t.test('extends namespaced existing models', function (t) {
t.plan(1)
const app = choo()
@@ -75,7 +89,7 @@ tape('should render on the server', function (t) {
t.equal(html, expected, 'strings are equal')
})
t.test('should throw if called without route', function (t) {
t.test('throws if called without route', function (t) {
t.plan(1)
const app = choo()
@@ -88,7 +102,7 @@ tape('should render on the server', function (t) {
t.throws(app.toString.bind(null), /route must be a string/)
})
t.test('should throw if calling send()', function (t) {
t.test('throws when calling send()', function (t) {
t.plan(1)
const app = choo()