@fragments-sdk/cli 0.2.2

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,571 @@
1
+ /**
2
+ * Unit tests for storyModuleToSegment 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
+ storyModuleToSegment,
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("storyModuleToSegment", () => {
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 segment 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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
45
+
46
+ expect(segment).not.toBeNull();
47
+ expect(segment!.meta.name).toBe("Button");
48
+ expect(segment!.meta.category).toBe("general");
49
+ expect(segment!.component).toBe(MockComponent);
50
+ expect(segment!.variants).toHaveLength(1);
51
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Intro.stories.tsx");
62
+ expect(segment).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 segment = storyModuleToSegment(storyModule, "TextField.stories.tsx");
75
+
76
+ expect(segment!.meta.name).toBe("TextField");
77
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "path/to/Component.stories.tsx");
89
+
90
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
116
+
117
+ expect(segment!.variants).toHaveLength(2);
118
+ expect(segment!.variants[0].name).toBe("Primary");
119
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
139
+
140
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
161
+
162
+ expect(segment!.variants).toHaveLength(2);
163
+ expect(segment!.variants[0].name).toBe("Primary");
164
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
180
+
181
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
202
+
203
+ expect(segment!.variants[0].hasPlayFunction).toBe(true);
204
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
234
+
235
+ expect(segment!.props.label.type).toBe("string");
236
+ expect(segment!.props.label.description).toBe("Button label text");
237
+ expect(segment!.props.disabled.type).toBe("boolean");
238
+ expect(segment!.props.size.type).toBe("enum");
239
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Test.stories.tsx");
265
+
266
+ expect(segment!.props.text.type).toBe("string");
267
+ expect(segment!.props.number.type).toBe("number");
268
+ expect(segment!.props.range.type).toBe("number");
269
+ expect(segment!.props.range.controlType).toBe("range");
270
+ expect(segment!.props.range.controlOptions).toEqual({ min: 0, max: 100 });
271
+ expect(segment!.props.boolean.type).toBe("boolean");
272
+ expect(segment!.props.color.type).toBe("string");
273
+ expect(segment!.props.color.controlType).toBe("color");
274
+ expect(segment!.props.date.type).toBe("string");
275
+ expect(segment!.props.date.controlType).toBe("date");
276
+ expect(segment!.props.object.type).toBe("object");
277
+ expect(segment!.props.radio.type).toBe("enum");
278
+ expect(segment!.props.inlineRadio.type).toBe("enum");
279
+ expect(segment!.props.select.type).toBe("enum");
280
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
298
+
299
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
316
+
317
+ expect(segment!.props.visible).toBeDefined();
318
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
343
+ segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
367
+ segment!.variants[0].render();
368
+ segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
394
+ segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
414
+
415
+ expect(segment!.variants).toHaveLength(2);
416
+ expect(segment!.variants.map((v) => v.name)).toContain("Primary");
417
+ expect(segment!.variants.map((v) => v.name)).toContain("Secondary");
418
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
433
+
434
+ expect(segment!.variants).toHaveLength(1);
435
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
451
+
452
+ expect(segment!.variants).toHaveLength(1);
453
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
471
+
472
+ expect(segment!.variants[0].loaders).toBeDefined();
473
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
490
+
491
+ // Should have both meta and story loaders
492
+ expect(segment!.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 segment = storyModuleToSegment(storyModule, "Button.stories.tsx");
508
+
509
+ // toId lowercases the export name without adding hyphens
510
+ expect(segment!.variants[0].storyId).toBe("components-forms-button--primary");
511
+ expect(segment!.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
+ });