diff --git a/packages/docs/development/roadmap.md b/packages/docs/development/roadmap.md index a0b0cfeb3..badc6df0d 100644 --- a/packages/docs/development/roadmap.md +++ b/packages/docs/development/roadmap.md @@ -203,7 +203,7 @@ OpenPencil deliberately preserves many Figma/Kiwi fields even when they are not These are parsed or visible in Figma docs and most likely to cause visible differences in real design files: -1. **Masks** — tune remaining exact Figma stack semantics beyond common alpha/vector/luminance and consecutive-mask paths. +1. **Masks** — tune remaining exact Figma stack semantics beyond common alpha/vector/luminance and consecutive-mask paths. `tests/fixtures/figma-oracles/masks.json` records live Figma API values for alpha, vector, and luminance masks. 2. **Corner smoothing** — expand Figma fixture comparisons and tune remaining stroke/effect edge cases. 3. **Pattern/noise/custom fills** — replace the current solid-color fallback with Figma-oracle rendering for schema-level paint objects and transforms beyond image tile fills. `tests/fixtures/figma-oracles/pattern-noise-custom-paints.json` records that the plugin runtime rejects authoring these paint types and current fixtures do not contain them, so this remains blocked on a Figma-authored sample. 4. **Variable-font and rich text fixtures** — broaden real-file coverage for variable axes, derived text data, leading trim, decoration style, semantic font metadata, and raw OpenType feature metadata; `tests/fixtures/figma-oracles/rich-text-decoration.json` captures the first live Figma rich-text oracle. diff --git a/tests/engine/io/fig/import/mask-oracle.test.ts b/tests/engine/io/fig/import/mask-oracle.test.ts new file mode 100644 index 000000000..8488e8fa3 --- /dev/null +++ b/tests/engine/io/fig/import/mask-oracle.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, test } from 'bun:test' +import { readFileSync } from 'node:fs' + +import type { NodeChange } from '#core/kiwi/fig/codec' +import { nodeChangeToProps } from '#core/kiwi/fig/node-change/convert' +import { sceneNodeToKiwi } from '#core/kiwi/fig/node-change/serialize' +import { SceneGraph, type MaskType } from '#core/scene-graph' + +interface MaskOracleEntry { + isMask: boolean + maskType: MaskType +} + +interface MaskOracle { + masks: MaskOracleEntry[] +} + +function readOracle(): MaskOracle { + return JSON.parse(readFileSync('tests/fixtures/figma-oracles/masks.json', 'utf8')) as MaskOracle +} + +describe('Figma mask oracle', () => { + test('imports live Figma mask types from schema fields', () => { + const oracle = readOracle() + + for (const mask of oracle.masks) { + const props = nodeChangeToProps( + { type: 'RECTANGLE', mask: mask.isMask, maskType: mask.maskType } as NodeChange, + [] + ) + + expect(props.isMask).toBe(true) + expect(props.maskType).toBe(mask.maskType) + } + }) + + test('exports live Figma mask types to schema fields', () => { + const oracle = readOracle() + const graph = new SceneGraph() + const page = graph.getPages()[0] + + for (const [index, mask] of oracle.masks.entries()) { + const node = graph.createNode('RECTANGLE', page.id, { + isMask: mask.isMask, + maskType: mask.maskType + }) + const changes = sceneNodeToKiwi( + node, + { sessionID: 1, localID: index + 1 }, + index, + { value: index + 2 }, + graph, + [] + ) + + expect(changes[0].mask).toBe(true) + expect(changes[0].maskType).toBe(mask.maskType) + } + }) +}) diff --git a/tests/fixtures/figma-oracles/masks.json b/tests/fixtures/figma-oracles/masks.json new file mode 100644 index 000000000..7a131b141 --- /dev/null +++ b/tests/fixtures/figma-oracles/masks.json @@ -0,0 +1,13 @@ +{ + "source": { + "tool": "figma-use eval", + "fileKey": "jSxlQDCrjsvqEQq17IWEcC", + "page": "OpenPencil mask oracle 2026-05-22", + "captured": "2026-05-22" + }, + "masks": [ + { "id": "296500:2240", "name": "Mask ALPHA", "isMask": true, "maskType": "ALPHA" }, + { "id": "296500:2241", "name": "Mask VECTOR", "isMask": true, "maskType": "VECTOR" }, + { "id": "296500:2242", "name": "Mask LUMINANCE", "isMask": true, "maskType": "LUMINANCE" } + ] +}