diff --git a/tests/engine/cli/dom.test.ts b/tests/engine/cli/dom.test.ts new file mode 100644 index 000000000..52b473063 --- /dev/null +++ b/tests/engine/cli/dom.test.ts @@ -0,0 +1,186 @@ +import { expect, setDefaultTimeout, test } from 'bun:test' +import { mkdtemp } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +import { parseFigFile } from '@open-pencil/core/io' +import type { SceneNode } from '@open-pencil/core/scene-graph' + +import { cliSourcePath } from '#tests/helpers/paths' + +setDefaultTimeout(30_000) + +const CLI = cliSourcePath('index.ts') + +interface CommandResult { + stdout: string + stderr: string + exitCode: number +} + +async function run(args: string[]): Promise { + const proc = Bun.spawn(['bun', CLI, ...args], { + stdout: 'pipe', + stderr: 'pipe' + }) + const [stdout, stderr] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text() + ]) + const exitCode = await proc.exited + return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode } +} + +async function createFixture() { + const dir = await mkdtemp(join(tmpdir(), 'open-pencil-dom-cli-')) + const htmlPath = join(dir, 'card.html') + const cssPath = join(dir, 'card.css') + + await Bun.write( + htmlPath, + ` +
+

DOM/CSS card

+

Imported from HTML and CSS.

+
+ ` + ) + await Bun.write( + cssPath, + ` + .card { + display: flex; + flex-direction: column; + gap: 8px; + width: 240px; + padding: 24px; + background: #ffffff; + border: 1px solid #dddddd; + border-radius: 16px; + } + h1 { + font-size: 24px; + } + ` + ) + + return { dir, htmlPath, cssPath } +} + +function findNode(nodes: Iterable, name: string): SceneNode | undefined { + for (const node of nodes) { + if (node.name === name) return node + } +} + +test('dom CLI writes DesignDOM JSON output', async () => { + const { htmlPath, cssPath, dir } = await createFixture() + const output = join(dir, 'card.json') + + const { stdout, stderr, exitCode } = await run([ + 'dom', + htmlPath, + '--css', + cssPath, + '--format', + 'json', + '--output', + output, + '--json' + ]) + + expect(stderr).toBe('') + expect(exitCode).toBe(0) + + const summary = JSON.parse(stdout) + expect(summary).toMatchObject({ format: 'json', output, pages: 1, rootElements: 1 }) + + const document = JSON.parse(await Bun.file(output).text()) + expect(document.children[0].tagName).toBe('article') + expect(document.children[0].computedStyle.display).toBe('flex') + expect(document.children[0].computedStyle.width).toBe('240px') +}) + +test('dom CLI writes serialized HTML output', async () => { + const { htmlPath, cssPath, dir } = await createFixture() + const output = join(dir, 'card.out.html') + + const { stderr, exitCode } = await run([ + 'dom', + htmlPath, + '--css', + cssPath, + '--format', + 'html', + '--output', + output + ]) + + expect(stderr).toBe('') + expect(exitCode).toBe(0) + + const html = await Bun.file(output).text() + expect(html).toContain('DOM/CSS card') + expect(html).toContain('class="card"') +}) + +test('dom CLI writes a .fig that core IO can import', async () => { + const { htmlPath, cssPath, dir } = await createFixture() + const output = join(dir, 'card.fig') + + const { stdout, stderr, exitCode } = await run([ + 'dom', + htmlPath, + '--css', + cssPath, + '--output', + output, + '--json' + ]) + + expect(stderr).toBe('') + expect(exitCode).toBe(0) + expect(JSON.parse(stdout)).toMatchObject({ format: 'fig', output, pages: 1, rootElements: 1 }) + + const bytes = new Uint8Array(await Bun.file(output).arrayBuffer()) + const graph = await parseFigFile(bytes) + const nodes = [...graph.nodes.values()] + const card = findNode(nodes, 'card') + const title = findNode(nodes, 'DOM/CSS card') + + expect(graph.getPages()).toHaveLength(1) + expect(card?.type).toBe('FRAME') + expect(title?.type).toBe('TEXT') +}) + +test('dom CLI compiles Tailwind candidates before import', async () => { + const dir = await mkdtemp(join(tmpdir(), 'open-pencil-dom-cli-tailwind-')) + const htmlPath = join(dir, 'tailwind.html') + const output = join(dir, 'tailwind.json') + const classes = ['flex', 'flex-col', 'gap-2', 'w-60', 'p-6', 'rounded-xl', 'bg-white'] + + await Bun.write( + htmlPath, + `

Tailwind card

` + ) + + const { stdout, stderr, exitCode } = await run([ + 'dom', + htmlPath, + '--tailwind', + classes.join(' '), + '--format', + 'json', + '--output', + output, + '--json' + ]) + + expect(stderr).toBe('') + expect(exitCode).toBe(0) + expect(JSON.parse(stdout)).toMatchObject({ format: 'json', output }) + + const document = JSON.parse(await Bun.file(output).text()) + expect(document.children[0].computedStyle.display).toBe('flex') + expect(document.children[0].computedStyle.width).toBe('240px') +})