@fragments-sdk/cli 0.10.0 → 0.11.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.
Files changed (167) hide show
  1. package/dist/bin.js +26 -8
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ZDA3PLQ6.js → chunk-5G3VZH43.js} +2 -2
  4. package/dist/{chunk-566BNPQZ.js → chunk-HRFUSSZI.js} +25 -6
  5. package/dist/chunk-HRFUSSZI.js.map +1 -0
  6. package/dist/{chunk-CAMXG5HJ.js → chunk-ZM4ZQZWZ.js} +2 -2
  7. package/dist/{generate-BGKTKO6E.js → generate-FBHSXR3D.js} +2 -2
  8. package/dist/index.js +2 -2
  9. package/dist/{init-Q53R5Q2T.js → init-UFGK5TCN.js} +77 -6
  10. package/dist/init-UFGK5TCN.js.map +1 -0
  11. package/dist/{scan-OQU7M4GH.js → scan-CJF2DOQW.js} +3 -3
  12. package/dist/{scan-generate-T5QNUG7N.js → scan-generate-SJAN5MVI.js} +2 -2
  13. package/dist/snapshot-SV2JOFZH.js +139 -0
  14. package/dist/snapshot-SV2JOFZH.js.map +1 -0
  15. package/dist/{test-2CSOSS3B.js → test-Z5LVO724.js} +2 -2
  16. package/dist/{tokens-DXEGYTOJ.js → tokens-CE46OTMD.js} +2 -2
  17. package/dist/{viewer-DBEPYM3G.js → viewer-DLLJIMCK.js} +69 -47
  18. package/dist/viewer-DLLJIMCK.js.map +1 -0
  19. package/package.json +6 -14
  20. package/src/bin.ts +30 -0
  21. package/src/commands/init.ts +76 -1
  22. package/src/commands/snapshot.ts +197 -0
  23. package/src/core/loader.ts +38 -8
  24. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  25. package/src/viewer/server.ts +37 -22
  26. package/src/viewer/vite-plugin.ts +25 -9
  27. package/dist/chunk-566BNPQZ.js.map +0 -1
  28. package/dist/init-Q53R5Q2T.js.map +0 -1
  29. package/dist/viewer-DBEPYM3G.js.map +0 -1
  30. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  31. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  32. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  33. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  34. package/src/viewer/assets/fragments-logo.ts +0 -4
  35. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  36. package/src/viewer/components/ActionCapture.tsx +0 -172
  37. package/src/viewer/components/ActionsPanel.tsx +0 -332
  38. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  39. package/src/viewer/components/App.tsx +0 -582
  40. package/src/viewer/components/BottomPanel.tsx +0 -288
  41. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  42. package/src/viewer/components/CodePanel.tsx +0 -118
  43. package/src/viewer/components/CommandPalette.tsx +0 -392
  44. package/src/viewer/components/ComponentDocView.tsx +0 -164
  45. package/src/viewer/components/ComponentGraph.tsx +0 -380
  46. package/src/viewer/components/ComponentHeader.tsx +0 -88
  47. package/src/viewer/components/ContractPanel.tsx +0 -241
  48. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  49. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  50. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  51. package/src/viewer/components/FragmentEditor.tsx +0 -525
  52. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  53. package/src/viewer/components/HeaderSearch.tsx +0 -24
  54. package/src/viewer/components/HealthDashboard.tsx +0 -441
  55. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  56. package/src/viewer/components/Icons.tsx +0 -479
  57. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  58. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  59. package/src/viewer/components/IsolatedRender.tsx +0 -113
  60. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  61. package/src/viewer/components/LandingPage.tsx +0 -421
  62. package/src/viewer/components/Layout.tsx +0 -27
  63. package/src/viewer/components/LeftSidebar.tsx +0 -472
  64. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  65. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  66. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  67. package/src/viewer/components/PanelShell.tsx +0 -161
  68. package/src/viewer/components/PerformancePanel.tsx +0 -304
  69. package/src/viewer/components/PreviewArea.tsx +0 -472
  70. package/src/viewer/components/PreviewAside.tsx +0 -168
  71. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  72. package/src/viewer/components/PreviewPane.tsx +0 -149
  73. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  74. package/src/viewer/components/PropsEditor.tsx +0 -506
  75. package/src/viewer/components/PropsTable.tsx +0 -111
  76. package/src/viewer/components/RelationsSection.tsx +0 -88
  77. package/src/viewer/components/ResizablePanel.tsx +0 -271
  78. package/src/viewer/components/RightSidebar.tsx +0 -102
  79. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  80. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  81. package/src/viewer/components/Sidebar.tsx +0 -169
  82. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  83. package/src/viewer/components/ThemeProvider.tsx +0 -42
  84. package/src/viewer/components/Toast.tsx +0 -3
  85. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  86. package/src/viewer/components/TopToolbar.tsx +0 -159
  87. package/src/viewer/components/UsageSection.tsx +0 -95
  88. package/src/viewer/components/VariantMatrix.tsx +0 -388
  89. package/src/viewer/components/VariantRenderer.tsx +0 -131
  90. package/src/viewer/components/VariantTabs.tsx +0 -40
  91. package/src/viewer/components/ViewerHeader.tsx +0 -69
  92. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  93. package/src/viewer/components/ViewportSelector.tsx +0 -172
  94. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  95. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  96. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  97. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  98. package/src/viewer/components/viewer-utils.ts +0 -16
  99. package/src/viewer/composition-renderer.ts +0 -381
  100. package/src/viewer/constants/index.ts +0 -1
  101. package/src/viewer/constants/ui.ts +0 -166
  102. package/src/viewer/entry.tsx +0 -335
  103. package/src/viewer/hooks/index.ts +0 -2
  104. package/src/viewer/hooks/useA11yCache.ts +0 -383
  105. package/src/viewer/hooks/useA11yService.ts +0 -364
  106. package/src/viewer/hooks/useActions.ts +0 -138
  107. package/src/viewer/hooks/useAppState.ts +0 -147
  108. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  109. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  110. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  111. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  112. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  113. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  114. package/src/viewer/hooks/useUrlState.ts +0 -318
  115. package/src/viewer/hooks/useViewSettings.ts +0 -111
  116. package/src/viewer/index.html +0 -28
  117. package/src/viewer/intelligence/healthReport.ts +0 -505
  118. package/src/viewer/intelligence/styleDrift.ts +0 -340
  119. package/src/viewer/intelligence/usageScanner.ts +0 -309
  120. package/src/viewer/jsx-parser.ts +0 -486
  121. package/src/viewer/preview-frame-entry.tsx +0 -25
  122. package/src/viewer/preview-frame.html +0 -125
  123. package/src/viewer/public/favicon.ico +0 -0
  124. package/src/viewer/render-template.html +0 -68
  125. package/src/viewer/styles/globals.css +0 -278
  126. package/src/viewer/types/a11y.ts +0 -197
  127. package/src/viewer/utils/a11y-fixes.ts +0 -509
  128. package/src/viewer/utils/actionExport.ts +0 -372
  129. package/src/viewer/utils/colorSchemes.ts +0 -201
  130. package/src/viewer/utils/detectRelationships.ts +0 -256
  131. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  132. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  133. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  134. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  135. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  136. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  137. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  138. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  139. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  140. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  141. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  142. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  143. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  144. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -134
  145. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  146. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  147. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  148. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  149. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  150. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  151. package/src/viewer/vendor/shared/src/index.ts +0 -34
  152. package/src/viewer/vendor/shared/src/types.ts +0 -53
  153. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  154. package/src/viewer/webmcp/analytics.ts +0 -165
  155. package/src/viewer/webmcp/index.ts +0 -3
  156. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  157. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  158. package/src/viewer/webmcp/scan-utils.ts +0 -135
  159. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  160. package/src/viewer/webmcp/viewer-state.ts +0 -45
  161. /package/dist/{chunk-ZDA3PLQ6.js.map → chunk-5G3VZH43.js.map} +0 -0
  162. /package/dist/{chunk-CAMXG5HJ.js.map → chunk-ZM4ZQZWZ.js.map} +0 -0
  163. /package/dist/{generate-BGKTKO6E.js.map → generate-FBHSXR3D.js.map} +0 -0
  164. /package/dist/{scan-OQU7M4GH.js.map → scan-CJF2DOQW.js.map} +0 -0
  165. /package/dist/{scan-generate-T5QNUG7N.js.map → scan-generate-SJAN5MVI.js.map} +0 -0
  166. /package/dist/{test-2CSOSS3B.js.map → test-Z5LVO724.js.map} +0 -0
  167. /package/dist/{tokens-DXEGYTOJ.js.map → tokens-CE46OTMD.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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";