openpencil/tests/engine/render/jsx/text-props.test.ts
2026-05-19 17:57:43 +03:00

150 lines
5.5 KiB
TypeScript

import { describe, expect, it } from 'bun:test'
import { renderJSX, sceneNodeToJSX } from '@open-pencil/core'
import { getNodeOrThrow, childIdAt } from '#tests/helpers/assert'
import { makeSceneGraph } from '#tests/helpers/scene'
describe('text props round-trip', () => {
it('lineHeight renders and exports', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text color="#000" lineHeight={24}>Hello</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.lineHeight).toBe(24)
const jsx = sceneNodeToJSX(n.id, g)
expect(jsx).toContain('lineHeight={24}')
})
it('letterSpacing renders and exports', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text color="#000" letterSpacing={2}>Spaced</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.letterSpacing).toBe(2)
const jsx = sceneNodeToJSX(n.id, g)
expect(jsx).toContain('letterSpacing={2}')
})
it('textDecoration renders and exports', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text color="#000" textDecoration="underline">Link</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.textDecoration).toBe('UNDERLINE')
const jsx = sceneNodeToJSX(n.id, g)
expect(jsx).toContain('textDecoration="underline"')
})
it('textCase renders and exports', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text color="#000" textCase="upper">label</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.textCase).toBe('UPPER')
const jsx = sceneNodeToJSX(n.id, g)
expect(jsx).toContain('textCase="upper"')
})
it('maxLines renders with truncation', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text color="#000" maxLines={2}>Long text here</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.maxLines).toBe(2)
expect(n.textTruncation).toBe('ENDING')
const jsx = sceneNodeToJSX(n.id, g)
expect(jsx).toContain('maxLines={2}')
})
it('truncate without maxLines', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text color="#000" truncate>Overflow</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.textTruncation).toBe('ENDING')
const jsx = sceneNodeToJSX(n.id, g)
expect(jsx).toContain('truncate')
})
it('accepts Figma-style text aliases', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(
g,
'<Text text="Alias text" fontSize={24} fontFamily="Inter" fontWeight={700} fill="#123456" textHorizontalAlignment="CENTER" textVerticalAlignment="CENTER" />'
)
const n = getNodeOrThrow(g, result.id)
expect(n.text).toBe('Alias text')
expect(n.fontSize).toBe(24)
expect(n.fontFamily).toBe('Inter')
expect(n.fontWeight).toBe(700)
expect(n.textAlignHorizontal).toBe('CENTER')
expect(n.textAlignVertical).toBe('CENTER')
expect(n.fills[0]?.type).toBe('SOLID')
})
it('accepts characters as a text content alias', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text characters="Characters text" color="#000" />')
const n = getNodeOrThrow(g, result.id)
expect(n.text).toBe('Characters text')
})
it('prefers text children over text prop alias', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text text="Prop text" color="#000">Child text</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.text).toBe('Child text')
})
it('defaults omit text props', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(g, '<Text color="#000">Plain</Text>')
const n = getNodeOrThrow(g, result.id)
expect(n.lineHeight).toBeNull()
expect(n.letterSpacing).toBe(0)
expect(n.textDecoration).toBe('NONE')
expect(n.textCase).toBe('ORIGINAL')
expect(n.maxLines).toBeNull()
const jsx = sceneNodeToJSX(n.id, g)
expect(jsx).not.toContain('lineHeight')
expect(jsx).not.toContain('letterSpacing')
expect(jsx).not.toContain('textDecoration')
expect(jsx).not.toContain('textCase')
expect(jsx).not.toContain('maxLines')
expect(jsx).not.toContain('truncate')
})
it('text w="fill" in flex="col" exports as w="fill" not w={computed}', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(
g,
`
<Frame name="Card" flex="col" w={300} p={20}>
<Text name="Title" size={22} weight="bold" color="#111" w="fill">Hello World</Text>
</Frame>
`
)
const card = getNodeOrThrow(g, result.id)
const title = getNodeOrThrow(g, childIdAt(card, 0))
expect(title.layoutAlignSelf).toBe('STRETCH')
expect(title.textAutoResize).toBe('HEIGHT')
const jsx = sceneNodeToJSX(title.id, g)
expect(jsx).toContain('w="fill"')
expect(jsx).not.toMatch(/w=\{/)
})
it('text grow={1} in flex="row" exports as grow not w={computed}', async () => {
const g = makeSceneGraph()
const [result] = await renderJSX(
g,
`
<Frame name="Row" flex="row" w={300}>
<Text name="Label" color="#999" w={60}>Label</Text>
<Text name="Value" color="#111" w="fill">Some value text</Text>
</Frame>
`
)
const row = getNodeOrThrow(g, result.id)
const value = getNodeOrThrow(g, childIdAt(row, 1))
expect(value.layoutGrow).toBe(1)
const jsx = sceneNodeToJSX(value.id, g)
expect(jsx).toContain('grow={1}')
expect(jsx).not.toMatch(/\bw=\{\d+\}/)
})
})