/** * HtmlPreviewFrame delivery tests — the piece that exists ONLY because of the * DSH Desktop renderer fence: the host refuses `/sidebar/html/…` to any * request without the Electron renderer capability, which a frame sandboxed * without allow-same-origin (an opaque origin) can never carry. The document * is therefore fetched by the MAIN frame and handed over through `srcdoc`, * with the sandbox attribute untouched. * * Mounted client-side (createRoot + act) because the delivery path is an * effect; the sandbox-contract assertions live in sandbox-views.spec.tsx. */ // @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from 'vitest' import { createElement } from 'react' import { act } from 'react-dom/test-utils' import { HtmlPreviewFrame } from '../src/client/HtmlPreviewFrame.tsx' import { HTML_IFRAME_SANDBOX } from '../src/client/html-preview.ts' import { renderRoot, setupReactAct } from './test-utils.ts' setupReactAct() const URL_ = '/sidebar/html/s1/p/a/index.html' const DOC = 't

hi

' /** Stub the global fetch with one canned response. */ function stubFetch(text: string, ok = true): void { vi.stubGlobal('fetch', vi.fn(async () => ({ ok, status: ok ? 200 : 403, text: async () => text }))) } /** Flush the fetch's microtasks + the React commit they trigger. */ async function flush(): Promise { await act(async () => { await new Promise(resolve => { setTimeout(resolve, 0) }) }) } afterEach(() => { vi.unstubAllGlobals() }) describe('HtmlPreviewFrame delivery', () => { it('starts empty, never pointing the frame at the route itself', () => { stubFetch(DOC) const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'index.html' })) const iframe = r.container.querySelector('iframe')! expect(iframe.getAttribute('srcdoc')).toBe('') expect(iframe.getAttribute('src')).toBe(null) // The boundary is unchanged while the document is in flight. expect(iframe.getAttribute('sandbox')).toBe(HTML_IFRAME_SANDBOX) r.unmount() }) it('hands the fetched markup to the frame through srcdoc, base-rewritten, sandbox kept', async () => { stubFetch(DOC) const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'index.html' })) await flush() const iframe = r.container.querySelector('iframe')! const srcdoc = iframe.getAttribute('srcdoc') expect(srcdoc).toContain('') expect(srcdoc).toContain('

hi

') // Still no route src: the opaque frame must not be asked to fetch. expect(iframe.getAttribute('src')).toBe(null) expect(iframe.getAttribute('sandbox')).toBe(HTML_IFRAME_SANDBOX) expect(HTML_IFRAME_SANDBOX).not.toContain('allow-same-origin') r.unmount() }) it('drops the sandbox attribute for the warned unlock path (the frame inherits the GUI origin)', async () => { stubFetch(DOC) const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: false, title: 'index.html' })) await flush() const iframe = r.container.querySelector('iframe')! expect(iframe.getAttribute('sandbox')).toBe(null) expect(iframe.getAttribute('srcdoc')).toContain('

hi

') r.unmount() }) it('falls back to the route URL when the fetch fails, so the route still answers', async () => { stubFetch('', false) const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'index.html' })) await flush() const iframe = r.container.querySelector('iframe')! expect(iframe.getAttribute('src')).toBe(URL_) expect(iframe.getAttribute('srcdoc')).toBe(null) r.unmount() }) it('refetches when the previewed file changes', async () => { stubFetch('

one

') const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'a.html' })) await flush() expect(r.container.querySelector('iframe')!.getAttribute('srcdoc')).toContain('

one

') stubFetch('

two

') r.rerender(createElement(HtmlPreviewFrame, { url: '/sidebar/html/s1/p/a/two.html', sandboxed: true, title: 'b.html' })) await flush() expect(r.container.querySelector('iframe')!.getAttribute('srcdoc')).toContain('

two

') r.unmount() }) })