From 41825498d882f4717a1cb948e9796f7ddcfd5a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=ED=99=98?= Date: Wed, 7 Jan 2026 10:34:38 +0900 Subject: [PATCH 1/2] feat: Implment basic snapshot testing framework --- packages/backend/__tests__/README.md | 105 ++++++ .../frame-vertical-text-mixed-style.json | 323 ++++++++++++++++++ .../__tests__/fixtures/text-simple.json | 138 ++++++++ .../__tests__/helpers/codeGenerator.ts | 54 +++ .../__tests__/helpers/fixtureLoader.ts | 27 ++ .../__tests__/helpers/rendererConfig.ts | 30 ++ .../__tests__/helpers/snapshotManager.ts | 94 +++++ packages/backend/__tests__/setup.ts | 7 + packages/backend/__tests__/snapshot.test.ts | 89 +++++ .../frame-vertical-text-mixed-style-html.html | 3 + .../frame-vertical-text-mixed-style-jsx.tsx | 3 + ...cal-text-mixed-style-styled-components.tsx | 52 +++ .../snapshots/html/text-simple-html.html | 1 + .../snapshots/html/text-simple-jsx.tsx | 1 + .../html/text-simple-styled-components.tsx | 22 ++ packages/backend/package.json | 7 +- packages/backend/vitest.config.ts | 16 + pnpm-lock.yaml | 267 +++++++++++++++ 18 files changed, 1237 insertions(+), 2 deletions(-) create mode 100644 packages/backend/__tests__/README.md create mode 100644 packages/backend/__tests__/fixtures/frame-vertical-text-mixed-style.json create mode 100644 packages/backend/__tests__/fixtures/text-simple.json create mode 100644 packages/backend/__tests__/helpers/codeGenerator.ts create mode 100644 packages/backend/__tests__/helpers/fixtureLoader.ts create mode 100644 packages/backend/__tests__/helpers/rendererConfig.ts create mode 100644 packages/backend/__tests__/helpers/snapshotManager.ts create mode 100644 packages/backend/__tests__/setup.ts create mode 100644 packages/backend/__tests__/snapshot.test.ts create mode 100644 packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-html.html create mode 100644 packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-jsx.tsx create mode 100644 packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-styled-components.tsx create mode 100644 packages/backend/__tests__/snapshots/html/text-simple-html.html create mode 100644 packages/backend/__tests__/snapshots/html/text-simple-jsx.tsx create mode 100644 packages/backend/__tests__/snapshots/html/text-simple-styled-components.tsx create mode 100644 packages/backend/vitest.config.ts diff --git a/packages/backend/__tests__/README.md b/packages/backend/__tests__/README.md new file mode 100644 index 00000000..258b2f7e --- /dev/null +++ b/packages/backend/__tests__/README.md @@ -0,0 +1,105 @@ +# Snapshot Tests + +Snapshot tests capture code generation output to detect unintended behavior changes when modifying the codebase. + +## Directory Structure + +``` +__tests__/ +├── fixtures/ # Figma JSON input files +│ └── {name}.json +├── snapshots/ # Generated code snapshots +│ └── html/ +│ ├── {name}-html.html +│ ├── {name}-jsx.tsx +│ └── {name}-styled-components.tsx +├── helpers/ # Test utilities +└── snapshot.test.ts # Main test file +``` + +## Running Tests + +```bash +# Run tests +pnpm --filter backend test + +# Watch mode +pnpm --filter backend test:watch +``` + +## Workflow + +### 1. Adding a New Test Case + +1. Extract JSON from Figma + - Select element(s) in Figma + - Click the Info (i) button in the plugin + - Click "Copy Selection JSON" + +2. Create Fixture File + - Extract the first element from the `newConversion` array in the copied JSON + - Save as `__tests__/fixtures/{name}.json` + - Naming convention: `{parent-type}-{modifier}-{child-type}.json` + - Examples: `frame-autolayout-text.json`, `text-simple.json` + +3. Run Tests to Generate Snapshots + ```bash + pnpm --filter backend test + ``` + - New snapshots are created with `-new` suffix + - Test will fail (this is expected behavior) + +4. Review and Accept Snapshots + ```bash + # Review generated snapshots + cat __tests__/snapshots/html/{name}-html-new.html + + # Accept by removing -new suffix + cd __tests__/snapshots/html + mv {name}-html-new.html {name}-html.html + mv {name}-jsx-new.tsx {name}-jsx.tsx + mv {name}-styled-components-new.tsx {name}-styled-components.tsx + ``` + +5. Verify Tests Pass + ```bash + pnpm --filter backend test + ``` + +### 2. Testing After Code Changes + +1. Run Tests + ```bash + pnpm --filter backend test + ``` + +2. Check Results + - MATCHED: Output matches snapshot (pass) + - MISMATCH: Output differs from snapshot (fail, `-new` file created) + - NEW: New snapshot needs to be created + +3. If Changes Are Intentional + - Review the `-new` file contents + - Accept by removing `-new` suffix from filename + +4. If Changes Are Unintentional + - Fix the code to restore original behavior + - Delete the `-new` file + +## Supported Renderers + +| Renderer | Mode | Extension | +|----------|------|-----------| +| HTML | html | .html | +| HTML | jsx | .tsx | +| HTML | styled-components | .tsx | + +## Snapshot Naming Convention + +- Existing snapshot: `{fixture-name}-{mode}.{ext}` +- New/changed snapshot: `{fixture-name}-{mode}-new.{ext}` + +Examples: +- `text-simple-html.html` +- `text-simple-jsx.tsx` +- `text-simple-styled-components.tsx` diff --git a/packages/backend/__tests__/fixtures/frame-vertical-text-mixed-style.json b/packages/backend/__tests__/fixtures/frame-vertical-text-mixed-style.json new file mode 100644 index 00000000..7188de4b --- /dev/null +++ b/packages/backend/__tests__/fixtures/frame-vertical-text-mixed-style.json @@ -0,0 +1,323 @@ +{ + "id": "5101:6", + "name": "TextBox", + "type": "FRAME", + "scrollBehavior": "SCROLLS", + "children": [ + { + "id": "5101:7", + "name": "Description", + "type": "TEXT", + "scrollBehavior": "SCROLLS", + "blendMode": "PASS_THROUGH", + "fills": [ + { + "blendMode": "NORMAL", + "type": "SOLID", + "color": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + } + } + ], + "strokes": [], + "strokeWeight": 0, + "strokeAlign": "CENTER", + "absoluteBoundingBox": { + "x": 1126, + "y": 227, + "width": 102, + "height": 56 + }, + "absoluteRenderBounds": { + "x": 1126.1259765625, + "y": 234.3199920654297, + "width": 53.8160400390625, + "height": 41.93199157714844 + }, + "constraints": { + "vertical": "TOP", + "horizontal": "LEFT" + }, + "layoutAlign": "INHERIT", + "layoutGrow": 0, + "layoutSizingHorizontal": "FIXED", + "layoutSizingVertical": "FIXED", + "characters": "Hello, World!\n", + "characterStyleOverrides": [ + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "styleOverrideTable": { + "1": { + "fontFamily": "Noto Sans", + "fontPostScriptName": "NotoSans-DisplayBold", + "fontStyle": "Display Bold", + "fontWeight": 700 + } + }, + "lineTypes": [ + "NONE", + "NONE" + ], + "lineIndentations": [ + 0, + 0 + ], + "style": { + "fontFamily": "Noto Sans", + "fontPostScriptName": "NotoSans-Regular", + "fontStyle": "Regular", + "fontWeight": 400, + "fontSize": 18, + "textAlignHorizontal": "LEFT", + "textAlignVertical": "TOP", + "letterSpacing": 0, + "lineHeightPx": 28, + "lineHeightPercent": 114.21112823486328, + "lineHeightPercentFontSize": 155.55555725097656, + "lineHeightUnit": "PIXELS" + }, + "layoutVersion": 5, + "effects": [], + "interactions": [], + "cumulativeRotation": 0, + "uniqueName": "Description", + "styledTextSegments": [ + { + "characters": "Hel", + "start": 0, + "end": 3, + "fontSize": 18, + "fontName": { + "family": "Noto Sans", + "style": "Regular" + }, + "fontWeight": 400, + "textDecoration": "NONE", + "textCase": "ORIGINAL", + "lineHeight": { + "unit": "PIXELS", + "value": 28 + }, + "letterSpacing": { + "unit": "PIXELS", + "value": 0 + }, + "fills": [ + { + "type": "SOLID", + "visible": true, + "opacity": 1, + "blendMode": "NORMAL", + "color": { + "r": 0, + "g": 0, + "b": 0 + }, + "boundVariables": {} + } + ], + "textStyleId": "", + "fillStyleId": "", + "listOptions": { + "type": "NONE" + }, + "indentation": 0, + "hyperlink": null, + "openTypeFeatures": {}, + "uniqueId": "description_span_01" + }, + { + "characters": "lo, Wo", + "start": 3, + "end": 9, + "fontSize": 18, + "fontName": { + "family": "Noto Sans", + "style": "Display Bold" + }, + "fontWeight": 700, + "textDecoration": "NONE", + "textCase": "ORIGINAL", + "lineHeight": { + "unit": "PIXELS", + "value": 28 + }, + "letterSpacing": { + "unit": "PIXELS", + "value": 0 + }, + "fills": [ + { + "type": "SOLID", + "visible": true, + "opacity": 1, + "blendMode": "NORMAL", + "color": { + "r": 0, + "g": 0, + "b": 0 + }, + "boundVariables": {} + } + ], + "textStyleId": "", + "fillStyleId": "", + "listOptions": { + "type": "NONE" + }, + "indentation": 0, + "hyperlink": null, + "openTypeFeatures": {}, + "uniqueId": "description_span_02" + }, + { + "characters": "rld!", + "start": 9, + "end": 14, + "fontSize": 18, + "fontName": { + "family": "Noto Sans", + "style": "Regular" + }, + "fontWeight": 400, + "textDecoration": "NONE", + "textCase": "ORIGINAL", + "lineHeight": { + "unit": "PIXELS", + "value": 28 + }, + "letterSpacing": { + "unit": "PIXELS", + "value": 0 + }, + "fills": [ + { + "type": "SOLID", + "visible": true, + "opacity": 1, + "blendMode": "NORMAL", + "color": { + "r": 0, + "g": 0, + "b": 0 + }, + "boundVariables": {} + } + ], + "textStyleId": "", + "fillStyleId": "", + "listOptions": { + "type": "NONE" + }, + "indentation": 0, + "hyperlink": null, + "openTypeFeatures": {}, + "uniqueId": "description_span_03" + } + ], + "fontFamily": "Noto Sans", + "fontPostScriptName": "NotoSans-Regular", + "fontStyle": "Regular", + "fontWeight": 400, + "fontSize": 18, + "textAlignHorizontal": "LEFT", + "textAlignVertical": "TOP", + "letterSpacing": 0, + "lineHeightPx": 28, + "lineHeightPercent": 114.21112823486328, + "lineHeightPercentFontSize": 155.55555725097656, + "lineHeightUnit": "PIXELS", + "textAutoResize": "NONE", + "width": 102, + "height": 56, + "x": 0, + "y": 0, + "canBeFlattened": false, + "layoutMode": "NONE", + "primaryAxisAlignItems": "MIN", + "counterAxisAlignItems": "MIN" + } + ], + "blendMode": "PASS_THROUGH", + "clipsContent": false, + "background": [ + { + "blendMode": "NORMAL", + "type": "SOLID", + "color": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + } + } + ], + "fills": [ + { + "blendMode": "NORMAL", + "type": "SOLID", + "color": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + } + } + ], + "strokes": [], + "strokeWeight": 1, + "strokeAlign": "INSIDE", + "backgroundColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "layoutMode": "VERTICAL", + "paddingBottom": 75, + "itemSpacing": 16, + "layoutWrap": "NO_WRAP", + "absoluteBoundingBox": { + "x": 1126, + "y": 227, + "width": 102, + "height": 131 + }, + "absoluteRenderBounds": { + "x": 1126, + "y": 227, + "width": 102, + "height": 131 + }, + "constraints": { + "vertical": "TOP", + "horizontal": "LEFT" + }, + "layoutSizingHorizontal": "HUG", + "layoutSizingVertical": "HUG", + "effects": [], + "interactions": [], + "uniqueName": "TextBox", + "width": 102, + "height": 131, + "x": 0, + "y": 0, + "canBeFlattened": false, + "paddingLeft": 0, + "paddingRight": 0, + "paddingTop": 0, + "layoutGrow": 0, + "primaryAxisAlignItems": "MIN", + "counterAxisAlignItems": "MIN" +} diff --git a/packages/backend/__tests__/fixtures/text-simple.json b/packages/backend/__tests__/fixtures/text-simple.json new file mode 100644 index 00000000..a48cce5e --- /dev/null +++ b/packages/backend/__tests__/fixtures/text-simple.json @@ -0,0 +1,138 @@ +{ + "id": "5042:17", + "name": "Description", + "type": "TEXT", + "scrollBehavior": "SCROLLS", + "blendMode": "PASS_THROUGH", + "fills": [ + { + "blendMode": "NORMAL", + "type": "SOLID", + "color": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + } + } + ], + "strokes": [], + "strokeWeight": 0, + "strokeAlign": "CENTER", + "absoluteBoundingBox": { + "x": 984, + "y": 227, + "width": 102, + "height": 56 + }, + "absoluteRenderBounds": { + "x": 984.2160034179688, + "y": 234.3199920654297, + "width": 53.74359130859375, + "height": 41.93199157714844 + }, + "constraints": { + "vertical": "TOP", + "horizontal": "LEFT" + }, + "layoutAlign": "INHERIT", + "layoutGrow": 0, + "layoutSizingHorizontal": "FIXED", + "layoutSizingVertical": "FIXED", + "characters": "Hello, World!\n", + "characterStyleOverrides": [], + "styleOverrideTable": {}, + "lineTypes": [ + "NONE", + "NONE" + ], + "lineIndentations": [ + 0, + 0 + ], + "style": { + "fontFamily": "Noto Sans", + "fontPostScriptName": "NotoSans-Regular", + "fontStyle": "Regular", + "fontWeight": 400, + "fontSize": 18, + "textAlignHorizontal": "LEFT", + "textAlignVertical": "TOP", + "letterSpacing": 0, + "lineHeightPx": 28, + "lineHeightPercent": 114.21112823486328, + "lineHeightPercentFontSize": 155.55555725097656, + "lineHeightUnit": "PIXELS" + }, + "layoutVersion": 5, + "effects": [], + "interactions": [], + "uniqueName": "Description", + "styledTextSegments": [ + { + "characters": "Hello, World!", + "start": 0, + "end": 14, + "fontSize": 18, + "fontName": { + "family": "Noto Sans", + "style": "Regular" + }, + "fontWeight": 400, + "textDecoration": "NONE", + "textCase": "ORIGINAL", + "lineHeight": { + "unit": "PIXELS", + "value": 28 + }, + "letterSpacing": { + "unit": "PIXELS", + "value": 0 + }, + "fills": [ + { + "type": "SOLID", + "visible": true, + "opacity": 1, + "blendMode": "NORMAL", + "color": { + "r": 0, + "g": 0, + "b": 0 + }, + "boundVariables": {} + } + ], + "textStyleId": "", + "fillStyleId": "", + "listOptions": { + "type": "NONE" + }, + "indentation": 0, + "hyperlink": null, + "openTypeFeatures": {}, + "uniqueId": "description_span" + } + ], + "fontFamily": "Noto Sans", + "fontPostScriptName": "NotoSans-Regular", + "fontStyle": "Regular", + "fontWeight": 400, + "fontSize": 18, + "textAlignHorizontal": "LEFT", + "textAlignVertical": "TOP", + "letterSpacing": 0, + "lineHeightPx": 28, + "lineHeightPercent": 114.21112823486328, + "lineHeightPercentFontSize": 155.55555725097656, + "lineHeightUnit": "PIXELS", + "textAutoResize": "NONE", + "width": 102, + "height": 56, + "x": 0, + "y": 0, + "canBeFlattened": false, + "layoutMode": "NONE", + "primaryAxisAlignItems": "MIN", + "counterAxisAlignItems": "MIN" +} diff --git a/packages/backend/__tests__/helpers/codeGenerator.ts b/packages/backend/__tests__/helpers/codeGenerator.ts new file mode 100644 index 00000000..5f7f541f --- /dev/null +++ b/packages/backend/__tests__/helpers/codeGenerator.ts @@ -0,0 +1,54 @@ +import type { PluginSettings } from "types"; +import { htmlMain } from "../../src/html/htmlMain"; +import { RendererConfig } from "./rendererConfig"; + +function createDefaultSettings(): PluginSettings { + return { + framework: "HTML", + showLayerNames: false, + embedImages: false, + embedVectors: false, + useColorVariables: false, + htmlGenerationMode: "html", + tailwindGenerationMode: "html", + roundTailwindValues: true, + roundTailwindColors: true, + customTailwindPrefix: "", + baseFontSize: 16, + useTailwind4: false, + thresholdPercent: 0.05, + baseFontFamily: "Inter", + fontFamilyCustomConfig: {}, + flutterGenerationMode: "snippet", + swiftUIGenerationMode: "snippet", + composeGenerationMode: "snippet", + useOldPluginVersion2025: false, + responsiveRoot: false, + }; +} + +function createSettingsForConfig(config: RendererConfig): PluginSettings { + const baseSettings = createDefaultSettings(); + + return { + ...baseSettings, + framework: config.framework, + [config.settingsKey]: config.mode, + }; +} + +export async function generateCode( + nodes: any[], + config: RendererConfig +): Promise { + const settings = createSettingsForConfig(config); + + switch (config.framework) { + case "HTML": { + const result = await htmlMain(nodes as any, settings); + return result.html; + } + default: + throw new Error(`Unknown framework: ${config.framework}`); + } +} diff --git a/packages/backend/__tests__/helpers/fixtureLoader.ts b/packages/backend/__tests__/helpers/fixtureLoader.ts new file mode 100644 index 00000000..378002e6 --- /dev/null +++ b/packages/backend/__tests__/helpers/fixtureLoader.ts @@ -0,0 +1,27 @@ +import fs from "fs"; +import path from "path"; + +const FIXTURES_DIR = path.join(__dirname, "..", "fixtures"); + +export interface FixtureInfo { + name: string; + data: any[]; +} + +export function loadAllFixtures(): FixtureInfo[] { + if (!fs.existsSync(FIXTURES_DIR)) { + fs.mkdirSync(FIXTURES_DIR, { recursive: true }); + return []; + } + + const files = fs.readdirSync(FIXTURES_DIR).filter((f) => f.endsWith(".json")); + + return files.map((file) => { + const filePath = path.join(FIXTURES_DIR, file); + const name = path.basename(file, ".json"); + const rawData = JSON.parse(fs.readFileSync(filePath, "utf-8")); + const data = Array.isArray(rawData) ? rawData : [rawData]; + + return { name, data }; + }); +} diff --git a/packages/backend/__tests__/helpers/rendererConfig.ts b/packages/backend/__tests__/helpers/rendererConfig.ts new file mode 100644 index 00000000..8dd8eb99 --- /dev/null +++ b/packages/backend/__tests__/helpers/rendererConfig.ts @@ -0,0 +1,30 @@ +import type { Framework } from "types"; + +export interface RendererConfig { + framework: Framework; + mode: string; + extension: string; + settingsKey: string; +} + +// POC: HTML renderer with 3 modes +export const RENDERER_CONFIGS: RendererConfig[] = [ + { + framework: "HTML", + mode: "html", + extension: ".html", + settingsKey: "htmlGenerationMode", + }, + { + framework: "HTML", + mode: "jsx", + extension: ".tsx", + settingsKey: "htmlGenerationMode", + }, + { + framework: "HTML", + mode: "styled-components", + extension: ".tsx", + settingsKey: "htmlGenerationMode", + }, +]; diff --git a/packages/backend/__tests__/helpers/snapshotManager.ts b/packages/backend/__tests__/helpers/snapshotManager.ts new file mode 100644 index 00000000..ab7e043a --- /dev/null +++ b/packages/backend/__tests__/helpers/snapshotManager.ts @@ -0,0 +1,94 @@ +import fs from "fs"; +import path from "path"; + +const SNAPSHOTS_DIR = path.join(__dirname, "..", "snapshots"); + +export interface SnapshotResult { + status: "match" | "mismatch" | "new"; + existingPath?: string; + newPath?: string; + expected?: string; + actual?: string; +} + +function getSnapshotPath( + fixtureName: string, + framework: string, + mode: string, + extension: string, + isNew: boolean = false +): string { + const frameworkDir = path.join(SNAPSHOTS_DIR, framework.toLowerCase()); + const filename = `${fixtureName}-${mode}${isNew ? "-new" : ""}${extension}`; + return path.join(frameworkDir, filename); +} + +function normalizeCode(code: string): string { + return code + .replace(/\r\n/g, "\n") + .replace(/\s+$/gm, "") + .trim(); +} + +export function compareSnapshot( + fixtureName: string, + framework: string, + mode: string, + extension: string, + generatedCode: string +): SnapshotResult { + const snapshotPath = getSnapshotPath( + fixtureName, + framework, + mode, + extension, + false + ); + const newSnapshotPath = getSnapshotPath( + fixtureName, + framework, + mode, + extension, + true + ); + + // Ensure framework directory exists + const frameworkDir = path.dirname(snapshotPath); + if (!fs.existsSync(frameworkDir)) { + fs.mkdirSync(frameworkDir, { recursive: true }); + } + + // Case 1: No existing snapshot - create new + if (!fs.existsSync(snapshotPath)) { + fs.writeFileSync(newSnapshotPath, generatedCode, "utf-8"); + return { + status: "new", + newPath: newSnapshotPath, + actual: generatedCode, + }; + } + + // Case 2: Existing snapshot - compare + const existingSnapshot = fs.readFileSync(snapshotPath, "utf-8"); + + if (normalizeCode(existingSnapshot) === normalizeCode(generatedCode)) { + // Clean up any stale -new files + if (fs.existsSync(newSnapshotPath)) { + fs.unlinkSync(newSnapshotPath); + } + return { + status: "match", + existingPath: snapshotPath, + }; + } + + // Case 3: Mismatch - write new file + fs.writeFileSync(newSnapshotPath, generatedCode, "utf-8"); + return { + status: "mismatch", + existingPath: snapshotPath, + newPath: newSnapshotPath, + expected: existingSnapshot, + actual: generatedCode, + }; +} diff --git a/packages/backend/__tests__/setup.ts b/packages/backend/__tests__/setup.ts new file mode 100644 index 00000000..d865c357 --- /dev/null +++ b/packages/backend/__tests__/setup.ts @@ -0,0 +1,7 @@ +// Mock global figma object for tests +(global as any).figma = { + mixed: Symbol("figma.mixed"), + ui: { + postMessage: () => {}, + }, +}; diff --git a/packages/backend/__tests__/snapshot.test.ts b/packages/backend/__tests__/snapshot.test.ts new file mode 100644 index 00000000..02b83dd2 --- /dev/null +++ b/packages/backend/__tests__/snapshot.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { loadAllFixtures, FixtureInfo } from "./helpers/fixtureLoader"; +import { RENDERER_CONFIGS } from "./helpers/rendererConfig"; +import { generateCode } from "./helpers/codeGenerator"; +import { compareSnapshot, SnapshotResult } from "./helpers/snapshotManager"; + +describe("Code Generation Snapshots", () => { + let fixtures: FixtureInfo[]; + + beforeAll(() => { + fixtures = loadAllFixtures(); + }); + + it("should have fixtures to test", () => { + expect(fixtures.length).toBeGreaterThan(0); + }); + + it("should generate snapshots for all fixtures and modes", async () => { + const results: { + fixture: string; + config: string; + result: SnapshotResult; + }[] = []; + + for (const fixture of fixtures) { + for (const config of RENDERER_CONFIGS) { + const generatedCode = await generateCode(fixture.data, config); + + const result = compareSnapshot( + fixture.name, + config.framework, + config.mode, + config.extension, + generatedCode + ); + + results.push({ + fixture: fixture.name, + config: `${config.framework}-${config.mode}`, + result, + }); + } + } + + // Report results + const newSnapshots = results.filter((r) => r.result.status === "new"); + const mismatches = results.filter((r) => r.result.status === "mismatch"); + const matches = results.filter((r) => r.result.status === "match"); + + if (newSnapshots.length > 0) { + console.log("\n[NEW SNAPSHOTS]"); + newSnapshots.forEach((r) => { + console.log(` ${r.fixture} / ${r.config}: ${r.result.newPath}`); + }); + console.log( + '\nReview and rename files (remove "-new" suffix) to accept.\n' + ); + } + + if (mismatches.length > 0) { + console.log("\n[MISMATCHES]"); + mismatches.forEach((r) => { + console.log(` ${r.fixture} / ${r.config}:`); + console.log(` Existing: ${r.result.existingPath}`); + console.log(` New: ${r.result.newPath}`); + }); + console.log( + '\nReview and rename files (remove "-new" suffix) to accept changes.\n' + ); + } + + if (matches.length > 0) { + console.log(`\n[MATCHED] ${matches.length} snapshot(s) matched.`); + } + + // Fail if there are mismatches or new snapshots + if (mismatches.length > 0) { + expect.fail( + `${mismatches.length} snapshot(s) mismatched. Review the -new files and accept if correct.` + ); + } + + if (newSnapshots.length > 0) { + expect.fail( + `${newSnapshots.length} new snapshot(s) created. Review and accept by removing "-new" suffix.` + ); + } + }); +}); diff --git a/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-html.html b/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-html.html new file mode 100644 index 00000000..bd78661e --- /dev/null +++ b/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-html.html @@ -0,0 +1,3 @@ +
+
Hello, World!
+
\ No newline at end of file diff --git a/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-jsx.tsx b/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-jsx.tsx new file mode 100644 index 00000000..8af3348d --- /dev/null +++ b/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-jsx.tsx @@ -0,0 +1,3 @@ +
+
Hello, World!
+
\ No newline at end of file diff --git a/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-styled-components.tsx b/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-styled-components.tsx new file mode 100644 index 00000000..e7e734d1 --- /dev/null +++ b/packages/backend/__tests__/snapshots/html/frame-vertical-text-mixed-style-styled-components.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import styled from "styled-components"; + +const StyledDescriptionspan01 = styled.span` + color: black; + font-size: 18px; + font-family: Noto Sans; + font-weight: 400; + line-height: 28px; + word-wrap: break-word; +`; + +const StyledDescriptionspan02 = styled.span` + color: black; + font-size: 18px; + font-family: Noto Sans; + font-weight: 700; + line-height: 28px; + word-wrap: break-word; +`; + +const StyledDescriptionspan03 = styled.span` + color: black; + font-size: 18px; + font-family: Noto Sans; + font-weight: 400; + line-height: 28px; + word-wrap: break-word; +`; + +const StyledDescription = styled.p` + width: 102px; + height: 56px; +`; + +const StyledTextBox = styled.div` + padding-bottom: 75px; + background: white; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 16px; + display: inline-flex; +`; + +export const Textbox = () => { + return ( + + Hello, World! + + ); +}; \ No newline at end of file diff --git a/packages/backend/__tests__/snapshots/html/text-simple-html.html b/packages/backend/__tests__/snapshots/html/text-simple-html.html new file mode 100644 index 00000000..802af4ae --- /dev/null +++ b/packages/backend/__tests__/snapshots/html/text-simple-html.html @@ -0,0 +1 @@ +
Hello, World!
\ No newline at end of file diff --git a/packages/backend/__tests__/snapshots/html/text-simple-jsx.tsx b/packages/backend/__tests__/snapshots/html/text-simple-jsx.tsx new file mode 100644 index 00000000..e85ef930 --- /dev/null +++ b/packages/backend/__tests__/snapshots/html/text-simple-jsx.tsx @@ -0,0 +1 @@ +
Hello, World!
\ No newline at end of file diff --git a/packages/backend/__tests__/snapshots/html/text-simple-styled-components.tsx b/packages/backend/__tests__/snapshots/html/text-simple-styled-components.tsx new file mode 100644 index 00000000..c5e284c5 --- /dev/null +++ b/packages/backend/__tests__/snapshots/html/text-simple-styled-components.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import styled from "styled-components"; + +const StyledDescriptionspan = styled.span` + color: black; + font-size: 18px; + font-family: Noto Sans; + font-weight: 400; + line-height: 28px; + word-wrap: break-word; +`; + +const StyledDescription = styled.p` + width: 102px; + height: 56px; +`; + +export const Description = () => { + return ( + Hello, World! + ); +}; \ No newline at end of file diff --git a/packages/backend/package.json b/packages/backend/package.json index 762c578a..a9068b9d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -10,7 +10,9 @@ "dist/**" ], "scripts": { - "lint": "eslint \"src/**/*.ts*\"" + "lint": "eslint \"src/**/*.ts*\"", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@figma/plugin-typings": "^1.121.0", @@ -28,6 +30,7 @@ "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", "tsup": "^8.5.1", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^2.1.8" } } diff --git a/packages/backend/vitest.config.ts b/packages/backend/vitest.config.ts new file mode 100644 index 00000000..5dd2a15a --- /dev/null +++ b/packages/backend/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vitest/config"; +import path from "path"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["__tests__/**/*.test.ts"], + setupFiles: ["__tests__/setup.ts"], + }, + resolve: { + alias: { + types: path.resolve(__dirname, "../types/src"), + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d755a1ae..d50415f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,6 +215,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@24.10.2)(lightningcss@1.30.2) packages/eslint-config-custom: dependencies: @@ -1636,6 +1639,35 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1699,6 +1731,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -1769,6 +1805,10 @@ packages: caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1782,6 +1822,10 @@ packages: character-reference-invalid@1.1.4: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -1871,6 +1915,10 @@ packages: supports-color: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1927,6 +1975,9 @@ packages: resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -2101,10 +2152,17 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2584,6 +2642,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} @@ -2758,9 +2819,16 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2994,6 +3062,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3008,6 +3079,12 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -3101,6 +3178,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -3108,6 +3188,18 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3234,6 +3326,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-plugin-singlefile@2.3.0: resolution: {integrity: sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==} engines: {node: '>18.0.0'} @@ -3272,6 +3369,31 @@ packages: terser: optional: true + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3293,6 +3415,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4335,6 +4462,46 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@24.10.2)(lightningcss@1.30.2))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@24.10.2)(lightningcss@1.30.2) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -4427,6 +4594,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-function@1.0.0: {} @@ -4492,6 +4661,14 @@ snapshots: caniuse-lite@1.0.30001760: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -4503,6 +4680,8 @@ snapshots: character-reference-invalid@1.1.4: {} + check-error@2.1.3: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -4584,6 +4763,8 @@ snapshots: dependencies: ms: 2.1.3 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -4703,6 +4884,8 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -5026,8 +5209,14 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} + expect-type@1.3.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -5470,6 +5659,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lowlight@1.20.0: dependencies: fault: 1.0.4 @@ -5652,8 +5843,12 @@ snapshots: path-parse@1.0.7: {} + pathe@1.1.2: {} + pathe@2.0.3: {} + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5943,6 +6138,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + source-map-js@1.2.1: {} source-map@0.7.6: {} @@ -5951,6 +6148,10 @@ snapshots: stable-hash@0.0.5: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -6061,6 +6262,8 @@ snapshots: dependencies: any-promise: 1.3.0 + tinybench@2.9.0: {} + tinyexec@0.3.2: {} tinyglobby@0.2.15: @@ -6068,6 +6271,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6231,6 +6440,24 @@ snapshots: dependencies: punycode: 2.3.1 + vite-node@2.1.9(@types/node@24.10.2)(lightningcss@1.30.2): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@24.10.2)(lightningcss@1.30.2) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-plugin-singlefile@2.3.0(rollup@4.53.3)(vite@5.4.21(@types/node@24.10.2)(lightningcss@1.30.2)): dependencies: micromatch: 4.0.8 @@ -6247,6 +6474,41 @@ snapshots: fsevents: 2.3.3 lightningcss: 1.30.2 + vitest@2.1.9(@types/node@24.10.2)(lightningcss@1.30.2): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@24.10.2)(lightningcss@1.30.2)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@24.10.2)(lightningcss@1.30.2) + vite-node: 2.1.9(@types/node@24.10.2)(lightningcss@1.30.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.10.2 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -6292,6 +6554,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: From 29f34e5484c563584ad469bd06925c0942f0e73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=ED=99=98?= Date: Wed, 7 Jan 2026 10:38:26 +0900 Subject: [PATCH 2/2] doc: Fix wrong information about naming convention --- packages/backend/__tests__/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/__tests__/README.md b/packages/backend/__tests__/README.md index 258b2f7e..619896e5 100644 --- a/packages/backend/__tests__/README.md +++ b/packages/backend/__tests__/README.md @@ -39,8 +39,8 @@ pnpm --filter backend test:watch 2. Create Fixture File - Extract the first element from the `newConversion` array in the copied JSON - Save as `__tests__/fixtures/{name}.json` - - Naming convention: `{parent-type}-{modifier}-{child-type}.json` - - Examples: `frame-autolayout-text.json`, `text-simple.json` + - Naming convention: `{parent-type}-{parent-modifier}-{child-type}-{child-modifier}.json` + - Examples: `frame-autolayout-text-simple.json`, `frame-vertical-text-mixed-style.json` 3. Run Tests to Generate Snapshots ```bash