import { expect, test, type Page } from '@playwright/test' import type { evaluateDesignJSX as EvaluateDesignJSX } from '@/app/code/sandbox/evaluate' import { CanvasHelper } from '#tests/helpers/canvas' test.beforeEach(async ({ page }) => { await page.goto('/') const canvas = new CanvasHelper(page) await canvas.waitForInit() }) async function evaluate(page: Page, source: string, timeoutMs = 1_000) { return page.evaluate( async ({ source, timeoutMs }) => { const modulePath = '/src/app/code/sandbox/evaluate.ts' const { evaluateDesignJSX } = (await import(/* @vite-ignore */ modulePath)) as { evaluateDesignJSX: typeof EvaluateDesignJSX } return evaluateDesignJSX(source, { timeoutMs }) }, { source, timeoutMs } ) } test('evaluates JSX into plain inert data', async ({ page }) => { const result = await evaluate( page, 'Hello' ) expect(result).toEqual({ ok: true, roots: [ { type: 'frame', props: { w: 320, fill: { __openPencilHelper: 'solid', args: ['#fff'] } }, children: [{ type: 'text', props: {}, children: ['Hello'] }] } ] }) }) test('supports local constants, function components, arrays, and conditionals', async ({ page }) => { const result = await evaluate( page, `const colors = { surface: '#fff' } const Card = ({ title, visible }) => ( {visible ? {title} : null} {[1, 2].map((item) => {item})} ) ` ) expect(result).toEqual({ ok: true, roots: [ { type: 'frame', props: { fill: '#fff' }, children: [ { type: 'text', props: {}, children: ['Hello'] }, { type: 'text', props: {}, children: [1] }, { type: 'text', props: {}, children: [2] } ] } ] }) }) test('converts variable helpers without executing host capabilities', async ({ page }) => { const result = await evaluate( page, `const vars = defineVars({ primary: { name: 'Primary', value: '#ff0000' } }) Hello` ) expect(result.ok).toBe(true) if (!result.ok) return expect(result.roots[0]).toMatchObject({ type: 'frame', props: { fill: { __openPencilHelper: 'designVar' } } }) }) test('supports multiple roots through fragments', async ({ page }) => { const result = await evaluate(page, '<>') expect(result).toEqual({ ok: true, roots: [ { type: 'rectangle', props: { name: 'One' }, children: [] }, { type: 'ellipse', props: { name: 'Two' }, children: [] } ] }) }) test('cannot access the parent or application origin', async ({ page }) => { const result = await evaluate(page, '{String(window.parent.document)}') expect(result.ok).toBe(false) if (!result.ok) expect(result.error).toMatch(/window is not defined|Can't find variable: window/) }) test('blocks network requests from evaluated code', async ({ page }) => { const requests: string[] = [] page.on('request', (request) => { if (request.url().includes('sandbox-probe.invalid')) requests.push(request.url()) }) const result = await evaluate( page, '{fetch("https://sandbox-probe.invalid/leak")}' ) expect(result.ok).toBe(false) expect(requests).toEqual([]) }) test('terminates accidental infinite loops without freezing the host', async ({ page }) => { const started = Date.now() const result = await evaluate(page, '(() => { while (true) {} })()', 100) expect(result).toEqual({ ok: false, error: 'Design JSX execution timed out.' }) expect(Date.now() - started).toBeLessThan(2_000) await expect(page.getByTestId('editor-root')).toBeVisible() }) test('rejects oversized source and output', async ({ page }) => { const sourceResult = await page.evaluate(async () => { const modulePath = '/src/app/code/sandbox/evaluate.ts' const { evaluateDesignJSX } = (await import(/* @vite-ignore */ modulePath)) as { evaluateDesignJSX: typeof EvaluateDesignJSX } return evaluateDesignJSX('too large', { sourceBytes: 4 }) }) expect(sourceResult).toEqual({ ok: false, error: 'Design JSX source is too large.' }) const outputResult = await page.evaluate(async () => { const modulePath = '/src/app/code/sandbox/evaluate.ts' const { evaluateDesignJSX } = (await import(/* @vite-ignore */ modulePath)) as { evaluateDesignJSX: typeof EvaluateDesignJSX } return evaluateDesignJSX('{"x".repeat(100)}', { outputBytes: 20 }) }) expect(outputResult).toEqual({ ok: false, error: 'Design JSX output is too large.' }) const arrayResult = await page.evaluate(async () => { const modulePath = '/src/app/code/sandbox/evaluate.ts' const { evaluateDesignJSX } = (await import(/* @vite-ignore */ modulePath)) as { evaluateDesignJSX: typeof EvaluateDesignJSX } return evaluateDesignJSX( '{Array.from({ length: 10 }, (_, i) => {i})}', { arrayLength: 5 } ) }) expect(arrayResult).toEqual({ ok: false, error: 'Design JSX output contains an array that is too long.' }) })