@fragments-sdk/cli 0.10.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-client-I6MDWNYA.js +21 -0
- package/dist/bin.js +292 -367
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
- package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
- package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
- package/dist/chunk-GVDSFQ4E.js.map +1 -0
- package/dist/chunk-JJ2VRTBU.js +626 -0
- package/dist/chunk-JJ2VRTBU.js.map +1 -0
- package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
- package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
- package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
- package/dist/chunk-OQKMEFOS.js.map +1 -0
- package/dist/chunk-SXTKFDCR.js +104 -0
- package/dist/chunk-SXTKFDCR.js.map +1 -0
- package/dist/chunk-T5OMVL7E.js +443 -0
- package/dist/chunk-T5OMVL7E.js.map +1 -0
- package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
- package/dist/chunk-TPWGL2XS.js.map +1 -0
- package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
- package/dist/chunk-WFS63PCW.js.map +1 -0
- package/dist/core/index.js +9 -1
- package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
- package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/init-ZSX3NRCZ.js +636 -0
- package/dist/init-ZSX3NRCZ.js.map +1 -0
- package/dist/mcp-bin.js +2 -2
- package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
- package/dist/scan-generate-SYU4PYZD.js +1115 -0
- package/dist/scan-generate-SYU4PYZD.js.map +1 -0
- package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
- package/dist/snapshot-XOISO2IS.js +139 -0
- package/dist/snapshot-XOISO2IS.js.map +1 -0
- package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
- package/dist/static-viewer-5GXH2MGE.js.map +1 -0
- package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
- package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
- package/dist/{viewer-DNMNC5VS.js → viewer-7ZEAFBVN.js} +80 -58
- package/dist/viewer-7ZEAFBVN.js.map +1 -0
- package/package.json +6 -14
- package/src/ai-client.ts +156 -0
- package/src/bin.ts +74 -2
- package/src/build.ts +95 -33
- package/src/commands/__tests__/drift-sync.test.ts +252 -0
- package/src/commands/__tests__/scan-generate.test.ts +497 -45
- package/src/commands/enhance.ts +11 -35
- package/src/commands/init.ts +296 -193
- package/src/commands/scan-generate.ts +740 -139
- package/src/commands/scan.ts +37 -32
- package/src/commands/setup.ts +143 -52
- package/src/commands/snapshot.ts +197 -0
- package/src/commands/sync.ts +357 -0
- package/src/commands/validate.ts +43 -1
- package/src/core/component-extractor.test.ts +282 -0
- package/src/core/component-extractor.ts +1030 -0
- package/src/core/discovery.ts +93 -7
- package/src/service/enhance/props-extractor.ts +235 -13
- package/src/validators.ts +236 -0
- package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
- package/src/viewer/server.ts +37 -22
- package/src/viewer/vite-plugin.ts +25 -9
- package/dist/chunk-5G3VZH43.js.map +0 -1
- package/dist/chunk-OQO55NKV.js.map +0 -1
- package/dist/chunk-WXSR2II7.js.map +0 -1
- package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
- package/dist/init-NDQXUWDU.js +0 -796
- package/dist/init-NDQXUWDU.js.map +0 -1
- package/dist/scan-generate-SJAN5MVI.js +0 -691
- package/dist/scan-generate-SJAN5MVI.js.map +0 -1
- package/dist/viewer-DNMNC5VS.js.map +0 -1
- package/src/ai.ts +0 -266
- package/src/commands/init-framework.ts +0 -414
- package/src/mcp/bin.ts +0 -36
- package/src/migrate/bin.ts +0 -114
- package/src/theme/index.ts +0 -77
- package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
- package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
- package/src/viewer/__tests__/render-utils.test.ts +0 -232
- package/src/viewer/__tests__/style-utils.test.ts +0 -404
- package/src/viewer/assets/fragments-logo.ts +0 -4
- package/src/viewer/assets/fragments_logo.png +0 -0
- package/src/viewer/bin.ts +0 -86
- package/src/viewer/cli/health.ts +0 -256
- package/src/viewer/cli/index.ts +0 -33
- package/src/viewer/cli/scan.ts +0 -124
- package/src/viewer/cli/utils.ts +0 -174
- package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
- package/src/viewer/components/ActionCapture.tsx +0 -172
- package/src/viewer/components/ActionsPanel.tsx +0 -332
- package/src/viewer/components/AllVariantsPreview.tsx +0 -78
- package/src/viewer/components/App.tsx +0 -582
- package/src/viewer/components/BottomPanel.tsx +0 -288
- package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
- package/src/viewer/components/CodePanel.tsx +0 -118
- package/src/viewer/components/CommandPalette.tsx +0 -392
- package/src/viewer/components/ComponentDocView.tsx +0 -164
- package/src/viewer/components/ComponentGraph.tsx +0 -380
- package/src/viewer/components/ComponentHeader.tsx +0 -88
- package/src/viewer/components/ContractPanel.tsx +0 -241
- package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
- package/src/viewer/components/ErrorBoundary.tsx +0 -97
- package/src/viewer/components/FigmaEmbed.tsx +0 -238
- package/src/viewer/components/FragmentEditor.tsx +0 -525
- package/src/viewer/components/FragmentRenderer.tsx +0 -61
- package/src/viewer/components/HeaderSearch.tsx +0 -24
- package/src/viewer/components/HealthDashboard.tsx +0 -441
- package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
- package/src/viewer/components/Icons.tsx +0 -479
- package/src/viewer/components/InteractionsPanel.tsx +0 -757
- package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
- package/src/viewer/components/IsolatedRender.tsx +0 -113
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
- package/src/viewer/components/LandingPage.tsx +0 -421
- package/src/viewer/components/Layout.tsx +0 -27
- package/src/viewer/components/LeftSidebar.tsx +0 -472
- package/src/viewer/components/LoadErrorMessage.tsx +0 -102
- package/src/viewer/components/MultiViewportPreview.tsx +0 -522
- package/src/viewer/components/NoVariantsMessage.tsx +0 -59
- package/src/viewer/components/PanelShell.tsx +0 -161
- package/src/viewer/components/PerformancePanel.tsx +0 -304
- package/src/viewer/components/PreviewArea.tsx +0 -472
- package/src/viewer/components/PreviewAside.tsx +0 -168
- package/src/viewer/components/PreviewFrameHost.tsx +0 -303
- package/src/viewer/components/PreviewPane.tsx +0 -149
- package/src/viewer/components/PreviewToolbar.tsx +0 -80
- package/src/viewer/components/PropsEditor.tsx +0 -506
- package/src/viewer/components/PropsTable.tsx +0 -111
- package/src/viewer/components/RelationsSection.tsx +0 -88
- package/src/viewer/components/ResizablePanel.tsx +0 -271
- package/src/viewer/components/RightSidebar.tsx +0 -102
- package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
- package/src/viewer/components/ScreenshotButton.tsx +0 -90
- package/src/viewer/components/Sidebar.tsx +0 -169
- package/src/viewer/components/SkeletonLoader.tsx +0 -161
- package/src/viewer/components/ThemeProvider.tsx +0 -42
- package/src/viewer/components/Toast.tsx +0 -3
- package/src/viewer/components/TokenStylePanel.tsx +0 -699
- package/src/viewer/components/TopToolbar.tsx +0 -159
- package/src/viewer/components/UsageSection.tsx +0 -95
- package/src/viewer/components/VariantMatrix.tsx +0 -388
- package/src/viewer/components/VariantRenderer.tsx +0 -131
- package/src/viewer/components/VariantTabs.tsx +0 -40
- package/src/viewer/components/ViewerHeader.tsx +0 -69
- package/src/viewer/components/ViewerStateSync.tsx +0 -52
- package/src/viewer/components/ViewportSelector.tsx +0 -172
- package/src/viewer/components/WebMCPDevTools.tsx +0 -503
- package/src/viewer/components/WebMCPIntegration.tsx +0 -47
- package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
- package/src/viewer/components/_future/CreatePage.tsx +0 -836
- package/src/viewer/components/viewer-utils.ts +0 -16
- package/src/viewer/composition-renderer.ts +0 -381
- package/src/viewer/constants/index.ts +0 -1
- package/src/viewer/constants/ui.ts +0 -166
- package/src/viewer/entry.tsx +0 -335
- package/src/viewer/hooks/index.ts +0 -2
- package/src/viewer/hooks/useA11yCache.ts +0 -383
- package/src/viewer/hooks/useA11yService.ts +0 -364
- package/src/viewer/hooks/useActions.ts +0 -138
- package/src/viewer/hooks/useAppState.ts +0 -147
- package/src/viewer/hooks/useCompiledFragments.ts +0 -42
- package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
- package/src/viewer/hooks/useHmrStatus.ts +0 -109
- package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
- package/src/viewer/hooks/usePreviewBridge.ts +0 -347
- package/src/viewer/hooks/useScrollSpy.ts +0 -78
- package/src/viewer/hooks/useUrlState.ts +0 -318
- package/src/viewer/hooks/useViewSettings.ts +0 -111
- package/src/viewer/index.html +0 -28
- package/src/viewer/intelligence/healthReport.ts +0 -505
- package/src/viewer/intelligence/styleDrift.ts +0 -340
- package/src/viewer/intelligence/usageScanner.ts +0 -309
- package/src/viewer/jsx-parser.ts +0 -486
- package/src/viewer/preview-frame-entry.tsx +0 -25
- package/src/viewer/preview-frame.html +0 -125
- package/src/viewer/public/favicon.ico +0 -0
- package/src/viewer/render-template.html +0 -68
- package/src/viewer/styles/globals.css +0 -278
- package/src/viewer/types/a11y.ts +0 -197
- package/src/viewer/utils/a11y-fixes.ts +0 -509
- package/src/viewer/utils/actionExport.ts +0 -372
- package/src/viewer/utils/colorSchemes.ts +0 -201
- package/src/viewer/utils/detectRelationships.ts +0 -256
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
- package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
- package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
- package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
- package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
- package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
- package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
- package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -137
- package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
- package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
- package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
- package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
- package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
- package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/index.ts +0 -34
- package/src/viewer/vendor/shared/src/types.ts +0 -53
- package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
- package/src/viewer/webmcp/analytics.ts +0 -165
- package/src/viewer/webmcp/index.ts +0 -3
- package/src/viewer/webmcp/posthog-bridge.ts +0 -39
- package/src/viewer/webmcp/runtime-tools.ts +0 -152
- package/src/viewer/webmcp/scan-utils.ts +0 -135
- package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
- package/src/viewer/webmcp/viewer-state.ts +0 -45
- /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
- /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
- /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
- /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
- /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
- /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
- /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
- /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
- /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
serializeValue,
|
|
4
|
-
serializePropsToJsx,
|
|
5
|
-
findFragmentByName,
|
|
6
|
-
getAvailableComponents,
|
|
7
|
-
generateRenderScript,
|
|
8
|
-
} from "../render-utils.js";
|
|
9
|
-
|
|
10
|
-
describe("serializeValue", () => {
|
|
11
|
-
it("serializes null", () => {
|
|
12
|
-
expect(serializeValue(null)).toBe("null");
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("serializes undefined", () => {
|
|
16
|
-
expect(serializeValue(undefined)).toBe("undefined");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("serializes strings with proper escaping", () => {
|
|
20
|
-
expect(serializeValue("hello")).toBe('"hello"');
|
|
21
|
-
expect(serializeValue("hello world")).toBe('"hello world"');
|
|
22
|
-
expect(serializeValue('with "quotes"')).toBe('"with \\"quotes\\""');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("serializes numbers", () => {
|
|
26
|
-
expect(serializeValue(42)).toBe("42");
|
|
27
|
-
expect(serializeValue(3.14)).toBe("3.14");
|
|
28
|
-
expect(serializeValue(-10)).toBe("-10");
|
|
29
|
-
expect(serializeValue(0)).toBe("0");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("serializes booleans", () => {
|
|
33
|
-
expect(serializeValue(true)).toBe("true");
|
|
34
|
-
expect(serializeValue(false)).toBe("false");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("serializes arrays", () => {
|
|
38
|
-
expect(serializeValue([])).toBe("[]");
|
|
39
|
-
expect(serializeValue([1, 2, 3])).toBe("[1, 2, 3]");
|
|
40
|
-
expect(serializeValue(["a", "b"])).toBe('["a", "b"]');
|
|
41
|
-
expect(serializeValue([true, null, "x"])).toBe('[true, null, "x"]');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("serializes objects", () => {
|
|
45
|
-
expect(serializeValue({})).toBe("{}");
|
|
46
|
-
expect(serializeValue({ a: 1 })).toBe('{"a": 1}');
|
|
47
|
-
expect(serializeValue({ name: "test", value: 42 })).toBe(
|
|
48
|
-
'{"name": "test", "value": 42}'
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("serializes nested structures", () => {
|
|
53
|
-
expect(serializeValue({ items: [1, 2], nested: { deep: true } })).toBe(
|
|
54
|
-
'{"items": [1, 2], "nested": {"deep": true}}'
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("returns undefined for functions", () => {
|
|
59
|
-
expect(serializeValue(() => {})).toBe("undefined");
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe("serializePropsToJsx", () => {
|
|
64
|
-
it("serializes empty props", () => {
|
|
65
|
-
expect(serializePropsToJsx({})).toBe("");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("serializes string props as JSX attributes", () => {
|
|
69
|
-
expect(serializePropsToJsx({ variant: "primary" })).toBe(
|
|
70
|
-
'variant="primary"'
|
|
71
|
-
);
|
|
72
|
-
expect(serializePropsToJsx({ label: "Click me" })).toBe('label="Click me"');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("serializes boolean props with JSX expression syntax", () => {
|
|
76
|
-
expect(serializePropsToJsx({ disabled: true })).toBe("disabled={true}");
|
|
77
|
-
expect(serializePropsToJsx({ checked: false })).toBe("checked={false}");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("serializes number props with JSX expression syntax", () => {
|
|
81
|
-
expect(serializePropsToJsx({ count: 5 })).toBe("count={5}");
|
|
82
|
-
expect(serializePropsToJsx({ size: 24.5 })).toBe("size={24.5}");
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("serializes multiple props", () => {
|
|
86
|
-
const result = serializePropsToJsx({
|
|
87
|
-
variant: "danger",
|
|
88
|
-
disabled: false,
|
|
89
|
-
size: 16,
|
|
90
|
-
});
|
|
91
|
-
expect(result).toBe('variant="danger" disabled={false} size={16}');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("filters out undefined props", () => {
|
|
95
|
-
expect(
|
|
96
|
-
serializePropsToJsx({
|
|
97
|
-
variant: "primary",
|
|
98
|
-
disabled: undefined,
|
|
99
|
-
label: "test",
|
|
100
|
-
})
|
|
101
|
-
).toBe('variant="primary" label="test"');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("serializes object props", () => {
|
|
105
|
-
expect(serializePropsToJsx({ style: { color: "red" } })).toBe(
|
|
106
|
-
'style={{"color": "red"}}'
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("serializes array props", () => {
|
|
111
|
-
expect(serializePropsToJsx({ items: [1, 2, 3] })).toBe("items={[1, 2, 3]}");
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe("findFragmentByName", () => {
|
|
116
|
-
const mockFragments = [
|
|
117
|
-
{ path: "Button.fragment.tsx", fragment: { meta: { name: "Button" } } },
|
|
118
|
-
{ path: "Card.fragment.tsx", fragment: { meta: { name: "Card" } } },
|
|
119
|
-
{ path: "Alert.fragment.tsx", fragment: { meta: { name: "Alert" } } },
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
it("finds fragment by exact name", () => {
|
|
123
|
-
const result = findFragmentByName("Button", mockFragments);
|
|
124
|
-
expect(result).toEqual({ name: "Button", path: "Button.fragment.tsx" });
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("finds fragment case-insensitively", () => {
|
|
128
|
-
expect(findFragmentByName("button", mockFragments)).toEqual({
|
|
129
|
-
name: "Button",
|
|
130
|
-
path: "Button.fragment.tsx",
|
|
131
|
-
});
|
|
132
|
-
expect(findFragmentByName("BUTTON", mockFragments)).toEqual({
|
|
133
|
-
name: "Button",
|
|
134
|
-
path: "Button.fragment.tsx",
|
|
135
|
-
});
|
|
136
|
-
expect(findFragmentByName("BuTtOn", mockFragments)).toEqual({
|
|
137
|
-
name: "Button",
|
|
138
|
-
path: "Button.fragment.tsx",
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("returns null for non-existent component", () => {
|
|
143
|
-
expect(findFragmentByName("NonExistent", mockFragments)).toBeNull();
|
|
144
|
-
expect(findFragmentByName("", mockFragments)).toBeNull();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("handles empty fragments array", () => {
|
|
148
|
-
expect(findFragmentByName("Button", [])).toBeNull();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe("getAvailableComponents", () => {
|
|
153
|
-
it("returns sorted list of component names", () => {
|
|
154
|
-
const fragments = [
|
|
155
|
-
{ fragment: { meta: { name: "Zebra" } } },
|
|
156
|
-
{ fragment: { meta: { name: "Alert" } } },
|
|
157
|
-
{ fragment: { meta: { name: "Button" } } },
|
|
158
|
-
];
|
|
159
|
-
expect(getAvailableComponents(fragments)).toEqual([
|
|
160
|
-
"Alert",
|
|
161
|
-
"Button",
|
|
162
|
-
"Zebra",
|
|
163
|
-
]);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("returns empty array for empty fragments", () => {
|
|
167
|
-
expect(getAvailableComponents([])).toEqual([]);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe("generateRenderScript", () => {
|
|
172
|
-
it("generates script with correct import path", () => {
|
|
173
|
-
const script = generateRenderScript("/path/to/Button.fragment.tsx", "Button", {});
|
|
174
|
-
expect(script).toContain('import("/path/to/Button.fragment.tsx")');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("generates script with empty props object when no props", () => {
|
|
178
|
-
const script = generateRenderScript("/path/to/Button.tsx", "Button", {});
|
|
179
|
-
expect(script).toContain("React.createElement(Component, {})");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it("generates script with props object", () => {
|
|
183
|
-
const script = generateRenderScript("/path/to/Button.tsx", "Button", {
|
|
184
|
-
variant: "danger",
|
|
185
|
-
disabled: true,
|
|
186
|
-
});
|
|
187
|
-
expect(script).toContain(
|
|
188
|
-
'React.createElement(Component, {"variant":"danger","disabled":true})'
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("handles children prop specially", () => {
|
|
193
|
-
const script = generateRenderScript("/path/to/Button.tsx", "Button", {
|
|
194
|
-
variant: "primary",
|
|
195
|
-
children: "Click me",
|
|
196
|
-
});
|
|
197
|
-
// Children should be passed as third argument, not in props object
|
|
198
|
-
expect(script).toContain(
|
|
199
|
-
'React.createElement(Component, {"variant":"primary"}, "Click me")'
|
|
200
|
-
);
|
|
201
|
-
// Children should NOT be in the props object
|
|
202
|
-
expect(script).not.toContain('"children":"Click me"');
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("handles children with other props", () => {
|
|
206
|
-
const script = generateRenderScript("/path/to/Alert.tsx", "Alert", {
|
|
207
|
-
severity: "warning",
|
|
208
|
-
children: "Warning message",
|
|
209
|
-
dismissible: true,
|
|
210
|
-
});
|
|
211
|
-
expect(script).toContain(
|
|
212
|
-
'React.createElement(Component, {"severity":"warning","dismissible":true}, "Warning message")'
|
|
213
|
-
);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("includes React and ReactDOM imports", () => {
|
|
217
|
-
const script = generateRenderScript("/path/to/Button.tsx", "Button", {});
|
|
218
|
-
expect(script).toContain('import React from "react"');
|
|
219
|
-
expect(script).toContain('import { createRoot } from "react-dom/client"');
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("includes render ready signal", () => {
|
|
223
|
-
const script = generateRenderScript("/path/to/Button.tsx", "Button", {});
|
|
224
|
-
expect(script).toContain("window.__RENDER_READY__ = true");
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("includes error handling", () => {
|
|
228
|
-
const script = generateRenderScript("/path/to/Button.tsx", "Button", {});
|
|
229
|
-
expect(script).toContain("window.__RENDER_ERROR__");
|
|
230
|
-
expect(script).toContain('class="render-error"');
|
|
231
|
-
});
|
|
232
|
-
});
|
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
compareStyles,
|
|
4
|
-
compareStyleValue,
|
|
5
|
-
normalizeStyleValue,
|
|
6
|
-
compareColors,
|
|
7
|
-
parseColor,
|
|
8
|
-
compareNumericValues,
|
|
9
|
-
} from "../style-utils.js";
|
|
10
|
-
|
|
11
|
-
describe("parseColor", () => {
|
|
12
|
-
describe("hex colors", () => {
|
|
13
|
-
it("parses 6-digit hex color", () => {
|
|
14
|
-
expect(parseColor("#ff0000")).toEqual({ r: 255, g: 0, b: 0 });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("parses lowercase hex color", () => {
|
|
18
|
-
expect(parseColor("#00ff00")).toEqual({ r: 0, g: 255, b: 0 });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("parses mixed case hex color", () => {
|
|
22
|
-
expect(parseColor("#0000FF")).toEqual({ r: 0, g: 0, b: 255 });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("parses hex color with values 0-255", () => {
|
|
26
|
-
expect(parseColor("#336699")).toEqual({ r: 51, g: 102, b: 153 });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("returns null for 3-digit hex color", () => {
|
|
30
|
-
expect(parseColor("#f00")).toBeNull();
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("rgb/rgba colors", () => {
|
|
35
|
-
it("parses rgb color", () => {
|
|
36
|
-
expect(parseColor("rgb(255, 0, 0)")).toEqual({ r: 255, g: 0, b: 0, a: 1 });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("parses rgba color with alpha", () => {
|
|
40
|
-
expect(parseColor("rgba(255, 0, 0, 0.5)")).toEqual({
|
|
41
|
-
r: 255,
|
|
42
|
-
g: 0,
|
|
43
|
-
b: 0,
|
|
44
|
-
a: 0.5,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("parses rgba color with alpha 0", () => {
|
|
49
|
-
expect(parseColor("rgba(0, 0, 0, 0)")).toEqual({
|
|
50
|
-
r: 0,
|
|
51
|
-
g: 0,
|
|
52
|
-
b: 0,
|
|
53
|
-
a: 0,
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("parses rgba color with alpha 1", () => {
|
|
58
|
-
expect(parseColor("rgba(100, 150, 200, 1)")).toEqual({
|
|
59
|
-
r: 100,
|
|
60
|
-
g: 150,
|
|
61
|
-
b: 200,
|
|
62
|
-
a: 1,
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("handles extra whitespace", () => {
|
|
67
|
-
expect(parseColor("rgb( 255 , 128 , 64 )")).toEqual({
|
|
68
|
-
r: 255,
|
|
69
|
-
g: 128,
|
|
70
|
-
b: 64,
|
|
71
|
-
a: 1,
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("invalid colors", () => {
|
|
77
|
-
it("returns null for color keywords", () => {
|
|
78
|
-
expect(parseColor("red")).toBeNull();
|
|
79
|
-
expect(parseColor("transparent")).toBeNull();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("returns null for hsl colors", () => {
|
|
83
|
-
expect(parseColor("hsl(0, 100%, 50%)")).toBeNull();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("returns null for empty string", () => {
|
|
87
|
-
expect(parseColor("")).toBeNull();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe("compareColors", () => {
|
|
93
|
-
it("matches identical hex colors", () => {
|
|
94
|
-
expect(compareColors("#ff0000", "#ff0000", 0)).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("matches colors within tolerance", () => {
|
|
98
|
-
expect(compareColors("#ff0000", "#fe0000", 5)).toBe(true);
|
|
99
|
-
expect(compareColors("rgb(255, 0, 0)", "rgb(252, 0, 0)", 5)).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("fails colors outside tolerance", () => {
|
|
103
|
-
expect(compareColors("#ff0000", "#f00000", 5)).toBe(false);
|
|
104
|
-
expect(compareColors("rgb(255, 0, 0)", "rgb(240, 0, 0)", 5)).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("matches hex and rgba representations of same color", () => {
|
|
108
|
-
expect(compareColors("#ff0000", "rgba(255, 0, 0, 1)", 0)).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("matches colors with similar alpha", () => {
|
|
112
|
-
expect(compareColors("rgba(255, 0, 0, 0.5)", "rgba(255, 0, 0, 0.52)", 5)).toBe(
|
|
113
|
-
true
|
|
114
|
-
);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("fails colors with different alpha", () => {
|
|
118
|
-
expect(compareColors("rgba(255, 0, 0, 0.5)", "rgba(255, 0, 0, 0.7)", 5)).toBe(
|
|
119
|
-
false
|
|
120
|
-
);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("falls back to string comparison for unparseable colors", () => {
|
|
124
|
-
expect(compareColors("red", "red", 0)).toBe(true);
|
|
125
|
-
expect(compareColors("red", "blue", 0)).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe("compareNumericValues", () => {
|
|
130
|
-
it("matches identical values", () => {
|
|
131
|
-
expect(compareNumericValues("10px", "10px", 0)).toBe(true);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("matches values within tolerance", () => {
|
|
135
|
-
expect(compareNumericValues("10px", "11px", 1)).toBe(true);
|
|
136
|
-
expect(compareNumericValues("10px", "9px", 1)).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("fails values outside tolerance", () => {
|
|
140
|
-
expect(compareNumericValues("10px", "12px", 1)).toBe(false);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("handles values without units", () => {
|
|
144
|
-
expect(compareNumericValues("10", "11", 1)).toBe(true);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("handles decimal values", () => {
|
|
148
|
-
expect(compareNumericValues("10.5px", "11px", 1)).toBe(true);
|
|
149
|
-
expect(compareNumericValues("10.5px", "12px", 1)).toBe(false);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("falls back to string comparison for non-numeric values", () => {
|
|
153
|
-
expect(compareNumericValues("auto", "auto", 0)).toBe(true);
|
|
154
|
-
expect(compareNumericValues("auto", "none", 0)).toBe(false);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe("normalizeStyleValue", () => {
|
|
159
|
-
it("trims whitespace", () => {
|
|
160
|
-
expect(normalizeStyleValue("any", " 10px ")).toBe("10px");
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("collapses multiple spaces", () => {
|
|
164
|
-
expect(normalizeStyleValue("any", "10px 20px")).toBe("10px 20px");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('normalizes "none" boxShadow to empty string', () => {
|
|
168
|
-
expect(normalizeStyleValue("boxShadow", "none")).toBe("");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('does not normalize "none" for other properties', () => {
|
|
172
|
-
expect(normalizeStyleValue("display", "none")).toBe("none");
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it("normalizes transparent color to 'transparent'", () => {
|
|
176
|
-
expect(normalizeStyleValue("backgroundColor", "rgba(0, 0, 0, 0)")).toBe(
|
|
177
|
-
"transparent"
|
|
178
|
-
);
|
|
179
|
-
expect(normalizeStyleValue("borderColor", "rgba( 0 , 0 , 0 , 0 )")).toBe(
|
|
180
|
-
"transparent"
|
|
181
|
-
);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe("compareStyleValue", () => {
|
|
186
|
-
describe("exact match", () => {
|
|
187
|
-
it("matches identical values", () => {
|
|
188
|
-
expect(compareStyleValue("fontFamily", "Inter", "Inter")).toBe(true);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("matches after normalization", () => {
|
|
192
|
-
expect(compareStyleValue("fontFamily", " Inter ", "Inter")).toBe(true);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
describe("color properties", () => {
|
|
197
|
-
it("uses color comparison for backgroundColor", () => {
|
|
198
|
-
expect(
|
|
199
|
-
compareStyleValue("backgroundColor", "#ff0000", "rgb(255, 0, 0)")
|
|
200
|
-
).toBe(true);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("uses color comparison for borderColor", () => {
|
|
204
|
-
expect(
|
|
205
|
-
compareStyleValue("borderColor", "#00ff00", "rgb(0, 255, 0)")
|
|
206
|
-
).toBe(true);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it("allows tolerance in color comparison", () => {
|
|
210
|
-
expect(
|
|
211
|
-
compareStyleValue("backgroundColor", "#ff0000", "rgb(252, 0, 0)")
|
|
212
|
-
).toBe(true);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe("numeric properties", () => {
|
|
217
|
-
it("uses numeric comparison for borderWidth", () => {
|
|
218
|
-
expect(compareStyleValue("borderWidth", "1px", "2px")).toBe(true);
|
|
219
|
-
expect(compareStyleValue("borderWidth", "1px", "3px")).toBe(false);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("uses numeric comparison for borderRadius", () => {
|
|
223
|
-
expect(compareStyleValue("borderRadius", "8px", "9px")).toBe(true);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("uses numeric comparison for fontSize", () => {
|
|
227
|
-
expect(compareStyleValue("fontSize", "16px", "17px")).toBe(true);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("uses numeric comparison for padding", () => {
|
|
231
|
-
expect(compareStyleValue("padding", "10px", "11px")).toBe(true);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("uses numeric comparison for gap", () => {
|
|
235
|
-
expect(compareStyleValue("gap", "16px", "17px")).toBe(true);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
describe("other properties", () => {
|
|
240
|
-
it("uses strict comparison for fontFamily", () => {
|
|
241
|
-
expect(compareStyleValue("fontFamily", "Inter", "Arial")).toBe(false);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("uses strict comparison for fontWeight", () => {
|
|
245
|
-
expect(compareStyleValue("fontWeight", "400", "500")).toBe(false);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("uses strict comparison for textAlign", () => {
|
|
249
|
-
expect(compareStyleValue("textAlign", "left", "center")).toBe(false);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
describe("compareStyles", () => {
|
|
255
|
-
it("returns match true when all styles match", () => {
|
|
256
|
-
const figmaStyles = {
|
|
257
|
-
backgroundColor: "#ff0000",
|
|
258
|
-
fontSize: "16px",
|
|
259
|
-
};
|
|
260
|
-
const renderedStyles = {
|
|
261
|
-
backgroundColor: "rgb(255, 0, 0)",
|
|
262
|
-
fontSize: "16px",
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
266
|
-
expect(result.match).toBe(true);
|
|
267
|
-
expect(result.properties).toHaveLength(2);
|
|
268
|
-
expect(result.properties.every((p) => p.match)).toBe(true);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("returns match false when some styles differ", () => {
|
|
272
|
-
const figmaStyles = {
|
|
273
|
-
backgroundColor: "#ff0000",
|
|
274
|
-
fontSize: "16px",
|
|
275
|
-
};
|
|
276
|
-
const renderedStyles = {
|
|
277
|
-
backgroundColor: "rgb(0, 255, 0)",
|
|
278
|
-
fontSize: "16px",
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
282
|
-
expect(result.match).toBe(false);
|
|
283
|
-
expect(result.properties.find((p) => p.property === "backgroundColor")?.match).toBe(
|
|
284
|
-
false
|
|
285
|
-
);
|
|
286
|
-
expect(result.properties.find((p) => p.property === "fontSize")?.match).toBe(
|
|
287
|
-
true
|
|
288
|
-
);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it("only compares properties present in figmaStyles", () => {
|
|
292
|
-
const figmaStyles = {
|
|
293
|
-
backgroundColor: "#ff0000",
|
|
294
|
-
};
|
|
295
|
-
const renderedStyles = {
|
|
296
|
-
backgroundColor: "rgb(255, 0, 0)",
|
|
297
|
-
fontSize: "16px",
|
|
298
|
-
fontFamily: "Inter",
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
302
|
-
expect(result.properties).toHaveLength(1);
|
|
303
|
-
expect(result.properties[0].property).toBe("backgroundColor");
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it("marks missing rendered styles as not matching", () => {
|
|
307
|
-
const figmaStyles = {
|
|
308
|
-
backgroundColor: "#ff0000",
|
|
309
|
-
borderRadius: "8px",
|
|
310
|
-
};
|
|
311
|
-
const renderedStyles = {
|
|
312
|
-
backgroundColor: "rgb(255, 0, 0)",
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
316
|
-
const borderRadius = result.properties.find(
|
|
317
|
-
(p) => p.property === "borderRadius"
|
|
318
|
-
);
|
|
319
|
-
expect(borderRadius?.rendered).toBe("(not set)");
|
|
320
|
-
expect(borderRadius?.match).toBe(false);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it("returns cleaned figmaStyles object", () => {
|
|
324
|
-
const figmaStyles = {
|
|
325
|
-
backgroundColor: "#ff0000",
|
|
326
|
-
fontSize: undefined,
|
|
327
|
-
fontFamily: "Inter",
|
|
328
|
-
};
|
|
329
|
-
const renderedStyles = {
|
|
330
|
-
backgroundColor: "rgb(255, 0, 0)",
|
|
331
|
-
fontFamily: "Inter",
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
335
|
-
expect(result.figmaStyles).toEqual({
|
|
336
|
-
backgroundColor: "#ff0000",
|
|
337
|
-
fontFamily: "Inter",
|
|
338
|
-
});
|
|
339
|
-
expect(result.figmaStyles.fontSize).toBeUndefined();
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it("preserves renderedStyles in output", () => {
|
|
343
|
-
const figmaStyles = {
|
|
344
|
-
backgroundColor: "#ff0000",
|
|
345
|
-
};
|
|
346
|
-
const renderedStyles = {
|
|
347
|
-
backgroundColor: "rgb(255, 0, 0)",
|
|
348
|
-
fontSize: "16px",
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
352
|
-
expect(result.renderedStyles).toBe(renderedStyles);
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it("handles empty figmaStyles", () => {
|
|
356
|
-
const figmaStyles = {};
|
|
357
|
-
const renderedStyles = {
|
|
358
|
-
backgroundColor: "rgb(255, 0, 0)",
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
362
|
-
expect(result.match).toBe(true);
|
|
363
|
-
expect(result.properties).toHaveLength(0);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it("compares all standard properties when present", () => {
|
|
367
|
-
const figmaStyles = {
|
|
368
|
-
backgroundColor: "#ff0000",
|
|
369
|
-
borderColor: "#000000",
|
|
370
|
-
borderWidth: "1px",
|
|
371
|
-
borderRadius: "4px",
|
|
372
|
-
fontFamily: "Inter",
|
|
373
|
-
fontSize: "16px",
|
|
374
|
-
fontWeight: "400",
|
|
375
|
-
lineHeight: "24px",
|
|
376
|
-
letterSpacing: "-0.5px",
|
|
377
|
-
textAlign: "left",
|
|
378
|
-
boxShadow: "0px 4px 8px rgba(0, 0, 0, 0.25)",
|
|
379
|
-
padding: "16px",
|
|
380
|
-
gap: "8px",
|
|
381
|
-
opacity: "0.9",
|
|
382
|
-
};
|
|
383
|
-
const renderedStyles = {
|
|
384
|
-
backgroundColor: "rgb(255, 0, 0)",
|
|
385
|
-
borderColor: "rgb(0, 0, 0)",
|
|
386
|
-
borderWidth: "1px",
|
|
387
|
-
borderRadius: "4px",
|
|
388
|
-
fontFamily: "Inter",
|
|
389
|
-
fontSize: "16px",
|
|
390
|
-
fontWeight: "400",
|
|
391
|
-
lineHeight: "24px",
|
|
392
|
-
letterSpacing: "-0.5px",
|
|
393
|
-
textAlign: "left",
|
|
394
|
-
boxShadow: "0px 4px 8px rgba(0, 0, 0, 0.25)",
|
|
395
|
-
padding: "16px",
|
|
396
|
-
gap: "8px",
|
|
397
|
-
opacity: "0.9",
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
const result = compareStyles(figmaStyles, renderedStyles);
|
|
401
|
-
expect(result.match).toBe(true);
|
|
402
|
-
expect(result.properties).toHaveLength(14);
|
|
403
|
-
});
|
|
404
|
-
});
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
// Inline base64 data URL for the Fragments logo PNG.
|
|
2
|
-
// Vite cannot resolve static asset imports from node_modules,
|
|
3
|
-
// so we embed the image directly to avoid resolution errors.
|
|
4
|
-
export const fragmentsLogo = "data:image/png;base64,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";
|
|
Binary file
|