Files
buuh/packages/core/test/core.test.js
T

268 lines
8.5 KiB
JavaScript
Raw Normal View History

// Ported from choo 7.1.0 test/node.js (MIT), tape → node:test.
// The hyperscript case now uses a plain object with outerHTML, which is the
// contract toString actually relies on.
import { test } from 'node:test'
import assert from 'node:assert'
import html from '@choojs/html'
import raw from '@choojs/html/raw'
import choo, { Choo } from '@choojs/core'
test('should render on the server with @choojs/html', () => {
const app = choo()
app.route('/', function (state, emit) {
const strong = '<strong>Hello filthy planet</strong>'
return html`
<p>${raw(strong)}</p>
`
})
const res = app.toString('/')
const exp = '<p><strong>Hello filthy planet</strong></p>'
assert.strictEqual(res.toString().trim(), exp, 'result was OK')
})
test('should render on the server with any view returning outerHTML', () => {
const app = choo()
app.route('/', function (state, emit) {
return { outerHTML: '<p><strong>Hello filthy planet</strong></p>', nodeName: 'P' }
})
const res = app.toString('/')
const exp = '<p><strong>Hello filthy planet</strong></p>'
assert.strictEqual(res.toString().trim(), exp, 'result was OK')
})
test('should expose a public API', () => {
const app = choo()
assert.strictEqual(typeof app.route, 'function', 'app.route prototype method exists')
assert.strictEqual(typeof app.toString, 'function', 'app.toString prototype method exists')
assert.strictEqual(typeof app.start, 'function', 'app.start prototype method exists')
assert.strictEqual(typeof app.mount, 'function', 'app.mount prototype method exists')
assert.strictEqual(typeof app.emitter, 'object', 'app.emitter prototype method exists')
assert.strictEqual(typeof app.emit, 'function', 'app.emit instance method exists')
assert.strictEqual(typeof app.router, 'object', 'app.router instance object exists')
assert.strictEqual(typeof app.state, 'object', 'app.state instance object exists')
})
test('the default export works with and without new, and instances are Choo', () => {
assert.ok(choo() instanceof Choo, 'choo() returns a Choo instance')
assert.ok(new Choo() instanceof Choo, 'new Choo() works')
})
test('should enable history and href by default', () => {
const app = choo()
assert.ok(app._historyEnabled, 'history enabled')
assert.ok(app._hrefEnabled, 'href enabled')
})
test('router should pass state and emit to view', () => {
let calls = 0
const app = choo()
app.route('/', function (state, emit) {
assert.strictEqual(typeof state, 'object', 'state is an object')
assert.strictEqual(typeof emit, 'function', 'emit is a function')
calls++
return html`<div></div>`
})
app.toString('/')
assert.strictEqual(calls, 1, 'view was rendered')
})
test('router should support a default route', () => {
let calls = 0
const app = choo()
app.route('*', function (state, emit) {
calls++
return html`<div></div>`
})
app.toString('/random')
assert.strictEqual(calls, 1, 'default route was rendered')
})
test('enabling hash routing should treat hashes as slashes', () => {
let calls = 0
const app = choo({ hash: true })
app.route('/account/security', function (state, emit) {
calls++
return html`<div></div>`
})
app.toString('/account#security')
assert.strictEqual(calls, 1, 'hash route was rendered')
})
test('router should ignore hashes by default', () => {
let calls = 0
const app = choo()
app.route('/account', function (state, emit) {
calls++
return html`<div></div>`
})
app.toString('/account#security')
assert.strictEqual(calls, 1, 'route was rendered')
})
test('cache should default to 100 instances', () => {
let pruned = 0
const app = choo()
app.route('/', function (state, emit) {
let i
for (i = 0; i <= 100; i++) state.cache(Component, i)
state.cache(Component, 0)
return html`<div></div>`
function Component (id) {
if (id < i) pruned++
}
})
app.toString('/')
assert.strictEqual(pruned, 1, 'oldest instance was pruned when exceeding 100')
})
test('cache option should override number of max instances', () => {
let pruned = 0
const app = choo({ cache: 1 })
app.route('/', function (state, emit) {
let instances = 0
state.cache(Component, instances)
state.cache(Component, instances)
state.cache(Component, 0)
return html`<div></div>`
function Component (id) {
if (id < instances) pruned++
instances++
}
})
app.toString('/')
assert.strictEqual(pruned, 1, 'oldest instance was pruned when exceeding 1')
})
test('cache option should override default LRU cache', () => {
let gets = 0
let sets = 0
const cache = {
get (id) { gets++ },
set (id, el) { sets++ }
}
const app = choo({ cache })
app.route('/', function (state, emit) {
state.cache(Component, 'foo')
return html`<div></div>`
})
app.toString('/')
assert.strictEqual(gets, 1, 'called get')
assert.strictEqual(sets, 1, 'called set')
function Component () {}
})
// built-in state
test('state should include events', () => {
let checked = false
const app = choo()
app.route('/', function (state, emit) {
assert.ok(Object.hasOwn(state, 'events'), 'state has events property')
assert.ok(Object.keys(state.events).length > 0, 'events object has keys')
checked = true
return html`<div></div>`
})
app.toString('/')
assert.ok(checked)
})
test('state should include location on render', () => {
let checked = false
const app = choo()
app.route('/:first/:second/*', function (state, emit) {
const params = { first: 'foo', second: 'bar', wildcard: 'file.txt' }
assert.strictEqual(state.href, '/foo/bar/file.txt', 'state has href')
assert.strictEqual(state.route, ':first/:second/*', 'state has route')
assert.ok(Object.hasOwn(state, 'params'), 'state has params')
assert.deepStrictEqual(state.params, params, 'params match')
assert.ok(Object.hasOwn(state, 'query'), 'state has query')
assert.deepStrictEqual(state.query, { bin: 'baz' }, 'query match')
checked = true
return html`<div></div>`
})
app.toString('/foo/bar/file.txt?bin=baz')
assert.ok(checked)
})
test('state should include location on store init', () => {
let checked = false
const app = choo()
app.use(store)
app.route('/:first/:second/*', function (state, emit) {
return html`<div></div>`
})
app.toString('/foo/bar/file.txt?bin=baz')
assert.ok(checked)
function store (state, emit) {
const params = { first: 'foo', second: 'bar', wildcard: 'file.txt' }
assert.strictEqual(state.href, '/foo/bar/file.txt', 'state has href')
assert.strictEqual(state.route, ':first/:second/*', 'state has route')
assert.ok(Object.hasOwn(state, 'params'), 'state has params')
assert.deepStrictEqual(state.params, params, 'params match')
assert.ok(Object.hasOwn(state, 'query'), 'state has query')
assert.deepStrictEqual(state.query, { bin: 'baz' }, 'query match')
checked = true
}
})
test('state should include cache', () => {
let constructed = 0
const app = choo()
app.route('/', function (state, emit) {
assert.strictEqual(typeof state.cache, 'function', 'state has cache method')
const cached = state.cache(Component, 'foo', 'arg')
assert.strictEqual(cached, state.cache(Component, 'foo'), 'consecutive calls return same instance')
return html`<div></div>`
})
app.toString('/')
assert.strictEqual(constructed, 1, 'component constructed once')
function Component (id, state, emit, arg) {
assert.strictEqual(id, 'foo', 'id was prefixed to constructor args')
assert.strictEqual(typeof state, 'object', 'state was prefixed to constructor args')
assert.strictEqual(typeof emit, 'function', 'emit was prefixed to constructor args')
assert.strictEqual(arg, 'arg', 'constructor args were forwarded')
constructed++
}
})
test('state should not mutate on toString', () => {
const app = choo()
app.use(store)
const routes = ['foo', 'bar']
const states = routes.map(function (route) {
const state = {}
app.route(`/${route}`, view)
app.toString(`/${route}`, state)
return state
})
for (let i = 0; i < routes.length; i++) {
assert.strictEqual(states[i].test, routes[i], 'store was used')
assert.strictEqual(states[i].title, routes[i], 'title was added to state')
}
function store (state, emitter) {
state.test = null
emitter.on('test', function (str) {
assert.strictEqual(state.test, null, 'state has been reset')
state.test = str
})
}
function view (state, emit) {
emit('test', state.route)
emit(state.events.DOMTITLECHANGE, state.route)
return html`<body>Hello ${state.route}</body>`
}
})