Figma clipboard data includes VARIABLE_SET, VARIABLE, and other non-visual types that were falling through to the default RECTANGLE case in mapNodeType. These now join DOCUMENT and CANVAS in the skipTypes set.
130 lines
6.6 KiB
TypeScript
130 lines
6.6 KiB
TypeScript
import { describe, expect, it } from 'bun:test'
|
|
|
|
import {
|
|
parseFigmaClipboard,
|
|
importClipboardNodes,
|
|
} from '../../packages/core/src/clipboard'
|
|
import { SceneGraph } from '../../packages/core/src/scene-graph'
|
|
|
|
function makeClipboardHtml(nodeChanges: unknown[], meta = { fileKey: 'test', pasteID: 1, dataType: 'scene' }) {
|
|
// Minimal fig-kiwi clipboard: just meta + empty figma buffer
|
|
// For real parsing we'd need actual Kiwi binary — these tests use importClipboardNodes directly
|
|
const metaB64 = btoa(JSON.stringify(meta))
|
|
return `<meta charset='utf-8'><span data-metadata="<!--(figmeta)${metaB64}(/figmeta)-->"></span><span data-buffer="<!--(figma)(/figma)-->"></span>`
|
|
}
|
|
|
|
function createGraphWithPage(): { graph: SceneGraph; pageId: string } {
|
|
const graph = new SceneGraph()
|
|
graph.addPage('Test')
|
|
return { graph, pageId: graph.rootId }
|
|
}
|
|
|
|
describe('importClipboardNodes', () => {
|
|
it('skips VARIABLE_SET and VARIABLE nodes', () => {
|
|
const { graph, pageId } = createGraphWithPage()
|
|
|
|
const nodeChanges = [
|
|
{ guid: { sessionID: 0, localID: 0 }, type: 'DOCUMENT', name: 'Document' },
|
|
{ guid: { sessionID: 0, localID: 1 }, parentIndex: { guid: { sessionID: 0, localID: 0 }, position: '!' }, type: 'CANVAS', name: 'Page 1' },
|
|
{ guid: { sessionID: 0, localID: 2 }, parentIndex: { guid: { sessionID: 0, localID: 1 }, position: '!' }, type: 'VARIABLE_SET', name: 'Primitives' },
|
|
{ guid: { sessionID: 0, localID: 3 }, parentIndex: { guid: { sessionID: 0, localID: 2 }, position: '!' }, type: 'VARIABLE', name: 'Colors/Brand/500' },
|
|
{ guid: { sessionID: 0, localID: 10 }, parentIndex: { guid: { sessionID: 0, localID: 1 }, position: '"' }, type: 'FRAME', name: 'Card', size: { x: 300, y: 200 }, transform: { m00: 1, m01: 0, m02: 50, m10: 0, m11: 1, m12: 50 } },
|
|
{ guid: { sessionID: 0, localID: 11 }, parentIndex: { guid: { sessionID: 0, localID: 10 }, position: '!' }, type: 'TEXT', name: 'Title', size: { x: 200, y: 30 }, transform: { m00: 1, m01: 0, m02: 10, m10: 0, m11: 1, m12: 10 }, textData: { characters: 'Hello' }, fontSize: 16 },
|
|
] as any[]
|
|
|
|
const created = importClipboardNodes(nodeChanges, graph, pageId)
|
|
expect(created).toHaveLength(1)
|
|
|
|
const card = graph.getNode(created[0])!
|
|
expect(card.type).toBe('FRAME')
|
|
expect(card.name).toBe('Card')
|
|
|
|
const children = graph.getChildren(card.id)
|
|
expect(children).toHaveLength(1)
|
|
expect(children[0].type).toBe('TEXT')
|
|
expect(children[0].name).toBe('Title')
|
|
|
|
const allNodes = [...graph.getAllNodes()]
|
|
const variableNodes = allNodes.filter(n => n.name.includes('Primitives') || n.name.includes('Colors/'))
|
|
expect(variableNodes).toHaveLength(0)
|
|
})
|
|
|
|
it('skips non-visual Figma types', () => {
|
|
const { graph, pageId } = createGraphWithPage()
|
|
|
|
const nonVisualTypes = ['WIDGET', 'STAMP', 'STICKY', 'CONNECTOR', 'CODE_BLOCK', 'SHAPE_WITH_TEXT', 'TABLE_NODE', 'TABLE_CELL']
|
|
const nodeChanges = [
|
|
{ guid: { sessionID: 0, localID: 0 }, type: 'DOCUMENT', name: 'Doc' },
|
|
{ guid: { sessionID: 0, localID: 1 }, parentIndex: { guid: { sessionID: 0, localID: 0 }, position: '!' }, type: 'CANVAS', name: 'Page' },
|
|
...nonVisualTypes.map((type, i) => ({
|
|
guid: { sessionID: 0, localID: 100 + i },
|
|
parentIndex: { guid: { sessionID: 0, localID: 1 }, position: String.fromCharCode(33 + i) },
|
|
type,
|
|
name: `${type}_node`,
|
|
size: { x: 100, y: 100 },
|
|
transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 },
|
|
})),
|
|
{ guid: { sessionID: 0, localID: 200 }, parentIndex: { guid: { sessionID: 0, localID: 1 }, position: 'z' }, type: 'RECTANGLE', name: 'RealShape', size: { x: 50, y: 50 }, transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 } },
|
|
] as any[]
|
|
|
|
const created = importClipboardNodes(nodeChanges, graph, pageId)
|
|
expect(created).toHaveLength(1)
|
|
expect(graph.getNode(created[0])!.name).toBe('RealShape')
|
|
})
|
|
|
|
it('imports nested frames with children', () => {
|
|
const { graph, pageId } = createGraphWithPage()
|
|
|
|
const nodeChanges = [
|
|
{ guid: { sessionID: 0, localID: 0 }, type: 'DOCUMENT', name: 'Doc' },
|
|
{ guid: { sessionID: 0, localID: 1 }, parentIndex: { guid: { sessionID: 0, localID: 0 }, position: '!' }, type: 'CANVAS', name: 'Page' },
|
|
{ guid: { sessionID: 0, localID: 10 }, parentIndex: { guid: { sessionID: 0, localID: 1 }, position: '!' }, type: 'FRAME', name: 'Outer', size: { x: 400, y: 300 }, transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 } },
|
|
{ guid: { sessionID: 0, localID: 11 }, parentIndex: { guid: { sessionID: 0, localID: 10 }, position: '!' }, type: 'FRAME', name: 'Inner', size: { x: 200, y: 100 }, transform: { m00: 1, m01: 0, m02: 20, m10: 0, m11: 1, m12: 20 } },
|
|
{ guid: { sessionID: 0, localID: 12 }, parentIndex: { guid: { sessionID: 0, localID: 11 }, position: '!' }, type: 'TEXT', name: 'Label', size: { x: 100, y: 20 }, transform: { m00: 1, m01: 0, m02: 5, m10: 0, m11: 1, m12: 5 }, textData: { characters: 'Test' }, fontSize: 14 },
|
|
] as any[]
|
|
|
|
const created = importClipboardNodes(nodeChanges, graph, pageId)
|
|
expect(created).toHaveLength(1)
|
|
|
|
const outer = graph.getNode(created[0])!
|
|
expect(outer.name).toBe('Outer')
|
|
|
|
const innerList = graph.getChildren(outer.id)
|
|
expect(innerList).toHaveLength(1)
|
|
expect(innerList[0].name).toBe('Inner')
|
|
|
|
const labels = graph.getChildren(innerList[0].id)
|
|
expect(labels).toHaveLength(1)
|
|
expect(labels[0].name).toBe('Label')
|
|
expect(labels[0].text).toBe('Test')
|
|
})
|
|
|
|
it('preserves fills and strokes', () => {
|
|
const { graph, pageId } = createGraphWithPage()
|
|
|
|
const nodeChanges = [
|
|
{ guid: { sessionID: 0, localID: 0 }, type: 'DOCUMENT', name: 'Doc' },
|
|
{ guid: { sessionID: 0, localID: 1 }, parentIndex: { guid: { sessionID: 0, localID: 0 }, position: '!' }, type: 'CANVAS', name: 'Page' },
|
|
{
|
|
guid: { sessionID: 0, localID: 10 },
|
|
parentIndex: { guid: { sessionID: 0, localID: 1 }, position: '!' },
|
|
type: 'RECTANGLE',
|
|
name: 'Colored',
|
|
size: { x: 100, y: 100 },
|
|
transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 },
|
|
fillPaints: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0, a: 1 }, opacity: 1, visible: true }],
|
|
strokePaints: [{ type: 'SOLID', color: { r: 0, g: 0, b: 1, a: 1 }, opacity: 1, visible: true }],
|
|
strokeWeight: 2,
|
|
},
|
|
] as any[]
|
|
|
|
const created = importClipboardNodes(nodeChanges, graph, pageId)
|
|
const node = graph.getNode(created[0])!
|
|
expect(node.fills).toHaveLength(1)
|
|
expect(node.fills[0].color.r).toBe(1)
|
|
expect(node.strokes).toHaveLength(1)
|
|
expect(node.strokes[0].color.b).toBe(1)
|
|
expect(node.strokes[0].weight).toBe(2)
|
|
})
|
|
})
|