@fragments-sdk/cli 0.9.0 → 0.10.0

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 (166) hide show
  1. package/dist/bin.d.ts +1 -0
  2. package/dist/bin.js +502 -84
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-CJEGT3WD.js → chunk-566BNPQZ.js} +21 -6
  5. package/dist/chunk-566BNPQZ.js.map +1 -0
  6. package/dist/{chunk-WI6SLMSO.js → chunk-CAMXG5HJ.js} +5 -5
  7. package/dist/chunk-D2CDBRNU.js +2 -0
  8. package/dist/{chunk-YMPGYEWK.js → chunk-D5PYOXEI.js} +2 -2
  9. package/dist/{chunk-NGIMCIK2.js → chunk-OQO55NKV.js} +405 -34
  10. package/dist/chunk-OQO55NKV.js.map +1 -0
  11. package/dist/{chunk-TOIE7VXF.js → chunk-PW7QTQA6.js} +2 -2
  12. package/dist/{chunk-AWYCDRPG.js → chunk-WXSR2II7.js} +2 -2
  13. package/dist/chunk-WXSR2II7.js.map +1 -0
  14. package/dist/{chunk-2JIKCJX3.js → chunk-ZDA3PLQ6.js} +17 -14
  15. package/dist/chunk-ZDA3PLQ6.js.map +1 -0
  16. package/dist/core/index.d.ts +1 -2092
  17. package/dist/core/index.js +26 -21
  18. package/dist/{discovery-Z4RDDFVR.js → discovery-NEOY4MPN.js} +3 -3
  19. package/dist/generate-BGKTKO6E.js +459 -0
  20. package/dist/generate-BGKTKO6E.js.map +1 -0
  21. package/dist/index.d.ts +3 -5
  22. package/dist/index.js +7 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/{init-KSAAS7X3.js → init-Q53R5Q2T.js} +66 -76
  25. package/dist/init-Q53R5Q2T.js.map +1 -0
  26. package/dist/mcp-bin.js +5 -7
  27. package/dist/mcp-bin.js.map +1 -1
  28. package/dist/scan-OQU7M4GH.js +14 -0
  29. package/dist/scan-generate-T5QNUG7N.js +691 -0
  30. package/dist/scan-generate-T5QNUG7N.js.map +1 -0
  31. package/dist/{service-A5GIGGGK.js → service-TQYWY65E.js} +4 -5
  32. package/dist/{static-viewer-NSODM5VX.js → static-viewer-NUBFPKWH.js} +4 -5
  33. package/dist/static-viewer-NUBFPKWH.js.map +1 -0
  34. package/dist/{test-RPWZAYSJ.js → test-2CSOSS3B.js} +4 -5
  35. package/dist/{test-RPWZAYSJ.js.map → test-2CSOSS3B.js.map} +1 -1
  36. package/dist/{tokens-NIXSZRX7.js → tokens-DXEGYTOJ.js} +6 -7
  37. package/dist/{tokens-NIXSZRX7.js.map → tokens-DXEGYTOJ.js.map} +1 -1
  38. package/dist/{viewer-SBTJDMP7.js → viewer-DBEPYM3G.js} +245 -23
  39. package/dist/viewer-DBEPYM3G.js.map +1 -0
  40. package/package.json +2 -1
  41. package/src/bin.ts +33 -1
  42. package/src/build.ts +13 -3
  43. package/src/commands/__tests__/scan-generate.test.ts +308 -0
  44. package/src/commands/build.ts +16 -2
  45. package/src/commands/generate.ts +383 -68
  46. package/src/commands/init.ts +81 -56
  47. package/src/commands/perf.ts +1 -1
  48. package/src/commands/scan-generate.ts +1013 -0
  49. package/src/commands/setup.ts +499 -0
  50. package/src/core/auto-props.ts +1 -1
  51. package/src/core/bundle-measurer.ts +2 -2
  52. package/src/core/config.ts +16 -4
  53. package/src/core/discovery.ts +2 -2
  54. package/src/core/generators/context.ts +1 -1
  55. package/src/core/generators/registry.ts +3 -3
  56. package/src/core/generators/typescript-extractor.ts +11 -1
  57. package/src/core/graph-extractor.ts +1 -1
  58. package/src/core/index.ts +3 -190
  59. package/src/core/loader.ts +2 -2
  60. package/src/core/parser.ts +1 -1
  61. package/src/core/previewLoader.ts +1 -1
  62. package/src/index.ts +2 -2
  63. package/src/migrate/converter.ts +9 -1
  64. package/src/migrate/parser.ts +2 -0
  65. package/src/migrate/types.ts +2 -0
  66. package/src/service/snippet-validation.test.ts +1 -1
  67. package/src/service/snippet-validation.ts +2 -2
  68. package/src/setup.ts +69 -24
  69. package/src/viewer/__tests__/viewer-integration.test.ts +4 -10
  70. package/src/viewer/components/AccessibilityPanel.tsx +305 -312
  71. package/src/viewer/components/ActionsPanel.tsx +31 -29
  72. package/src/viewer/components/AllVariantsPreview.tsx +78 -0
  73. package/src/viewer/components/App.tsx +187 -740
  74. package/src/viewer/components/BottomPanel.tsx +228 -132
  75. package/src/viewer/components/CodePanel.tsx +1 -1
  76. package/src/viewer/components/CommandPalette.tsx +7 -10
  77. package/src/viewer/components/ComponentDocView.tsx +164 -0
  78. package/src/viewer/components/ComponentGraph.tsx +111 -142
  79. package/src/viewer/components/ContractPanel.tsx +6 -6
  80. package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
  81. package/src/viewer/components/FigmaEmbed.tsx +20 -18
  82. package/src/viewer/components/FragmentEditor.tsx +92 -115
  83. package/src/viewer/components/HeaderSearch.tsx +24 -0
  84. package/src/viewer/components/HealthDashboard.tsx +16 -2
  85. package/src/viewer/components/Icons.tsx +9 -0
  86. package/src/viewer/components/InteractionsPanel.tsx +101 -117
  87. package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
  88. package/src/viewer/components/LandingPage.tsx +3 -3
  89. package/src/viewer/components/LeftSidebar.tsx +141 -63
  90. package/src/viewer/components/LoadErrorMessage.tsx +102 -0
  91. package/src/viewer/components/MultiViewportPreview.tsx +61 -142
  92. package/src/viewer/components/NoVariantsMessage.tsx +59 -0
  93. package/src/viewer/components/PanelShell.tsx +161 -0
  94. package/src/viewer/components/PerformancePanel.tsx +31 -28
  95. package/src/viewer/components/PreviewArea.tsx +1 -1
  96. package/src/viewer/components/PreviewAside.tsx +168 -0
  97. package/src/viewer/components/PreviewFrameHost.tsx +3 -3
  98. package/src/viewer/components/PropsEditor.tsx +70 -156
  99. package/src/viewer/components/ResizablePanel.tsx +103 -263
  100. package/src/viewer/components/RightSidebar.tsx +3 -9
  101. package/src/viewer/components/SkeletonLoader.tsx +13 -13
  102. package/src/viewer/components/TokenStylePanel.tsx +182 -209
  103. package/src/viewer/components/TopToolbar.tsx +159 -0
  104. package/src/viewer/components/VariantMatrix.tsx +42 -86
  105. package/src/viewer/components/VariantTabs.tsx +3 -3
  106. package/src/viewer/components/ViewerHeader.tsx +69 -0
  107. package/src/viewer/components/WebMCPDevTools.tsx +17 -23
  108. package/src/viewer/components/viewer-utils.ts +16 -0
  109. package/src/viewer/entry.tsx +5 -0
  110. package/src/viewer/hooks/useAppState.ts +27 -4
  111. package/src/viewer/hooks/usePreviewBridge.ts +2 -2
  112. package/src/viewer/preview-frame.html +6 -12
  113. package/src/viewer/server.ts +169 -2
  114. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
  115. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
  116. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
  117. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
  118. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
  119. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
  120. package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
  121. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
  122. package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
  123. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +114 -0
  124. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
  125. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
  126. package/src/viewer/vendor/shared/src/index.ts +8 -0
  127. package/src/viewer/vendor/shared/src/types.ts +12 -0
  128. package/src/viewer/vite-plugin.ts +109 -4
  129. package/dist/chunk-2JIKCJX3.js.map +0 -1
  130. package/dist/chunk-AWYCDRPG.js.map +0 -1
  131. package/dist/chunk-CJEGT3WD.js.map +0 -1
  132. package/dist/chunk-EKLMXTWU.js +0 -80
  133. package/dist/chunk-EKLMXTWU.js.map +0 -1
  134. package/dist/chunk-GOVI6COW.js +0 -195
  135. package/dist/chunk-GOVI6COW.js.map +0 -1
  136. package/dist/chunk-NGIMCIK2.js.map +0 -1
  137. package/dist/defineFragment-D0UTve-I.d.ts +0 -665
  138. package/dist/generate-35OIMW4Y.js +0 -252
  139. package/dist/generate-35OIMW4Y.js.map +0 -1
  140. package/dist/init-KSAAS7X3.js.map +0 -1
  141. package/dist/scan-65RH3QMM.js +0 -15
  142. package/dist/viewer-SBTJDMP7.js.map +0 -1
  143. package/src/core/__tests__/preview-runtime.test.tsx +0 -111
  144. package/src/core/composition.test.ts +0 -262
  145. package/src/core/composition.ts +0 -318
  146. package/src/core/constants.ts +0 -114
  147. package/src/core/context.ts +0 -2
  148. package/src/core/defineFragment.ts +0 -141
  149. package/src/core/figma.ts +0 -263
  150. package/src/core/fragment-types.ts +0 -214
  151. package/src/core/performance-presets.ts +0 -142
  152. package/src/core/preview-runtime.tsx +0 -144
  153. package/src/core/schema.ts +0 -221
  154. package/src/core/storyAdapter.test.ts +0 -571
  155. package/src/core/storyAdapter.ts +0 -761
  156. package/src/core/storybook-csf.ts +0 -11
  157. package/src/core/token-parser.ts +0 -321
  158. package/src/core/token-types.ts +0 -287
  159. package/src/core/types.ts +0 -762
  160. /package/dist/{chunk-WI6SLMSO.js.map → chunk-CAMXG5HJ.js.map} +0 -0
  161. /package/dist/{discovery-Z4RDDFVR.js.map → chunk-D2CDBRNU.js.map} +0 -0
  162. /package/dist/{chunk-YMPGYEWK.js.map → chunk-D5PYOXEI.js.map} +0 -0
  163. /package/dist/{chunk-TOIE7VXF.js.map → chunk-PW7QTQA6.js.map} +0 -0
  164. /package/dist/{scan-65RH3QMM.js.map → discovery-NEOY4MPN.js.map} +0 -0
  165. /package/dist/{service-A5GIGGGK.js.map → scan-OQU7M4GH.js.map} +0 -0
  166. /package/dist/{static-viewer-NSODM5VX.js.map → service-TQYWY65E.js.map} +0 -0
