// choo.toStream(): web-standard streaming SSR with prefetch and lazy routes. import { test } from 'node:test' import assert from 'node:assert' import html from '@uhhm/buuh-html' import choo, { lazy } from '@uhhm/buuh' const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms)) async function readAll (stream) { const decoder = new TextDecoder() const chunks = [] for await (const chunk of stream) chunks.push(decoder.decode(chunk, { stream: true })) return chunks } test('toStream matches toString for a plain sync app', async () => { const makeApp = () => { const app = choo() app.route('/', (state) => html`

hi

`) return app } const chunks = await readAll(makeApp().toStream('/')) assert.strictEqual(chunks.join(''), makeApp().toString('/')) }) test('returns a web ReadableStream', () => { const app = choo() app.route('/', () => html`
`) assert.ok(app.toStream('/') instanceof ReadableStream) }) test('stores can defer rendering with state.prefetch promises', async () => { const app = choo() app.use((state) => { state.user = null state.prefetch.push(wait(10, null).then(() => { state.user = 'bendik' })) }) app.route('/', (state) => html`

hello ${state.user}

`) const chunks = await readAll(app.toStream('/')) assert.strictEqual(chunks.join(''), '

hello bendik

', 'render waited for prefetch') }) test('toString refuses prefetching stores with guidance', () => { const app = choo() app.use((state) => { state.prefetch.push(Promise.resolve()) }) app.route('/', () => html`
`) assert.throws(() => app.toString('/'), /toStream/) }) test('async template holes stream: shell first, slow content later', async () => { const app = choo() app.route('/', (state) => html`

shell

${wait(15, html`
slow
`)}`) const stream = app.toStream('/') const reader = stream.getReader() const decoder = new TextDecoder() const started = performance.now() const first = decoder.decode((await reader.read()).value) const firstAt = performance.now() - started assert.strictEqual(first, '

shell

', 'shell flushed immediately') assert.ok(firstAt < 10, `shell arrived before the slow hole resolved (${firstAt.toFixed(1)}ms)`) let rest = '' for (;;) { const { done, value } = await reader.read() if (done) break rest += decoder.decode(value, { stream: true }) } assert.strictEqual(rest, '
slow
') }) test('toStream awaits lazy route views; toString refuses them', async () => { const makeApp = () => { const app = choo() app.route('/', lazy(() => wait(10, { default: (state) => html`
lazy view
` }))) return app } assert.throws(() => makeApp().toString('/'), /toStream/) const chunks = await readAll(makeApp().toStream('/')) assert.strictEqual(chunks.join(''), '
lazy view
') }) test('stream errors propagate through the stream, not as sync throws', async () => { const app = choo() app.route('/', () => html`
${Promise.reject(new Error('boom'))}
`) await assert.rejects(async () => readAll(app.toStream('/')), /boom/) })