openpencil/tests/engine/io/dom-css-formats.test.ts
Danila Poyarkov 7a37f14327
refactor!: register HTML and Tailwind JSX as IO formats (#774)
* refactor!: register HTML and Tailwind JSX as IO formats

HTML and Tailwind JSX went around the IO registry: the CLI appended
`html` to its format list and had its own HTML and Tailwind export paths,
so the app's export options offered neither.

Core now registers `html` and `tailwind-jsx` adapters built on a new
browser-safe `@open-pencil/dom-css/export` entry. Export results can
carry assets written next to the main file, which covers standalone HTML
with external images and fonts, and the CLI writes every format the same
way. The CSS object model and Node file access load only when an export
needs them, so the app bundle stays free of the headless CSS runtime.

BREAKING CHANGE: `sceneNodesToTailwindJSX` and `designDocumentToTailwindJSX`
moved from `@open-pencil/dom-css/browser` to `@open-pencil/dom-css/export`.

* refactor(core): share export support and fixed-size options across IO formats

Five adapters export every target and six have no scale or quality
options; the new HTML and Tailwind JSX adapters repeated those blocks
again. Both are now named once and shared.

* fix(core): keep HTML asset paths relative for Windows output paths

The CLI passed the absolute output path as the export file name, and the
HTML adapter only split it on `/`, so on Windows the page referenced
absolute `C:\...\card.assets` paths and assets were written to a doubled
location. The CLI now passes the file name, and the adapter accepts
either separator.
2026-09-26 11:54:16 +04:00

92 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, test } from 'bun:test'
import { toUint8Array } from 'js-base64'
import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core/io'
import { SceneGraph } from '@open-pencil/scene-graph'
const io = new IORegistry(BUILTIN_IO_FORMATS)
function cardGraph() {
const graph = new SceneGraph()
const page = graph.getPages()[0]
const card = graph.createNode('FRAME', page.id, {
name: 'Card',
width: 120,
height: 80,
layoutMode: 'VERTICAL'
})
graph.createNode('TEXT', card.id, { name: 'Title', text: 'Hello', width: 100, height: 20 })
// A 1×1 PNG, exported as an external image file.
const png = toUint8Array(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
)
graph.images.set('pixel', png)
graph.createNode('RECTANGLE', card.id, {
name: 'Photo',
width: 40,
height: 40,
fills: [
{ type: 'IMAGE', imageHash: 'pixel', imageScaleMode: 'FILL', opacity: 1, visible: true }
]
})
return { graph, page, card }
}
describe('HTML export format', () => {
test('exports a node with its own box', async () => {
const { graph, card } = cardGraph()
const result = await io.exportContent('html', {
graph,
target: { scope: 'node', nodeId: card.id }
})
expect(result).toMatchObject({ format: 'html', extension: 'html', mimeType: 'text/html' })
expect(String(result.data)).toContain('width: 120px')
expect(String(result.data)).toContain('Hello')
})
test('writes external assets next to the file it was named after', async () => {
const { graph, page } = cardGraph()
const result = await io.exportContent(
'html',
{ graph, target: { scope: 'page', pageId: page.id }, fileName: 'out/card.html' },
{ html: 'standalone', assets: 'external' }
)
const paths = (result.assets ?? []).map((asset) => asset.path)
expect(paths.length).toBeGreaterThan(0)
for (const path of paths) expect(path).toStartWith('card.assets/')
for (const path of paths) expect(String(result.data)).toContain(path)
})
})
describe('HTML export asset paths', () => {
test('are relative for Windows paths too', async () => {
const { graph, page } = cardGraph()
const result = await io.exportContent(
'html',
{ graph, target: { scope: 'page', pageId: page.id }, fileName: 'C:\\out\\card.html' },
{ html: 'standalone', assets: 'external' }
)
const paths = (result.assets ?? []).map((asset) => asset.path)
expect(paths.length).toBeGreaterThan(0)
for (const path of paths) expect(path).toStartWith('card.assets/')
})
})
describe('Tailwind JSX export format', () => {
test('is a registered export next to OpenPencil JSX', async () => {
const { graph, card } = cardGraph()
const result = await io.exportContent('tailwind-jsx', {
graph,
target: { scope: 'selection', nodeIds: [card.id] }
})
expect(result).toMatchObject({ format: 'tailwind-jsx', extension: 'jsx' })
expect(String(result.data)).toStartWith('<div data-name="Card" className="flex flex-col')
expect(io.listExportFormats('page').map((format) => format.id)).toContain('tailwind-jsx')
})
})