@@ -1,571 +0,0 @@
1
- /**
2
- * Unit tests for storyModuleToFragment and related utilities.
3
- * Tests both CSF2 (Template.bind) and CSF3 (object stories) patterns.
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from "vitest";
7
- import { createElement, type ComponentType } from "react";
8
- import {
9
- storyModuleToFragment,
10
- setPreviewConfig,
11
- toId,
12
- storyNameFromExport,
13
- isExportStory,
14
- type StoryModule,
15
- type StoryMeta,
16
- type Story,
17
- type CSF2Story,
18
- type PreviewConfig,
19
- } from "./storyAdapter.js";
20
-
21
- // Mock component for testing
22
- const MockComponent = (({ label = "Test" }: { label?: string }) =>
23
- createElement("button", null, label)) as ComponentType<unknown>;
24
- (MockComponent as unknown as { displayName: string }).displayName = "MockComponent";
25
-
26
- describe("storyModuleToFragment", () => {
27
- beforeEach(() => {
28
- // Reset preview config before each test
29
- setPreviewConfig({});
30
- });
31
-
32
- describe("basic conversion", () => {
33
- it("should convert a basic story module to a fragment definition", () => {
34
- const storyModule: StoryModule = {
35
- default: {
36
- title: "Components/Button",
37
- component: MockComponent,
38
- },
39
- Primary: {
40
- args: { label: "Primary" },
41
- } as Story,
42
- };
43
-
44
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
45
-
46
- expect(fragment).not.toBeNull();
47
- expect(fragment!.meta.name).toBe("Button");
48
- expect(fragment!.meta.category).toBe("general");
49
- expect(fragment!.component).toBe(MockComponent);
50
- expect(fragment!.variants).toHaveLength(1);
51
- expect(fragment!.variants[0].name).toBe("Primary");
52
- });
53
-
54
- it("should return null for stories without a component", () => {
55
- const storyModule: StoryModule = {
56
- default: {
57
- title: "Docs/Introduction",
58
- },
59
- };
60
-
61
- const fragment = storyModuleToFragment(storyModule, "Intro.stories.tsx");
62
- expect(fragment).toBeNull();
63
- });
64
-
65
- it("should extract component name from title path", () => {
66
- const storyModule: StoryModule = {
67
- default: {
68
- title: "Design System/Forms/TextField",
69
- component: MockComponent,
70
- },
71
- Default: { args: {} } as Story,
72
- };
73
-
74
- const fragment = storyModuleToFragment(storyModule, "TextField.stories.tsx");
75
-
76
- expect(fragment!.meta.name).toBe("TextField");
77
- expect(fragment!.meta.category).toBe("forms");
78
- });
79
-
80
- it("should use component displayName when title is not provided", () => {
81
- const storyModule: StoryModule = {
82
- default: {
83
- component: MockComponent,
84
- },
85
- Default: { args: {} } as Story,
86
- };
87
-
88
- const fragment = storyModuleToFragment(storyModule, "path/to/Component.stories.tsx");
89
-
90
- expect(fragment!.meta.name).toBe("MockComponent");
91
- });
92
- });
93
-
94
- describe("CSF2 Template.bind() pattern", () => {
95
- it("should handle CSF2 story functions", () => {
96
- // Simulate Template.bind({}) pattern
97
- const Template: CSF2Story = (args) =>
98
- createElement(MockComponent, args);
99
-
100
- const Primary: CSF2Story = Template.bind({});
101
- Primary.args = { label: "Primary Button" };
102
-
103
- const Secondary: CSF2Story = Template.bind({});
104
- Secondary.args = { label: "Secondary Button" };
105
-
106
- const storyModule: StoryModule = {
107
- default: {
108
- title: "Components/Button",
109
- component: MockComponent,
110
- },
111
- Primary,
112
- Secondary,
113
- };
114
-
115
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
116
-
117
- expect(fragment!.variants).toHaveLength(2);
118
- expect(fragment!.variants[0].name).toBe("Primary");
119
- expect(fragment!.variants[1].name).toBe("Secondary");
120
- });
121
-
122
- it("should use storyName when provided on CSF2 story", () => {
123
- const Template: CSF2Story = (args) =>
124
- createElement(MockComponent, args);
125
-
126
- const MyStory: CSF2Story = Template.bind({});
127
- MyStory.args = { label: "Test" };
128
- MyStory.storyName = "Custom Story Name";
129
-
130
- const storyModule: StoryModule = {
131
- default: {
132
- title: "Components/Button",
133
- component: MockComponent,
134
- },
135
- MyStory,
136
- };
137
-
138
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
139
-
140
- expect(fragment!.variants[0].name).toBe("Custom Story Name");
141
- });
142
- });
143
-
144
- describe("CSF3 object stories", () => {
145
- it("should handle CSF3 story objects with args", () => {
146
- const storyModule: StoryModule = {
147
- default: {
148
- title: "Components/Button",
149
- component: MockComponent,
150
- },
151
- Primary: {
152
- args: { label: "Primary" },
153
- } as Story,
154
- WithCustomRender: {
155
- args: { label: "Custom" },
156
- render: (args) => createElement("div", null, createElement(MockComponent, args)),
157
- } as Story,
158
- };
159
-
160
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
161
-
162
- expect(fragment!.variants).toHaveLength(2);
163
- expect(fragment!.variants[0].name).toBe("Primary");
164
- expect(fragment!.variants[1].name).toBe("With Custom Render");
165
- });
166
-
167
- it("should use story name property when provided", () => {
168
- const storyModule: StoryModule = {
169
- default: {
170
- title: "Components/Button",
171
- component: MockComponent,
172
- },
173
- MyStory: {
174
- name: "Explicit Name",
175
- args: { label: "Test" },
176
- } as Story,
177
- };
178
-
179
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
180
-
181
- expect(fragment!.variants[0].name).toBe("Explicit Name");
182
- });
183
-
184
- it("should detect play functions", () => {
185
- const storyModule: StoryModule = {
186
- default: {
187
- title: "Components/Button",
188
- component: MockComponent,
189
- },
190
- WithInteraction: {
191
- args: { label: "Click me" },
192
- play: async () => {
193
- // Interaction test
194
- },
195
- } as Story,
196
- NoInteraction: {
197
- args: { label: "No interaction" },
198
- } as Story,
199
- };
200
-
201
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
202
-
203
- expect(fragment!.variants[0].hasPlayFunction).toBe(true);
204
- expect(fragment!.variants[1].hasPlayFunction).toBeUndefined();
205
- });
206
- });
207
-
208
- describe("argTypes mapping", () => {
209
- it("should convert basic argTypes to prop definitions", () => {
210
- const storyModule: StoryModule = {
211
- default: {
212
- title: "Components/Button",
213
- component: MockComponent,
214
- argTypes: {
215
- label: {
216
- control: "text",
217
- description: "Button label text",
218
- },
219
- disabled: {
220
- control: "boolean",
221
- description: "Disable the button",
222
- },
223
- size: {
224
- control: "select",
225
- options: ["small", "medium", "large"],
226
- description: "Button size",
227
- },
228
- },
229
- },
230
- Default: { args: {} } as Story,
231
- };
232
-
233
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
234
-
235
- expect(fragment!.props.label.type).toBe("string");
236
- expect(fragment!.props.label.description).toBe("Button label text");
237
- expect(fragment!.props.disabled.type).toBe("boolean");
238
- expect(fragment!.props.size.type).toBe("enum");
239
- expect(fragment!.props.size.values).toEqual(["small", "medium", "large"]);
240
- });
241
-
242
- it("should handle all control types", () => {
243
- const storyModule: StoryModule = {
244
- default: {
245
- title: "Components/Test",
246
- component: MockComponent,
247
- argTypes: {
248
- text: { control: "text" },
249
- number: { control: "number" },
250
- range: { control: { type: "range", min: 0, max: 100 } },
251
- boolean: { control: "boolean" },
252
- color: { control: "color" },
253
- date: { control: "date" },
254
- object: { control: "object" },
255
- radio: { control: "radio", options: ["a", "b"] },
256
- inlineRadio: { control: "inline-radio", options: ["x", "y"] },
257
- select: { control: "select", options: ["1", "2"] },
258
- multiSelect: { control: "multi-select", options: ["m", "n"] },
259
- },
260
- },
261
- Default: { args: {} } as Story,
262
- };
263
-
264
- const fragment = storyModuleToFragment(storyModule, "Test.stories.tsx");
265
-
266
- expect(fragment!.props.text.type).toBe("string");
267
- expect(fragment!.props.number.type).toBe("number");
268
- expect(fragment!.props.range.type).toBe("number");
269
- expect(fragment!.props.range.controlType).toBe("range");
270
- expect(fragment!.props.range.controlOptions).toEqual({ min: 0, max: 100 });
271
- expect(fragment!.props.boolean.type).toBe("boolean");
272
- expect(fragment!.props.color.type).toBe("string");
273
- expect(fragment!.props.color.controlType).toBe("color");
274
- expect(fragment!.props.date.type).toBe("string");
275
- expect(fragment!.props.date.controlType).toBe("date");
276
- expect(fragment!.props.object.type).toBe("object");
277
- expect(fragment!.props.radio.type).toBe("enum");
278
- expect(fragment!.props.inlineRadio.type).toBe("enum");
279
- expect(fragment!.props.select.type).toBe("enum");
280
- expect(fragment!.props.multiSelect.type).toBe("enum");
281
- });
282
-
283
- it("should handle action argTypes as functions", () => {
284
- const storyModule: StoryModule = {
285
- default: {
286
- title: "Components/Button",
287
- component: MockComponent,
288
- argTypes: {
289
- onClick: {
290
- action: "clicked",
291
- },
292
- },
293
- },
294
- Default: { args: {} } as Story,
295
- };
296
-
297
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
298
-
299
- expect(fragment!.props.onClick.type).toBe("function");
300
- });
301
-
302
- it("should skip disabled argTypes", () => {
303
- const storyModule: StoryModule = {
304
- default: {
305
- title: "Components/Button",
306
- component: MockComponent,
307
- argTypes: {
308
- visible: { control: "boolean" },
309
- hidden: { control: "boolean", table: { disable: true } },
310
- },
311
- },
312
- Default: { args: {} } as Story,
313
- };
314
-
315
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
316
-
317
- expect(fragment!.props.visible).toBeDefined();
318
- expect(fragment!.props.hidden).toBeUndefined();
319
- });
320
- });
321
-
322
- describe("decorators", () => {
323
- it("should apply story decorators", () => {
324
- let decoratorCalled = false;
325
-
326
- const storyModule: StoryModule = {
327
- default: {
328
- title: "Components/Button",
329
- component: MockComponent,
330
- },
331
- WithDecorator: {
332
- args: { label: "Test" },
333
- decorators: [
334
- (Story) => {
335
- decoratorCalled = true;
336
- return createElement("div", { className: "wrapper" }, Story());
337
- },
338
- ],
339
- } as Story,
340
- };
341
-
342
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
343
- fragment!.variants[0].render();
344
-
345
- expect(decoratorCalled).toBe(true);
346
- });
347
-
348
- it("should apply meta decorators to all stories", () => {
349
- let metaDecoratorCount = 0;
350
-
351
- const storyModule: StoryModule = {
352
- default: {
353
- title: "Components/Button",
354
- component: MockComponent,
355
- decorators: [
356
- (Story) => {
357
- metaDecoratorCount++;
358
- return Story();
359
- },
360
- ],
361
- },
362
- Story1: { args: { label: "1" } } as Story,
363
- Story2: { args: { label: "2" } } as Story,
364
- };
365
-
366
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
367
- fragment!.variants[0].render();
368
- fragment!.variants[1].render();
369
-
370
- expect(metaDecoratorCount).toBe(2);
371
- });
372
-
373
- it("should apply global decorators from preview config", () => {
374
- let globalDecoratorCalled = false;
375
-
376
- setPreviewConfig({
377
- decorators: [
378
- (Story) => {
379
- globalDecoratorCalled = true;
380
- return Story();
381
- },
382
- ],
383
- });
384
-
385
- const storyModule: StoryModule = {
386
- default: {
387
- title: "Components/Button",
388
- component: MockComponent,
389
- },
390
- Default: { args: { label: "Test" } } as Story,
391
- };
392
-
393
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
394
- fragment!.variants[0].render();
395
-
396
- expect(globalDecoratorCalled).toBe(true);
397
- });
398
- });
399
-
400
- describe("includeStories/excludeStories filtering", () => {
401
- it("should filter stories using includeStories array", () => {
402
- const storyModule: StoryModule = {
403
- default: {
404
- title: "Components/Button",
405
- component: MockComponent,
406
- includeStories: ["Primary", "Secondary"],
407
- },
408
- Primary: { args: { label: "Primary" } } as Story,
409
- Secondary: { args: { label: "Secondary" } } as Story,
410
- Excluded: { args: { label: "Excluded" } } as Story,
411
- };
412
-
413
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
414
-
415
- expect(fragment!.variants).toHaveLength(2);
416
- expect(fragment!.variants.map((v) => v.name)).toContain("Primary");
417
- expect(fragment!.variants.map((v) => v.name)).toContain("Secondary");
418
- expect(fragment!.variants.map((v) => v.name)).not.toContain("Excluded");
419
- });
420
-
421
- it("should filter stories using excludeStories array", () => {
422
- const storyModule: StoryModule = {
423
- default: {
424
- title: "Components/Button",
425
- component: MockComponent,
426
- excludeStories: ["Internal"],
427
- },
428
- Primary: { args: { label: "Primary" } } as Story,
429
- Internal: { args: { label: "Internal" } } as Story,
430
- };
431
-
432
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
433
-
434
- expect(fragment!.variants).toHaveLength(1);
435
- expect(fragment!.variants[0].name).toBe("Primary");
436
- });
437
-
438
- it("should filter stories using excludeStories regex", () => {
439
- const storyModule: StoryModule = {
440
- default: {
441
- title: "Components/Button",
442
- component: MockComponent,
443
- excludeStories: /^_/,
444
- },
445
- Primary: { args: { label: "Primary" } } as Story,
446
- _Private: { args: { label: "Private" } } as Story,
447
- _Internal: { args: { label: "Internal" } } as Story,
448
- };
449
-
450
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
451
-
452
- expect(fragment!.variants).toHaveLength(1);
453
- expect(fragment!.variants[0].name).toBe("Primary");
454
- });
455
- });
456
-
457
- describe("loaders", () => {
458
- it("should collect loaders from story", () => {
459
- const storyModule: StoryModule = {
460
- default: {
461
- title: "Components/Button",
462
- component: MockComponent,
463
- },
464
- WithLoader: {
465
- args: { label: "Test" },
466
- loaders: [async () => ({ data: "loaded" })],
467
- } as Story,
468
- };
469
-
470
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
471
-
472
- expect(fragment!.variants[0].loaders).toBeDefined();
473
- expect(fragment!.variants[0].loaders).toHaveLength(1);
474
- });
475
-
476
- it("should collect loaders from meta and story", () => {
477
- const storyModule: StoryModule = {
478
- default: {
479
- title: "Components/Button",
480
- component: MockComponent,
481
- loaders: [async () => ({ metaData: "meta" })],
482
- },
483
- WithLoader: {
484
- args: { label: "Test" },
485
- loaders: [async () => ({ storyData: "story" })],
486
- } as Story,
487
- };
488
-
489
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
490
-
491
- // Should have both meta and story loaders
492
- expect(fragment!.variants[0].loaders).toHaveLength(2);
493
- });
494
- });
495
-
496
- describe("story ID generation", () => {
497
- it("should generate correct story IDs using toId", () => {
498
- const storyModule: StoryModule = {
499
- default: {
500
- title: "Components/Forms/Button",
501
- component: MockComponent,
502
- },
503
- Primary: { args: { label: "Primary" } } as Story,
504
- WithIcon: { args: { label: "With Icon" } } as Story,
505
- };
506
-
507
- const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
508
-
509
- // toId lowercases the export name without adding hyphens
510
- expect(fragment!.variants[0].storyId).toBe("components-forms-button--primary");
511
- expect(fragment!.variants[1].storyId).toBe("components-forms-button--withicon");
512
- });
513
- });
514
- });
515
-
516
- describe("@storybook/csf utilities", () => {
517
- describe("toId", () => {
518
- it("should generate kebab-case story IDs", () => {
519
- // toId converts title to kebab-case and lowercases export name
520
- expect(toId("Components/Button", "Primary")).toBe("components-button--primary");
521
- expect(toId("Design System/Forms/Input", "WithLabel")).toBe(
522
- "design-system-forms-input--withlabel"
523
- );
524
- });
525
- });
526
-
527
- describe("storyNameFromExport", () => {
528
- it("should convert export names to display names", () => {
529
- expect(storyNameFromExport("Primary")).toBe("Primary");
530
- expect(storyNameFromExport("WithIcon")).toBe("With Icon");
531
- expect(storyNameFromExport("PrimaryButton")).toBe("Primary Button");
532
- });
533
- });
534
-
535
- describe("isExportStory", () => {
536
- it("should identify valid story exports", () => {
537
- const meta: StoryMeta = {
538
- title: "Test",
539
- component: MockComponent,
540
- };
541
-
542
- // All named exports are considered potential stories by isExportStory
543
- // The actual filtering of non-story exports (like "default") happens
544
- // in our extractVariants function where we check for story-like objects
545
- expect(isExportStory("Primary", meta)).toBe(true);
546
- expect(isExportStory("Secondary", meta)).toBe(true);
547
- });
548
-
549
- it("should respect includeStories", () => {
550
- const meta: StoryMeta = {
551
- title: "Test",
552
- component: MockComponent,
553
- includeStories: ["Primary"],
554
- };
555
-
556
- expect(isExportStory("Primary", meta)).toBe(true);
557
- expect(isExportStory("Secondary", meta)).toBe(false);
558
- });
559
-
560
- it("should respect excludeStories", () => {
561
- const meta: StoryMeta = {
562
- title: "Test",
563
- component: MockComponent,
564
- excludeStories: ["Internal"],
565
- };
566
-
567
- expect(isExportStory("Primary", meta)).toBe(true);
568
- expect(isExportStory("Internal", meta)).toBe(false);
569
- });
570
- });
571
- });