// @vitest-environment jsdom /** Bootstrap serialization and resource creation in the receiving document's environment. */ import { afterEach, describe, expect, it, vi } from 'vitest' import { runInNewContext } from 'node:vm' import { createHtmlDocument } from '../src/client/html/bootstrap.ts' import { decodeText, encodeText } from '../src/client/html/bytes.ts' afterEach(() => { vi.restoreAllMocks() }) function run(html: string) { const open = vi.fn() const write = vi.fn<(html: string) => void>() const close = vi.fn() const createObjectURL = vi.fn<(blob: Blob) => string>().mockImplementation(() => `blob:null/resource-${createObjectURL.mock.calls.length}`) const script = html.slice(html.indexOf('')) runInNewContext(script, { document: { open, write, close }, URL: { createObjectURL }, Blob, DOMParser, atob, TextDecoder, }) return { open, write, close, createObjectURL, document: new DOMParser().parseFromString(write.mock.calls[0]![0], 'text/html') } } const utf8 = (text: string): Uint8Array => new TextEncoder().encode(text) describe('HTML bootstrap', () => { it('carries UTF-8 and script-ending text as data, then writes the unchanged complete HTML', () => { const source = '你好

文本

"' const html = createHtmlDocument({ data: utf8(source), assets: [] }) expect(html.match(/<\/script>/gu)).toHaveLength(1) expect(html).not.toContain('你好') const result = run(html) expect(result.write).toHaveBeenCalledExactlyOnceWith(source) expect(result.open).toHaveBeenCalledOnce() expect(result.close).toHaveBeenCalledOnce() expect(result.createObjectURL).not.toHaveBeenCalled() }) it('creates local resources in the frame and preserves base, inline code and script order attributes', () => { const source = '' const result = run(createHtmlDocument({ data: utf8(source), assets: [ { kind: 'script', reference: './one.js', data: utf8('window.first=1') }, { kind: 'stylesheet', reference: './main.css', data: utf8('body{color:red}') }, ] })) expect(result.createObjectURL).toHaveBeenCalledTimes(2) expect(result.createObjectURL.mock.calls.map(([blob]) => blob.type)).toEqual(['application/javascript', 'text/css']) const scripts = [...result.document.querySelectorAll('script')] expect(scripts.map(script => script.getAttribute('src'))).toEqual(['blob:null/resource-1', null, 'blob:null/resource-1', 'https://example.invalid/remote.js']) expect(scripts[1]?.textContent).toBe('window.next=1') expect(scripts[2]?.defer).toBe(true) expect(scripts[3]?.hasAttribute('async')).toBe(true) expect(result.document.querySelector('link')?.getAttribute('href')).toBe('blob:null/resource-2') expect(result.document.querySelector('base')?.getAttribute('href')).toBe('https://example.invalid/assets/') expect(result.document.documentElement.lang).toBe('en') }) it('rejects non-UTF-8 root and asset bytes before creating an iframe document', () => { expect(() => createHtmlDocument({ data: utf8('

root

'), assets: [{ kind: 'script', reference: 'bad.js', data: new Uint8Array([255]) }] })).toThrow() expect(() => createHtmlDocument({ data: new Uint8Array([255]), assets: [] })).toThrow() expect(decodeText(utf8('雪\u2028\u2029'))).toBe('雪\u2028\u2029') }) it('base64-encodes a large UTF-8 payload in browser-safe chunks', () => { const source = `${'0123456789abcdef'.repeat(16_384)}雪` const bytes = Uint8Array.from(atob(encodeText(source)), character => character.charCodeAt(0)) expect(decodeText(bytes)).toBe(source) }) })