@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.
Files changed (223) hide show
  1. package/dist/ai-client-I6MDWNYA.js +21 -0
  2. package/dist/bin.js +292 -367
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
  5. package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
  6. package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
  7. package/dist/chunk-GVDSFQ4E.js.map +1 -0
  8. package/dist/chunk-JJ2VRTBU.js +626 -0
  9. package/dist/chunk-JJ2VRTBU.js.map +1 -0
  10. package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
  11. package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
  12. package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
  13. package/dist/chunk-OQKMEFOS.js.map +1 -0
  14. package/dist/chunk-SXTKFDCR.js +104 -0
  15. package/dist/chunk-SXTKFDCR.js.map +1 -0
  16. package/dist/chunk-T5OMVL7E.js +443 -0
  17. package/dist/chunk-T5OMVL7E.js.map +1 -0
  18. package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
  19. package/dist/chunk-TPWGL2XS.js.map +1 -0
  20. package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
  21. package/dist/chunk-WFS63PCW.js.map +1 -0
  22. package/dist/core/index.js +9 -1
  23. package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
  24. package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
  25. package/dist/index.js +7 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/init-ZSX3NRCZ.js +636 -0
  28. package/dist/init-ZSX3NRCZ.js.map +1 -0
  29. package/dist/mcp-bin.js +2 -2
  30. package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
  31. package/dist/scan-generate-SYU4PYZD.js +1115 -0
  32. package/dist/scan-generate-SYU4PYZD.js.map +1 -0
  33. package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
  34. package/dist/snapshot-XOISO2IS.js +139 -0
  35. package/dist/snapshot-XOISO2IS.js.map +1 -0
  36. package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
  37. package/dist/static-viewer-5GXH2MGE.js.map +1 -0
  38. package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
  39. package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
  40. package/dist/{viewer-DNMNC5VS.js → viewer-7ZEAFBVN.js} +80 -58
  41. package/dist/viewer-7ZEAFBVN.js.map +1 -0
  42. package/package.json +6 -14
  43. package/src/ai-client.ts +156 -0
  44. package/src/bin.ts +74 -2
  45. package/src/build.ts +95 -33
  46. package/src/commands/__tests__/drift-sync.test.ts +252 -0
  47. package/src/commands/__tests__/scan-generate.test.ts +497 -45
  48. package/src/commands/enhance.ts +11 -35
  49. package/src/commands/init.ts +296 -193
  50. package/src/commands/scan-generate.ts +740 -139
  51. package/src/commands/scan.ts +37 -32
  52. package/src/commands/setup.ts +143 -52
  53. package/src/commands/snapshot.ts +197 -0
  54. package/src/commands/sync.ts +357 -0
  55. package/src/commands/validate.ts +43 -1
  56. package/src/core/component-extractor.test.ts +282 -0
  57. package/src/core/component-extractor.ts +1030 -0
  58. package/src/core/discovery.ts +93 -7
  59. package/src/service/enhance/props-extractor.ts +235 -13
  60. package/src/validators.ts +236 -0
  61. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  62. package/src/viewer/server.ts +37 -22
  63. package/src/viewer/vite-plugin.ts +25 -9
  64. package/dist/chunk-5G3VZH43.js.map +0 -1
  65. package/dist/chunk-OQO55NKV.js.map +0 -1
  66. package/dist/chunk-WXSR2II7.js.map +0 -1
  67. package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
  68. package/dist/init-NDQXUWDU.js +0 -796
  69. package/dist/init-NDQXUWDU.js.map +0 -1
  70. package/dist/scan-generate-SJAN5MVI.js +0 -691
  71. package/dist/scan-generate-SJAN5MVI.js.map +0 -1
  72. package/dist/viewer-DNMNC5VS.js.map +0 -1
  73. package/src/ai.ts +0 -266
  74. package/src/commands/init-framework.ts +0 -414
  75. package/src/mcp/bin.ts +0 -36
  76. package/src/migrate/bin.ts +0 -114
  77. package/src/theme/index.ts +0 -77
  78. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  79. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  80. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  81. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  82. package/src/viewer/assets/fragments-logo.ts +0 -4
  83. package/src/viewer/assets/fragments_logo.png +0 -0
  84. package/src/viewer/bin.ts +0 -86
  85. package/src/viewer/cli/health.ts +0 -256
  86. package/src/viewer/cli/index.ts +0 -33
  87. package/src/viewer/cli/scan.ts +0 -124
  88. package/src/viewer/cli/utils.ts +0 -174
  89. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  90. package/src/viewer/components/ActionCapture.tsx +0 -172
  91. package/src/viewer/components/ActionsPanel.tsx +0 -332
  92. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  93. package/src/viewer/components/App.tsx +0 -582
  94. package/src/viewer/components/BottomPanel.tsx +0 -288
  95. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  96. package/src/viewer/components/CodePanel.tsx +0 -118
  97. package/src/viewer/components/CommandPalette.tsx +0 -392
  98. package/src/viewer/components/ComponentDocView.tsx +0 -164
  99. package/src/viewer/components/ComponentGraph.tsx +0 -380
  100. package/src/viewer/components/ComponentHeader.tsx +0 -88
  101. package/src/viewer/components/ContractPanel.tsx +0 -241
  102. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  103. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  104. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  105. package/src/viewer/components/FragmentEditor.tsx +0 -525
  106. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  107. package/src/viewer/components/HeaderSearch.tsx +0 -24
  108. package/src/viewer/components/HealthDashboard.tsx +0 -441
  109. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  110. package/src/viewer/components/Icons.tsx +0 -479
  111. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  112. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  113. package/src/viewer/components/IsolatedRender.tsx +0 -113
  114. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  115. package/src/viewer/components/LandingPage.tsx +0 -421
  116. package/src/viewer/components/Layout.tsx +0 -27
  117. package/src/viewer/components/LeftSidebar.tsx +0 -472
  118. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  119. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  120. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  121. package/src/viewer/components/PanelShell.tsx +0 -161
  122. package/src/viewer/components/PerformancePanel.tsx +0 -304
  123. package/src/viewer/components/PreviewArea.tsx +0 -472
  124. package/src/viewer/components/PreviewAside.tsx +0 -168
  125. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  126. package/src/viewer/components/PreviewPane.tsx +0 -149
  127. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  128. package/src/viewer/components/PropsEditor.tsx +0 -506
  129. package/src/viewer/components/PropsTable.tsx +0 -111
  130. package/src/viewer/components/RelationsSection.tsx +0 -88
  131. package/src/viewer/components/ResizablePanel.tsx +0 -271
  132. package/src/viewer/components/RightSidebar.tsx +0 -102
  133. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  134. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  135. package/src/viewer/components/Sidebar.tsx +0 -169
  136. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  137. package/src/viewer/components/ThemeProvider.tsx +0 -42
  138. package/src/viewer/components/Toast.tsx +0 -3
  139. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  140. package/src/viewer/components/TopToolbar.tsx +0 -159
  141. package/src/viewer/components/UsageSection.tsx +0 -95
  142. package/src/viewer/components/VariantMatrix.tsx +0 -388
  143. package/src/viewer/components/VariantRenderer.tsx +0 -131
  144. package/src/viewer/components/VariantTabs.tsx +0 -40
  145. package/src/viewer/components/ViewerHeader.tsx +0 -69
  146. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  147. package/src/viewer/components/ViewportSelector.tsx +0 -172
  148. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  149. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  150. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  151. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  152. package/src/viewer/components/viewer-utils.ts +0 -16
  153. package/src/viewer/composition-renderer.ts +0 -381
  154. package/src/viewer/constants/index.ts +0 -1
  155. package/src/viewer/constants/ui.ts +0 -166
  156. package/src/viewer/entry.tsx +0 -335
  157. package/src/viewer/hooks/index.ts +0 -2
  158. package/src/viewer/hooks/useA11yCache.ts +0 -383
  159. package/src/viewer/hooks/useA11yService.ts +0 -364
  160. package/src/viewer/hooks/useActions.ts +0 -138
  161. package/src/viewer/hooks/useAppState.ts +0 -147
  162. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  163. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  164. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  165. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  166. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  167. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  168. package/src/viewer/hooks/useUrlState.ts +0 -318
  169. package/src/viewer/hooks/useViewSettings.ts +0 -111
  170. package/src/viewer/index.html +0 -28
  171. package/src/viewer/intelligence/healthReport.ts +0 -505
  172. package/src/viewer/intelligence/styleDrift.ts +0 -340
  173. package/src/viewer/intelligence/usageScanner.ts +0 -309
  174. package/src/viewer/jsx-parser.ts +0 -486
  175. package/src/viewer/preview-frame-entry.tsx +0 -25
  176. package/src/viewer/preview-frame.html +0 -125
  177. package/src/viewer/public/favicon.ico +0 -0
  178. package/src/viewer/render-template.html +0 -68
  179. package/src/viewer/styles/globals.css +0 -278
  180. package/src/viewer/types/a11y.ts +0 -197
  181. package/src/viewer/utils/a11y-fixes.ts +0 -509
  182. package/src/viewer/utils/actionExport.ts +0 -372
  183. package/src/viewer/utils/colorSchemes.ts +0 -201
  184. package/src/viewer/utils/detectRelationships.ts +0 -256
  185. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  186. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  187. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  188. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  189. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  190. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  191. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  192. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  193. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  194. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  195. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  196. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  197. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  198. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -137
  199. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  200. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  201. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  202. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  203. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  204. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  205. package/src/viewer/vendor/shared/src/index.ts +0 -34
  206. package/src/viewer/vendor/shared/src/types.ts +0 -53
  207. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  208. package/src/viewer/webmcp/analytics.ts +0 -165
  209. package/src/viewer/webmcp/index.ts +0 -3
  210. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  211. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  212. package/src/viewer/webmcp/scan-utils.ts +0 -135
  213. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  214. package/src/viewer/webmcp/viewer-state.ts +0 -45
  215. /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
  216. /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
  217. /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
  218. /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
  219. /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
  220. /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
  221. /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
  222. /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
  223. /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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";