Add browser tests (#696)
This commit is contained in:
@@ -21,5 +21,10 @@ addons:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
- xvfb
|
||||
install:
|
||||
- export DISPLAY=':99.0'
|
||||
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
||||
- npm install
|
||||
script: npm run test
|
||||
# after_script: npm i -g codecov.io && cat ./coverage/lcov.info | codecov
|
||||
|
||||
@@ -138,11 +138,11 @@ Choo.prototype.start = function () {
|
||||
}
|
||||
|
||||
this._setCache(this.state)
|
||||
this._matchRoute()
|
||||
this._stores.forEach(function (initStore) {
|
||||
initStore(self.state)
|
||||
})
|
||||
|
||||
this._matchRoute()
|
||||
this._tree = this._prerender(this.state)
|
||||
assert.ok(this._tree, 'choo.start: no valid DOM node returned for location ' + this.state.href)
|
||||
|
||||
|
||||
+5
-1
@@ -23,7 +23,9 @@
|
||||
"inspect": "browserify --full-paths index -p tinyify | discify --open",
|
||||
"prepublishOnly": "npm run build",
|
||||
"start": "bankai start example",
|
||||
"test": "standard && npm run deps && node test.js"
|
||||
"test": "standard && npm run deps && npm run test:node && npm run test:browser",
|
||||
"test:node": "node test/node.js | tap-format-spec",
|
||||
"test:browser": "browserify test/browser.js | tape-run | tap-format-spec"
|
||||
},
|
||||
"repository": "choojs/choo",
|
||||
"keywords": [
|
||||
@@ -52,6 +54,7 @@
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tap-format/spec": "^0.2.0",
|
||||
"@types/node": "^10.3.1",
|
||||
"browserify": "^16.2.2",
|
||||
"bundle-collapser": "^1.2.1",
|
||||
@@ -62,6 +65,7 @@
|
||||
"spok": "^0.9.1",
|
||||
"standard": "^11.0.1",
|
||||
"tape": "^4.6.3",
|
||||
"tape-run": "^5.0.0",
|
||||
"tinyify": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
var tape = require('tape')
|
||||
var h = require('hyperscript')
|
||||
|
||||
var html = require('../html')
|
||||
var raw = require('../html/raw')
|
||||
var choo = require('..')
|
||||
|
||||
tape('should mount in the DOM', function (t) {
|
||||
t.plan(1)
|
||||
var app = choo()
|
||||
var container = init('/', 'p')
|
||||
app.route('/', function (state, emit) {
|
||||
var strong = '<strong>Hello filthy planet</strong>'
|
||||
window.requestAnimationFrame(function () {
|
||||
var exp = '<p><strong>Hello filthy planet</strong></p>'
|
||||
t.equal(container.outerHTML, exp, 'result was OK')
|
||||
})
|
||||
return html`
|
||||
<p>${raw(strong)}</p>
|
||||
`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('should render with hyperscript', function (t) {
|
||||
t.plan(1)
|
||||
var app = choo()
|
||||
var container = init('/', 'p')
|
||||
app.route('/', function (state, emit) {
|
||||
window.requestAnimationFrame(function () {
|
||||
var exp = '<p><strong>Hello filthy planet</strong></p>'
|
||||
t.equal(container.outerHTML, exp, 'result was OK')
|
||||
})
|
||||
return h('p', h('strong', 'Hello filthy planet'))
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('should expose a public API', function (t) {
|
||||
var app = choo()
|
||||
|
||||
t.equal(typeof app.route, 'function', 'app.route prototype method exists')
|
||||
t.equal(typeof app.toString, 'function', 'app.toString prototype method exists')
|
||||
t.equal(typeof app.start, 'function', 'app.start prototype method exists')
|
||||
t.equal(typeof app.mount, 'function', 'app.mount prototype method exists')
|
||||
t.equal(typeof app.emitter, 'object', 'app.emitter prototype method exists')
|
||||
|
||||
t.equal(typeof app.emit, 'function', 'app.emit instance method exists')
|
||||
t.equal(typeof app.router, 'object', 'app.router instance object exists')
|
||||
t.equal(typeof app.state, 'object', 'app.state instance object exists')
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape('should enable history and hash by defaut', function (t) {
|
||||
var app = choo()
|
||||
t.true(app._historyEnabled, 'history enabled')
|
||||
t.true(app._hrefEnabled, 'href enabled')
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape('router should pass state and emit to view', function (t) {
|
||||
t.plan(2)
|
||||
var app = choo()
|
||||
var container = init()
|
||||
app.route('/', function (state, emit) {
|
||||
t.equal(typeof state, 'object', 'state is an object')
|
||||
t.equal(typeof emit, 'function', 'emit is a function')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('router should support a default route', function (t) {
|
||||
t.plan(1)
|
||||
var app = choo()
|
||||
var container = init('/random')
|
||||
app.route('*', function (state, emit) {
|
||||
t.pass()
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('router should treat hashes as slashes by default', function (t) {
|
||||
t.plan(1)
|
||||
var app = choo()
|
||||
var container = init('/account#security')
|
||||
app.route('/account/security', function (state, emit) {
|
||||
t.pass()
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('router should ignore hashes if hash is disabled', function (t) {
|
||||
t.plan(1)
|
||||
var app = choo({ hash: false })
|
||||
var container = init('/account#security')
|
||||
app.route('/account', function (state, emit) {
|
||||
t.pass()
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('cache should default to 100 instances', function (t) {
|
||||
t.plan(1)
|
||||
var app = choo()
|
||||
var container = init()
|
||||
app.route('/', function (state, emit) {
|
||||
for (var i = 0; i <= 100; i++) state.cache(Component, i)
|
||||
state.cache(Component, 0)
|
||||
return html`<div></div>`
|
||||
|
||||
function Component (id) {
|
||||
if (id < i) t.pass('oldest instance was pruned when exceeding 100')
|
||||
}
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('cache option should override number of max instances', function (t) {
|
||||
t.plan(1)
|
||||
var app = choo({ cache: 1 })
|
||||
var container = init()
|
||||
app.route('/', function (state, emit) {
|
||||
var instances = 0
|
||||
state.cache(Component, instances)
|
||||
state.cache(Component, instances)
|
||||
state.cache(Component, 0)
|
||||
return html`<div></div>`
|
||||
|
||||
function Component (id) {
|
||||
if (id < instances) t.pass('oldest instance was pruned when exceeding 1')
|
||||
instances++
|
||||
}
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('cache option should override default LRU cache', function (t) {
|
||||
t.plan(2)
|
||||
var cache = {
|
||||
get (Component, id) {
|
||||
t.pass('called get')
|
||||
},
|
||||
set (Component, id) {
|
||||
t.pass('called set')
|
||||
}
|
||||
}
|
||||
var app = choo({ cache: cache })
|
||||
var container = init()
|
||||
app.route('/', function (state, emit) {
|
||||
state.cache(Component, 'foo')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
|
||||
function Component () {}
|
||||
})
|
||||
|
||||
// built-in state
|
||||
|
||||
tape('state should include events', function (t) {
|
||||
t.plan(2)
|
||||
var app = choo()
|
||||
var container = init()
|
||||
app.route('/', function (state, emit) {
|
||||
t.ok(state.hasOwnProperty('events'), 'state has event property')
|
||||
t.ok(Object.keys(state.events).length > 0, 'events object has keys')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('state should include location on render', function (t) {
|
||||
t.plan(6)
|
||||
var app = choo()
|
||||
var container = init('/foo/bar/file.txt?bin=baz')
|
||||
app.route('/:first/:second/*', function (state, emit) {
|
||||
var params = { first: 'foo', second: 'bar', wildcard: 'file.txt' }
|
||||
t.equal(state.href, '/foo/bar/file.txt', 'state has href')
|
||||
t.equal(state.route, ':first/:second/*', 'state has route')
|
||||
t.ok(state.hasOwnProperty('params'), 'state has params')
|
||||
t.deepEqual(state.params, params, 'params match')
|
||||
t.ok(state.hasOwnProperty('query'), 'state has query')
|
||||
t.deepEqual(state.query, { bin: 'baz' }, 'query match')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('state should include location on store init', function (t) {
|
||||
t.plan(6)
|
||||
var app = choo()
|
||||
var container = init('/foo/bar/file.txt?bin=baz')
|
||||
app.use(store)
|
||||
app.route('/:first/:second/*', function (state, emit) {
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
|
||||
function store (state, emit) {
|
||||
var params = { first: 'foo', second: 'bar', wildcard: 'file.txt' }
|
||||
t.equal(state.href, '/foo/bar/file.txt', 'state has href')
|
||||
t.equal(state.route, ':first/:second/*', 'state has route')
|
||||
t.ok(state.hasOwnProperty('params'), 'state has params')
|
||||
t.deepEqual(state.params, params, 'params match')
|
||||
t.ok(state.hasOwnProperty('query'), 'state has query')
|
||||
t.deepEqual(state.query, { bin: 'baz' }, 'query match')
|
||||
}
|
||||
})
|
||||
|
||||
tape('state should include title', function (t) {
|
||||
t.plan(3)
|
||||
document.title = 'foo'
|
||||
var app = choo()
|
||||
var container = init()
|
||||
t.equal(app.state.title, 'foo', 'title is match')
|
||||
app.use(function (state, emitter) {
|
||||
emitter.on(state.events.DOMTITLECHANGE, function (title) {
|
||||
t.equal(state.title, 'bar', 'title is changed in state')
|
||||
t.equal(document.title, 'bar', 'title is changed in document')
|
||||
})
|
||||
})
|
||||
app.route('/', function (state, emit) {
|
||||
emit(state.events.DOMTITLECHANGE, 'bar')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
})
|
||||
|
||||
tape('state should include cache', function (t) {
|
||||
t.plan(6)
|
||||
var app = choo()
|
||||
var container = init()
|
||||
app.route('/', function (state, emit) {
|
||||
t.equal(typeof state.cache, 'function', 'state has cache method')
|
||||
var cached = state.cache(Component, 'foo', 'arg')
|
||||
t.equal(cached, state.cache(Component, 'foo'), 'consecutive calls return same instance')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.mount(container)
|
||||
|
||||
function Component (id, state, emit, arg) {
|
||||
t.equal(id, 'foo', 'id was prefixed to constructor args')
|
||||
t.equal(typeof state, 'object', 'state was prefixed to constructor args')
|
||||
t.equal(typeof emit, 'function', 'emit was prefixed to constructor args')
|
||||
t.equal(arg, 'arg', 'constructor args were forwarded')
|
||||
}
|
||||
})
|
||||
|
||||
// create application container and set location
|
||||
// (str?, str?) -> Element
|
||||
function init (location, type) {
|
||||
location = location ? location.split('#') : ['/', '']
|
||||
window.history.replaceState({}, document.title, location[0])
|
||||
window.location.hash = location[1] || ''
|
||||
var container = document.createElement(type || 'div')
|
||||
document.body.appendChild(container)
|
||||
return container
|
||||
}
|
||||
+17
-44
@@ -1,9 +1,9 @@
|
||||
var tape = require('tape')
|
||||
var h = require('hyperscript')
|
||||
|
||||
var html = require('./html')
|
||||
var raw = require('./html/raw')
|
||||
var choo = require('./')
|
||||
var html = require('../html')
|
||||
var raw = require('../html/raw')
|
||||
var choo = require('..')
|
||||
|
||||
tape('should render on the server with nanohtml', function (t) {
|
||||
var app = choo()
|
||||
@@ -168,45 +168,20 @@ tape('state should include events', function (t) {
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape('state should include params', function (t) {
|
||||
t.plan(4)
|
||||
var app = choo()
|
||||
app.route('/:resource/:id/*', function (state, emit) {
|
||||
t.ok(state.hasOwnProperty('params'), 'state has params property')
|
||||
t.equal(state.params.resource, 'users', 'resources param is users')
|
||||
t.equal(state.params.id, '1', 'id param is 1')
|
||||
t.equal(state.params.wildcard, 'docs/foo.txt', 'wildcard captures what remains')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.toString('/users/1/docs/foo.txt')
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape('state should include query', function (t) {
|
||||
t.plan(2)
|
||||
var app = choo()
|
||||
app.route('/', function (state, emit) {
|
||||
t.ok(state.hasOwnProperty('query'), 'state has query property')
|
||||
t.equal(state.query.page, '2', 'page querystring is 2')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.toString('/?page=2')
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape('state should include location on render', function (t) {
|
||||
t.plan(6)
|
||||
var app = choo()
|
||||
app.route('/:foo', function (state, emit) {
|
||||
t.equal(state.href, '/foo', 'state has href')
|
||||
t.equal(state.route, ':foo', 'state has route')
|
||||
app.route('/:first/:second/*', function (state, emit) {
|
||||
var params = { first: 'foo', second: 'bar', wildcard: 'file.txt' }
|
||||
t.equal(state.href, '/foo/bar/file.txt', 'state has href')
|
||||
t.equal(state.route, ':first/:second/*', 'state has route')
|
||||
t.ok(state.hasOwnProperty('params'), 'state has params')
|
||||
t.deepEqual(state.params, { foo: 'foo' }, 'params match')
|
||||
t.deepEqual(state.params, params, 'params match')
|
||||
t.ok(state.hasOwnProperty('query'), 'state has query')
|
||||
t.deepEqual(state.query, { bar: 'baz' }, 'query match')
|
||||
t.deepEqual(state.query, { bin: 'baz' }, 'query match')
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.toString('/foo?bar=baz')
|
||||
app.toString('/foo/bar/file.txt?bin=baz')
|
||||
t.end()
|
||||
})
|
||||
|
||||
@@ -214,24 +189,22 @@ tape('state should include location on store init', function (t) {
|
||||
t.plan(6)
|
||||
var app = choo()
|
||||
app.use(store)
|
||||
app.route('/:foo', function (state, emit) {
|
||||
app.route('/:first/:second/*', function (state, emit) {
|
||||
return html`<div></div>`
|
||||
})
|
||||
app.toString('/foo?bar=baz')
|
||||
app.toString('/foo/bar/file.txt?bin=baz')
|
||||
|
||||
function store (state, emit) {
|
||||
t.equal(state.href, '/foo', 'state has href')
|
||||
t.equal(state.route, ':foo', 'state has route')
|
||||
var params = { first: 'foo', second: 'bar', wildcard: 'file.txt' }
|
||||
t.equal(state.href, '/foo/bar/file.txt', 'state has href')
|
||||
t.equal(state.route, ':first/:second/*', 'state has route')
|
||||
t.ok(state.hasOwnProperty('params'), 'state has params')
|
||||
t.deepEqual(state.params, { foo: 'foo' }, 'params match')
|
||||
t.deepEqual(state.params, params, 'params match')
|
||||
t.ok(state.hasOwnProperty('query'), 'state has query')
|
||||
t.deepEqual(state.query, { bar: 'baz' }, 'query match')
|
||||
t.deepEqual(state.query, { bin: 'baz' }, 'query match')
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: Implement this using jsdom, as this only works when window is present
|
||||
tape.skip('state should include title', function (t) {})
|
||||
|
||||
tape('state should include cache', function (t) {
|
||||
t.plan(6)
|
||||
var app = choo()
|
||||
Reference in New Issue
Block a user