Files

79 lines
3.0 KiB
JavaScript
Raw Permalink Normal View History

// Async holes and streaming in the server tag.
import { test } from 'node:test'
import assert from 'node:assert'
import html from '@uhhm/buuh-html'
import raw from '@uhhm/buuh-html/raw'
async function collect (chunks) {
const out = []
for await (const chunk of chunks) out.push(chunk)
return out
}
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms))
test('sync templates stream as a single chunk equal to toString()', async () => {
const res = html`<p>hello ${'world'}</p>`
const chunks = await collect(res)
assert.deepStrictEqual(chunks, ['<p>hello world</p>'])
assert.strictEqual(chunks.join(''), res.toString())
})
test('a promise child splits the stream at the hole', async () => {
const res = html`<main><h1>shell</h1>${wait(10, 'late')}<footer>end</footer></main>`
const chunks = await collect(res)
assert.strictEqual(chunks[0], '<main><h1>shell</h1>', 'everything before the hole flushes first')
assert.strictEqual(chunks.join(''), '<main><h1>shell</h1>late<footer>end</footer></main>')
})
test('toString() on async content throws with streaming guidance', () => {
const res = html`<p>${Promise.resolve('x')}</p>`
assert.throws(() => res.toString(), /toStream/)
})
test('resolved promises can carry templates, arrays, and raw', async () => {
const res = html`<ul>${wait(5, [html`<li>a</li>`, html`<li>${'<b>'}</li>`, raw('<li>raw</li>')])}</ul>`
const chunks = await collect(res)
assert.strictEqual(chunks.join(''), '<ul><li>a</li><li>&lt;b&gt;</li><li>raw</li></ul>')
})
test('nested templates with async holes stream through their parents', async () => {
const inner = html`<section>${wait(5, 'inner-late')}</section>`
const res = html`<div><p>early</p>${inner}</div>`
const chunks = await collect(res)
assert.strictEqual(chunks[0], '<div><p>early</p><section>', 'parent flushes up to the nested hole')
assert.strictEqual(chunks.join(''), '<div><p>early</p><section>inner-late</section></div>')
})
test('async iterable children stream chunk by chunk', async () => {
async function * rows () {
for (let i = 0; i < 3; i++) yield html`<li>${i}</li>`
}
const res = html`<ul>${rows()}</ul>`
const chunks = await collect(res)
assert.strictEqual(chunks.join(''), '<ul><li>0</li><li>1</li><li>2</li></ul>')
assert.ok(chunks.length >= 4, 'each yielded row is its own chunk')
})
test('multiple async holes resolve in document order', async () => {
// the second promise resolves first; output order must follow the document
const res = html`<div>${wait(20, 'A')}|${wait(5, 'B')}</div>`
const chunks = await collect(res)
assert.strictEqual(chunks.join(''), '<div>A|B</div>')
})
test('async values in attribute position throw immediately', () => {
assert.throws(
() => html`<div class=${Promise.resolve('x')}></div>`,
/child position/
)
})
test('resolved async strings are escaped like any child', async () => {
const res = html`<p>${wait(5, '<script>')}</p>`
const chunks = await collect(res)
assert.strictEqual(chunks.join(''), '<p>&lt;script&gt;</p>')
})