openpencil/tests/engine/font-normalize-export.test.ts
Shai Rubinstein 5139836816 fix: default TEXT nodes to solid black fill
TEXT nodes created via SceneGraph.createNode() defaulted to empty
fills, causing text to be invisible when .fig files are opened in
Figma. Now defaults to a solid black fill matching Figma's behavior.

Closes #133
2026-03-24 23:47:56 +03:00

152 lines
4.2 KiB
TypeScript

import { describe, test, expect, beforeAll } from 'bun:test'
import {
exportFigFile,
parseFigFile,
initCodec,
SceneGraph,
} from '@open-pencil/core'
beforeAll(async () => {
await initCodec()
})
function pageId(graph: SceneGraph) {
return graph.getPages()[0].id
}
describe('Font family normalization on .fig export', () => {
test('strips optical size suffix from font family', async () => {
const graph = new SceneGraph()
graph.createNode('TEXT', pageId(graph), {
name: 'Test',
x: 0,
y: 0,
width: 100,
height: 20,
text: 'Hello',
fontFamily: 'DM Sans 9pt',
fontWeight: 400,
fontSize: 14,
})
const exported = await exportFigFile(graph)
const reimported = await parseFigFile(exported.buffer as ArrayBuffer)
const nodes = [...reimported.nodes.values()]
const textNode = nodes.find((n) => n.type === 'TEXT')!
expect(textNode.fontFamily).toBe('DM Sans')
})
test('preserves normal font family names', async () => {
const graph = new SceneGraph()
graph.createNode('TEXT', pageId(graph), {
name: 'Test',
x: 0,
y: 0,
width: 100,
height: 20,
text: 'Hello',
fontFamily: 'Inter',
fontWeight: 400,
fontSize: 14,
})
const exported = await exportFigFile(graph)
const reimported = await parseFigFile(exported.buffer as ArrayBuffer)
const nodes = [...reimported.nodes.values()]
const textNode = nodes.find((n) => n.type === 'TEXT')!
expect(textNode.fontFamily).toBe('Inter')
})
test('strips Variable suffix from font family', async () => {
const graph = new SceneGraph()
graph.createNode('TEXT', pageId(graph), {
name: 'Test',
x: 0,
y: 0,
width: 100,
height: 20,
text: 'Hello',
fontFamily: 'Roboto Variable',
fontWeight: 400,
fontSize: 14,
})
const exported = await exportFigFile(graph)
const reimported = await parseFigFile(exported.buffer as ArrayBuffer)
const nodes = [...reimported.nodes.values()]
const textNode = nodes.find((n) => n.type === 'TEXT')!
expect(textNode.fontFamily).toBe('Roboto')
})
})
describe('TEXT node default fill', () => {
test('TEXT node without explicit fills exports with solid black fill', async () => {
const graph = new SceneGraph()
graph.createNode('TEXT', pageId(graph), {
name: 'No Fill Specified',
x: 0,
y: 0,
width: 100,
height: 20,
text: 'Hello',
fontFamily: 'Inter',
fontWeight: 400,
fontSize: 14,
})
const exported = await exportFigFile(graph)
const reimported = await parseFigFile(exported.buffer as ArrayBuffer)
const nodes = [...reimported.nodes.values()]
const textNode = nodes.find((n) => n.type === 'TEXT')!
expect(textNode.fills.length).toBe(1)
expect(textNode.fills[0].type).toBe('SOLID')
expect(textNode.fills[0].color.r).toBe(0)
expect(textNode.fills[0].color.g).toBe(0)
expect(textNode.fills[0].color.b).toBe(0)
expect(textNode.fills[0].color.a).toBe(1)
})
test('TEXT node with explicit fill preserves it', async () => {
const graph = new SceneGraph()
graph.createNode('TEXT', pageId(graph), {
name: 'Gold Text',
x: 0,
y: 0,
width: 100,
height: 20,
text: 'Hello',
fontFamily: 'Inter',
fontWeight: 400,
fontSize: 14,
fills: [{ type: 'SOLID', color: { r: 0.96, g: 0.72, b: 0, a: 1 }, opacity: 1, visible: true }],
})
const exported = await exportFigFile(graph)
const reimported = await parseFigFile(exported.buffer as ArrayBuffer)
const nodes = [...reimported.nodes.values()]
const textNode = nodes.find((n) => n.type === 'TEXT')!
expect(textNode.fills.length).toBe(1)
expect(textNode.fills[0].color.r).toBeCloseTo(0.96, 1)
expect(textNode.fills[0].color.g).toBeCloseTo(0.72, 1)
expect(textNode.fills[0].color.b).toBeCloseTo(0, 1)
})
test('non-TEXT node still defaults to empty fills', async () => {
const graph = new SceneGraph()
const node = graph.createNode('RECTANGLE', pageId(graph), {
name: 'Rect',
x: 0,
y: 0,
width: 100,
height: 100,
})
expect(graph.getNode(node.id)!.fills.length).toBe(0)
})
})