@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,1067 @@
1
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
+ import {
3
+ discoverSegmentFiles,
4
+ parseSegmentFile
5
+ } from "./chunk-OAENNG3G.js";
6
+ import "./chunk-XHNKNI6J.js";
7
+
8
+ // src/test/index.ts
9
+ import { resolve as resolve2, join as join2 } from "path";
10
+ import { mkdir as mkdir3 } from "fs/promises";
11
+ import pc3 from "picocolors";
12
+
13
+ // src/test/discovery.ts
14
+ import { readFile } from "fs/promises";
15
+ async function discoverTests(config, configDir, options = {}) {
16
+ const files = await discoverSegmentFiles(config, configDir);
17
+ const variants = [];
18
+ for (const file of files) {
19
+ try {
20
+ const content = await readFile(file.absolutePath, "utf-8");
21
+ const parsed = parseSegmentFile(content, file.relativePath);
22
+ if (!parsed.meta.name) continue;
23
+ if (options.component) {
24
+ const componentMatch = parsed.meta.name.toLowerCase().includes(options.component.toLowerCase());
25
+ if (!componentMatch) continue;
26
+ }
27
+ const componentTags = parsed.meta.tags || [];
28
+ for (const variant of parsed.variants) {
29
+ const hasPlay = hasPlayFunctionInSource(content, variant.name);
30
+ if (!hasPlay) continue;
31
+ if (options.tags && options.tags.length > 0) {
32
+ const hasMatchingTag = options.tags.some(
33
+ (tag) => componentTags.some((ct) => ct.toLowerCase().includes(tag.toLowerCase()))
34
+ );
35
+ if (!hasMatchingTag) continue;
36
+ }
37
+ if (options.grep) {
38
+ const pattern = new RegExp(options.grep, "i");
39
+ const matchesName = pattern.test(variant.name);
40
+ const matchesComponent = pattern.test(parsed.meta.name);
41
+ if (!matchesName && !matchesComponent) continue;
42
+ }
43
+ if (options.exclude) {
44
+ const pattern = new RegExp(options.exclude, "i");
45
+ const excludeName = pattern.test(variant.name);
46
+ const excludeComponent = pattern.test(parsed.meta.name);
47
+ if (excludeName || excludeComponent) continue;
48
+ }
49
+ variants.push({
50
+ component: parsed.meta.name,
51
+ variant: variant.name,
52
+ tags: componentTags,
53
+ hasPlayFunction: true,
54
+ storyId: void 0,
55
+ // Not available from static parsing
56
+ sourceFile: file.relativePath
57
+ });
58
+ }
59
+ } catch {
60
+ continue;
61
+ }
62
+ }
63
+ return variants.map((v) => ({
64
+ id: `${v.component}--${v.variant}`.replace(/\s+/g, "-").toLowerCase(),
65
+ component: v.component,
66
+ variant: v.variant,
67
+ tags: v.tags,
68
+ play: null,
69
+ // Play function loaded at runtime in browser
70
+ sourceFile: v.sourceFile,
71
+ storyId: v.storyId
72
+ }));
73
+ }
74
+ function hasPlayFunctionInSource(content, variantName) {
75
+ const patterns = [
76
+ // Export const VariantName = { ... play: ... }
77
+ new RegExp(`export\\s+const\\s+${escapeRegExp(variantName)}\\s*=\\s*\\{[^}]*\\bplay\\s*:`, "s"),
78
+ // variants: [{ name: 'VariantName', ... play: ... }]
79
+ new RegExp(`name\\s*:\\s*['"\`]${escapeRegExp(variantName)}['"\`][^}]*\\bplay\\s*:`, "s"),
80
+ // In case the variant has play in its definition object
81
+ new RegExp(`['"\`]${escapeRegExp(variantName)}['"\`]\\s*:\\s*\\{[^}]*\\bplay\\s*:`, "s")
82
+ ];
83
+ return patterns.some((pattern) => pattern.test(content));
84
+ }
85
+ function escapeRegExp(string) {
86
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
87
+ }
88
+ function groupTestsByComponent(testCases) {
89
+ const groups = /* @__PURE__ */ new Map();
90
+ for (const test of testCases) {
91
+ const existing = groups.get(test.component) || [];
92
+ existing.push(test);
93
+ groups.set(test.component, existing);
94
+ }
95
+ return groups;
96
+ }
97
+ function getUniqueTags(testCases) {
98
+ const tags = /* @__PURE__ */ new Set();
99
+ for (const test of testCases) {
100
+ for (const tag of test.tags) {
101
+ tags.add(tag);
102
+ }
103
+ }
104
+ return Array.from(tags).sort();
105
+ }
106
+ function getUniqueComponents(testCases) {
107
+ const components = /* @__PURE__ */ new Set();
108
+ for (const test of testCases) {
109
+ components.add(test.component);
110
+ }
111
+ return Array.from(components).sort();
112
+ }
113
+
114
+ // src/test/runner.ts
115
+ async function runTests(testCases, options, reporters = []) {
116
+ const startTime = /* @__PURE__ */ new Date();
117
+ const results = [];
118
+ for (const reporter of reporters) {
119
+ reporter.onRunStart?.(testCases.length);
120
+ }
121
+ let playwright;
122
+ try {
123
+ playwright = await import("playwright");
124
+ } catch {
125
+ throw new Error(
126
+ "Playwright is required for running tests. Install it with: npm install -D playwright"
127
+ );
128
+ }
129
+ const browserType = playwright[options.browser];
130
+ const browser = await browserType.launch({
131
+ headless: options.headless
132
+ });
133
+ const pool = await createBrowserPool(browser, options.parallel);
134
+ try {
135
+ const serverUrl = options.serverUrl || `http://localhost:${options.port}`;
136
+ if (options.parallel > 1) {
137
+ await runTestsParallel(
138
+ testCases,
139
+ pool,
140
+ serverUrl,
141
+ options,
142
+ reporters,
143
+ results
144
+ );
145
+ } else {
146
+ await runTestsSequential(
147
+ testCases,
148
+ pool.contexts[0],
149
+ serverUrl,
150
+ options,
151
+ reporters,
152
+ results
153
+ );
154
+ }
155
+ } finally {
156
+ await cleanupBrowserPool(pool);
157
+ }
158
+ const endTime = /* @__PURE__ */ new Date();
159
+ const suites = aggregateResults(results);
160
+ const runResult = {
161
+ suites,
162
+ totalTests: results.length,
163
+ totalPassed: results.filter((r) => r.status === "passed").length,
164
+ totalFailed: results.filter((r) => r.status === "failed").length,
165
+ totalSkipped: results.filter((r) => r.status === "skipped").length,
166
+ totalDuration: endTime.getTime() - startTime.getTime(),
167
+ startTime,
168
+ endTime,
169
+ totalA11yViolations: options.a11y ? results.reduce((sum, r) => sum + (r.accessibility?.violations.length || 0), 0) : void 0
170
+ };
171
+ for (const reporter of reporters) {
172
+ await reporter.onRunComplete(runResult);
173
+ }
174
+ return runResult;
175
+ }
176
+ async function createBrowserPool(browser, parallelism) {
177
+ const contexts = [];
178
+ for (let i = 0; i < parallelism; i++) {
179
+ const context = await browser.newContext({
180
+ viewport: { width: 1280, height: 720 }
181
+ });
182
+ const page = await context.newPage();
183
+ contexts.push({ id: i, context, page, busy: false });
184
+ }
185
+ return { browser, contexts };
186
+ }
187
+ async function cleanupBrowserPool(pool) {
188
+ for (const entry of pool.contexts) {
189
+ await entry.page.close();
190
+ await entry.context.close();
191
+ }
192
+ await pool.browser.close();
193
+ }
194
+ async function runTestsSequential(testCases, ctx, serverUrl, options, reporters, results) {
195
+ for (const testCase of testCases) {
196
+ for (const reporter of reporters) {
197
+ reporter.onTestStart?.(testCase);
198
+ }
199
+ const result = await runSingleTest(testCase, ctx, serverUrl, options);
200
+ results.push(result);
201
+ for (const reporter of reporters) {
202
+ reporter.onTestComplete?.(result);
203
+ }
204
+ if (options.bail && result.status === "failed") {
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ async function runTestsParallel(testCases, pool, serverUrl, options, reporters, results) {
210
+ const queue = [...testCases];
211
+ const inFlight = [];
212
+ let bailTriggered = false;
213
+ const acquireContext = () => {
214
+ const available = pool.contexts.find((c) => !c.busy);
215
+ if (available) {
216
+ available.busy = true;
217
+ return available;
218
+ }
219
+ return null;
220
+ };
221
+ const releaseContext = (ctx) => {
222
+ ctx.busy = false;
223
+ };
224
+ const runNext = async () => {
225
+ if (bailTriggered || queue.length === 0) return;
226
+ const ctx = acquireContext();
227
+ if (!ctx) return;
228
+ const testCase = queue.shift();
229
+ for (const reporter of reporters) {
230
+ reporter.onTestStart?.(testCase);
231
+ }
232
+ try {
233
+ const result = await runSingleTest(testCase, ctx, serverUrl, options);
234
+ results.push(result);
235
+ for (const reporter of reporters) {
236
+ reporter.onTestComplete?.(result);
237
+ }
238
+ if (options.bail && result.status === "failed") {
239
+ bailTriggered = true;
240
+ }
241
+ } finally {
242
+ releaseContext(ctx);
243
+ if (!bailTriggered && queue.length > 0) {
244
+ const nextPromise = runNext();
245
+ inFlight.push(nextPromise);
246
+ }
247
+ }
248
+ };
249
+ const initialBatch = Math.min(pool.contexts.length, queue.length);
250
+ for (let i = 0; i < initialBatch; i++) {
251
+ inFlight.push(runNext());
252
+ }
253
+ await Promise.all(inFlight);
254
+ }
255
+ async function runSingleTest(testCase, ctx, serverUrl, options) {
256
+ const { page } = ctx;
257
+ const startTime = performance.now();
258
+ const steps = [];
259
+ let error;
260
+ let status = "passed";
261
+ let retryAttempt = 0;
262
+ const maxAttempts = options.retries + 1;
263
+ while (retryAttempt < maxAttempts) {
264
+ try {
265
+ const url = buildVariantUrl(serverUrl, testCase);
266
+ await page.goto(url, { waitUntil: "networkidle" });
267
+ await page.waitForSelector('[data-preview-container="true"]', {
268
+ timeout: options.timeout
269
+ });
270
+ const playResult = await executePlayFunction(page, testCase, options.timeout);
271
+ if (playResult.error) {
272
+ error = playResult.error;
273
+ status = "failed";
274
+ steps.push(...playResult.steps);
275
+ if (retryAttempt < maxAttempts - 1) {
276
+ retryAttempt++;
277
+ continue;
278
+ }
279
+ } else {
280
+ status = "passed";
281
+ steps.push(...playResult.steps);
282
+ }
283
+ break;
284
+ } catch (e) {
285
+ error = {
286
+ message: e instanceof Error ? e.message : String(e),
287
+ stack: e instanceof Error ? e.stack : void 0
288
+ };
289
+ status = "failed";
290
+ if (retryAttempt < maxAttempts - 1) {
291
+ retryAttempt++;
292
+ continue;
293
+ }
294
+ break;
295
+ }
296
+ }
297
+ const duration = performance.now() - startTime;
298
+ let accessibility;
299
+ if (options.a11y && status === "passed") {
300
+ accessibility = await runA11yChecks(page);
301
+ }
302
+ let screenshotPath;
303
+ if (options.visual) {
304
+ screenshotPath = await captureScreenshot(page, testCase, options.outputDir);
305
+ }
306
+ return {
307
+ testCase,
308
+ status,
309
+ duration,
310
+ steps,
311
+ error,
312
+ accessibility,
313
+ screenshotPath,
314
+ retryAttempt: retryAttempt > 0 ? retryAttempt : void 0
315
+ };
316
+ }
317
+ function buildVariantUrl(serverUrl, testCase) {
318
+ const params = new URLSearchParams({
319
+ component: testCase.component,
320
+ variant: testCase.variant,
321
+ isolated: "true"
322
+ });
323
+ return `${serverUrl}?${params.toString()}`;
324
+ }
325
+ async function executePlayFunction(page, testCase, timeout) {
326
+ const result = await page.evaluate(
327
+ async ({ component, variant, timeout: timeout2 }) => {
328
+ const registry = window.__SEGMENTS_REGISTRY__;
329
+ if (!registry) {
330
+ return {
331
+ steps: [],
332
+ error: { message: "Segments registry not found. Make sure the viewer is loaded." }
333
+ };
334
+ }
335
+ const segment = registry.get(component);
336
+ if (!segment) {
337
+ return {
338
+ steps: [],
339
+ error: { message: `Component "${component}" not found in registry` }
340
+ };
341
+ }
342
+ const variantDef = segment.variants?.find((v) => v.name === variant);
343
+ if (!variantDef || !variantDef.play) {
344
+ return {
345
+ steps: [],
346
+ error: { message: `Variant "${variant}" not found or has no play function` }
347
+ };
348
+ }
349
+ const canvasElement = document.querySelector('[data-preview-container="true"]');
350
+ if (!canvasElement) {
351
+ return {
352
+ steps: [],
353
+ error: { message: "Preview container not found" }
354
+ };
355
+ }
356
+ const steps = [];
357
+ const step = async (name, fn) => {
358
+ const stepStart = performance.now();
359
+ try {
360
+ await fn();
361
+ steps.push({
362
+ name,
363
+ status: "passed",
364
+ duration: performance.now() - stepStart
365
+ });
366
+ } catch (e) {
367
+ steps.push({
368
+ name,
369
+ status: "failed",
370
+ duration: performance.now() - stepStart,
371
+ error: { message: e instanceof Error ? e.message : String(e) }
372
+ });
373
+ throw e;
374
+ }
375
+ };
376
+ const timeoutPromise = new Promise((_, reject) => {
377
+ setTimeout(() => reject(new Error(`Test timed out after ${timeout2}ms`)), timeout2);
378
+ });
379
+ try {
380
+ await Promise.race([
381
+ variantDef.play({
382
+ canvasElement,
383
+ args: {},
384
+ step
385
+ }),
386
+ timeoutPromise
387
+ ]);
388
+ if (steps.length === 0) {
389
+ steps.push({
390
+ name: "Play function",
391
+ status: "passed",
392
+ duration: 0
393
+ });
394
+ }
395
+ return { steps, error: void 0 };
396
+ } catch (e) {
397
+ if (!steps.some((s) => s.status === "failed")) {
398
+ steps.push({
399
+ name: "Play function",
400
+ status: "failed",
401
+ duration: 0,
402
+ error: { message: e instanceof Error ? e.message : String(e) }
403
+ });
404
+ }
405
+ return {
406
+ steps,
407
+ error: { message: e instanceof Error ? e.message : String(e) }
408
+ };
409
+ }
410
+ },
411
+ { component: testCase.component, variant: testCase.variant, timeout }
412
+ );
413
+ return result;
414
+ }
415
+ async function runA11yChecks(page) {
416
+ try {
417
+ await page.evaluate(() => {
418
+ if (window.axe) return;
419
+ return new Promise((resolve3, reject) => {
420
+ const script = document.createElement("script");
421
+ script.src = "https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.8.2/axe.min.js";
422
+ script.onload = () => resolve3();
423
+ script.onerror = () => reject(new Error("Failed to load axe-core"));
424
+ document.head.appendChild(script);
425
+ });
426
+ });
427
+ const results = await page.evaluate(async () => {
428
+ const axe = window.axe;
429
+ const container = document.querySelector('[data-preview-container="true"]');
430
+ if (!container) {
431
+ return { violations: [], passes: 0 };
432
+ }
433
+ const axeResults = await axe.run(container);
434
+ return {
435
+ violations: axeResults.violations,
436
+ passes: axeResults.passes.length
437
+ };
438
+ });
439
+ return {
440
+ violations: results.violations,
441
+ passes: results.passes
442
+ };
443
+ } catch {
444
+ return { violations: [], passes: 0 };
445
+ }
446
+ }
447
+ async function captureScreenshot(page, testCase, outputDir) {
448
+ const { mkdir: mkdir4, writeFile: writeFile3 } = await import("fs/promises");
449
+ const { join: join3 } = await import("path");
450
+ const screenshotsDir = join3(outputDir, "screenshots");
451
+ await mkdir4(screenshotsDir, { recursive: true });
452
+ const filename = `${testCase.component}--${testCase.variant}.png`.replace(/\s+/g, "-").toLowerCase();
453
+ const filepath = join3(screenshotsDir, filename);
454
+ const container = await page.$('[data-preview-container="true"]');
455
+ if (container) {
456
+ const screenshot = await container.screenshot();
457
+ await writeFile3(filepath, screenshot);
458
+ }
459
+ return filepath;
460
+ }
461
+ function aggregateResults(results) {
462
+ const grouped = groupTestsByComponent(results.map((r) => r.testCase));
463
+ const suites = [];
464
+ for (const [componentName, testCases] of grouped) {
465
+ const componentResults = results.filter((r) => r.testCase.component === componentName);
466
+ suites.push({
467
+ name: componentName,
468
+ tests: componentResults,
469
+ duration: componentResults.reduce((sum, r) => sum + r.duration, 0),
470
+ passed: componentResults.filter((r) => r.status === "passed").length,
471
+ failed: componentResults.filter((r) => r.status === "failed").length,
472
+ skipped: componentResults.filter((r) => r.status === "skipped").length
473
+ });
474
+ }
475
+ return suites;
476
+ }
477
+
478
+ // src/test/reporters/console.ts
479
+ import pc from "picocolors";
480
+ function createConsoleReporter(options = {}) {
481
+ const { verbose = false, showTiming = true } = options;
482
+ let startTime;
483
+ let testCount;
484
+ let currentIndex;
485
+ return {
486
+ onRunStart(count) {
487
+ testCount = count;
488
+ currentIndex = 0;
489
+ startTime = Date.now();
490
+ console.log();
491
+ console.log(pc.cyan(pc.bold("Segments Test Runner")));
492
+ console.log(pc.dim(`Running ${count} test${count === 1 ? "" : "s"}...`));
493
+ console.log();
494
+ },
495
+ onTestStart(testCase) {
496
+ currentIndex++;
497
+ if (verbose) {
498
+ const progress = pc.dim(`[${currentIndex}/${testCount}]`);
499
+ console.log(`${progress} ${pc.dim("Running")} ${testCase.component} \u203A ${testCase.variant}`);
500
+ }
501
+ },
502
+ onTestComplete(result) {
503
+ const { testCase, status, duration, steps, error, accessibility } = result;
504
+ const timing = showTiming ? pc.dim(` (${formatDuration(duration)})`) : "";
505
+ const statusIcon = getStatusIcon(status);
506
+ const testName = `${testCase.component} \u203A ${testCase.variant}`;
507
+ if (status === "passed") {
508
+ console.log(` ${statusIcon} ${testName}${timing}`);
509
+ if (accessibility && accessibility.violations.length > 0) {
510
+ console.log(
511
+ pc.yellow(` \u26A0 ${accessibility.violations.length} accessibility violation(s)`)
512
+ );
513
+ }
514
+ } else if (status === "failed") {
515
+ console.log(` ${statusIcon} ${pc.red(testName)}${timing}`);
516
+ if (error) {
517
+ console.log(pc.red(` ${error.message}`));
518
+ if (verbose && error.stack) {
519
+ console.log(pc.dim(` ${error.stack.split("\n").slice(1, 4).join("\n ")}`));
520
+ }
521
+ }
522
+ if (verbose || steps.some((s) => s.status === "failed")) {
523
+ for (const step of steps) {
524
+ if (step.status === "failed") {
525
+ console.log(pc.red(` \u2717 ${step.name}`));
526
+ if (step.error) {
527
+ console.log(pc.red(` ${step.error.message}`));
528
+ }
529
+ } else if (verbose && step.status === "passed") {
530
+ console.log(pc.dim(` \u2713 ${step.name}`));
531
+ }
532
+ }
533
+ }
534
+ if (result.retryAttempt) {
535
+ console.log(pc.dim(` (failed after ${result.retryAttempt + 1} attempts)`));
536
+ }
537
+ } else if (status === "skipped") {
538
+ console.log(` ${statusIcon} ${pc.dim(testName)} ${pc.dim("(skipped)")}`);
539
+ }
540
+ if (verbose && status === "passed" && steps.length > 0) {
541
+ for (const step of steps) {
542
+ const stepTiming = showTiming ? pc.dim(` ${formatDuration(step.duration)}`) : "";
543
+ console.log(pc.dim(` \u2713 ${step.name}${stepTiming}`));
544
+ }
545
+ }
546
+ },
547
+ onRunComplete(result) {
548
+ const { totalTests, totalPassed, totalFailed, totalSkipped, totalDuration, suites } = result;
549
+ console.log();
550
+ console.log(pc.dim("\u2500".repeat(50)));
551
+ console.log();
552
+ if (suites.length > 1) {
553
+ console.log(pc.bold("Test Suites:"));
554
+ for (const suite of suites) {
555
+ const statusIcon = suite.failed > 0 ? pc.red("\u2717") : pc.green("\u2713");
556
+ const failedText = suite.failed > 0 ? pc.red(`${suite.failed} failed, `) : "";
557
+ const passedText = suite.passed > 0 ? pc.green(`${suite.passed} passed`) : "";
558
+ const skippedText = suite.skipped > 0 ? pc.dim(`, ${suite.skipped} skipped`) : "";
559
+ console.log(` ${statusIcon} ${suite.name} (${failedText}${passedText}${skippedText})`);
560
+ }
561
+ console.log();
562
+ }
563
+ console.log(pc.bold("Tests:"));
564
+ const parts = [];
565
+ if (totalFailed > 0) {
566
+ parts.push(pc.red(`${totalFailed} failed`));
567
+ }
568
+ if (totalPassed > 0) {
569
+ parts.push(pc.green(`${totalPassed} passed`));
570
+ }
571
+ if (totalSkipped > 0) {
572
+ parts.push(pc.dim(`${totalSkipped} skipped`));
573
+ }
574
+ parts.push(`${totalTests} total`);
575
+ console.log(` ${parts.join(", ")}`);
576
+ console.log(` ${pc.dim("Time:")} ${formatDuration(totalDuration)}`);
577
+ if (result.totalA11yViolations !== void 0) {
578
+ if (result.totalA11yViolations > 0) {
579
+ console.log(
580
+ ` ${pc.yellow("Accessibility:")} ${result.totalA11yViolations} violation(s)`
581
+ );
582
+ } else {
583
+ console.log(` ${pc.green("Accessibility:")} All checks passed`);
584
+ }
585
+ }
586
+ console.log();
587
+ if (totalFailed > 0) {
588
+ console.log(pc.red(pc.bold("Test run failed")));
589
+ } else {
590
+ console.log(pc.green(pc.bold("Test run passed")));
591
+ }
592
+ console.log();
593
+ }
594
+ };
595
+ }
596
+ function getStatusIcon(status) {
597
+ switch (status) {
598
+ case "passed":
599
+ return pc.green("\u2713");
600
+ case "failed":
601
+ return pc.red("\u2717");
602
+ case "skipped":
603
+ return pc.dim("\u25CB");
604
+ default:
605
+ return " ";
606
+ }
607
+ }
608
+ function formatDuration(ms) {
609
+ if (ms < 1e3) {
610
+ return `${Math.round(ms)}ms`;
611
+ }
612
+ if (ms < 6e4) {
613
+ return `${(ms / 1e3).toFixed(2)}s`;
614
+ }
615
+ const minutes = Math.floor(ms / 6e4);
616
+ const seconds = (ms % 6e4 / 1e3).toFixed(1);
617
+ return `${minutes}m ${seconds}s`;
618
+ }
619
+
620
+ // src/test/reporters/junit.ts
621
+ import { writeFile, mkdir } from "fs/promises";
622
+ import { dirname } from "path";
623
+ function createJUnitReporter(options) {
624
+ const { outputPath, suiteName = "Segments Tests", includeProperties = true } = options;
625
+ return {
626
+ async onRunComplete(result) {
627
+ const xml = generateJUnitXml(result, suiteName, includeProperties);
628
+ await mkdir(dirname(outputPath), { recursive: true });
629
+ await writeFile(outputPath, xml, "utf-8");
630
+ }
631
+ };
632
+ }
633
+ function generateJUnitXml(result, suiteName, includeProperties) {
634
+ const lines = [];
635
+ lines.push('<?xml version="1.0" encoding="UTF-8"?>');
636
+ lines.push(
637
+ `<testsuites name="${escapeXml(suiteName)}" tests="${result.totalTests}" failures="${result.totalFailed}" skipped="${result.totalSkipped}" time="${(result.totalDuration / 1e3).toFixed(3)}">`
638
+ );
639
+ for (const suite of result.suites) {
640
+ lines.push(generateTestSuiteXml(suite, includeProperties));
641
+ }
642
+ lines.push("</testsuites>");
643
+ return lines.join("\n");
644
+ }
645
+ function generateTestSuiteXml(suite, includeProperties) {
646
+ const lines = [];
647
+ lines.push(
648
+ ` <testsuite name="${escapeXml(suite.name)}" tests="${suite.tests.length}" failures="${suite.failed}" skipped="${suite.skipped}" time="${(suite.duration / 1e3).toFixed(3)}">`
649
+ );
650
+ if (includeProperties) {
651
+ lines.push(" <properties>");
652
+ lines.push(` <property name="component" value="${escapeXml(suite.name)}"/>`);
653
+ lines.push(" </properties>");
654
+ }
655
+ for (const test of suite.tests) {
656
+ lines.push(generateTestCaseXml(test));
657
+ }
658
+ lines.push(" </testsuite>");
659
+ return lines.join("\n");
660
+ }
661
+ function generateTestCaseXml(result) {
662
+ const { testCase, status, duration, error, steps, accessibility } = result;
663
+ const lines = [];
664
+ const testName = testCase.variant;
665
+ const className = testCase.component;
666
+ const time = (duration / 1e3).toFixed(3);
667
+ if (status === "passed" && !accessibility?.violations.length) {
668
+ lines.push(` <testcase name="${escapeXml(testName)}" classname="${escapeXml(className)}" time="${time}"/>`);
669
+ } else {
670
+ lines.push(` <testcase name="${escapeXml(testName)}" classname="${escapeXml(className)}" time="${time}">`);
671
+ if (status === "failed") {
672
+ const message = error?.message || "Test failed";
673
+ lines.push(` <failure message="${escapeXml(message)}" type="AssertionError">`);
674
+ const details = [];
675
+ const failedSteps = steps.filter((s) => s.status === "failed");
676
+ if (failedSteps.length > 0) {
677
+ details.push("Failed steps:");
678
+ for (const step of failedSteps) {
679
+ details.push(` - ${step.name}: ${step.error?.message || "Failed"}`);
680
+ }
681
+ }
682
+ if (error?.stack) {
683
+ details.push("");
684
+ details.push("Stack trace:");
685
+ details.push(error.stack);
686
+ }
687
+ if (details.length > 0) {
688
+ lines.push(escapeXml(details.join("\n")));
689
+ }
690
+ lines.push(" </failure>");
691
+ } else if (status === "skipped") {
692
+ lines.push(" <skipped/>");
693
+ }
694
+ if (accessibility?.violations.length) {
695
+ lines.push(" <system-out>");
696
+ lines.push(`Accessibility violations (${accessibility.violations.length}):`);
697
+ for (const violation of accessibility.violations) {
698
+ lines.push(` [${violation.impact}] ${violation.id}: ${violation.description}`);
699
+ lines.push(` Help: ${violation.help}`);
700
+ for (const node of violation.nodes.slice(0, 3)) {
701
+ lines.push(` Element: ${node.html.substring(0, 100)}`);
702
+ }
703
+ if (violation.nodes.length > 3) {
704
+ lines.push(` ... and ${violation.nodes.length - 3} more elements`);
705
+ }
706
+ }
707
+ lines.push(" </system-out>");
708
+ }
709
+ lines.push(" </testcase>");
710
+ }
711
+ return lines.join("\n");
712
+ }
713
+ function escapeXml(str) {
714
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "");
715
+ }
716
+
717
+ // src/test/reporters/json.ts
718
+ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
719
+ import { dirname as dirname2 } from "path";
720
+ function createJsonReporter(options) {
721
+ const { outputPath, pretty = true, includeSteps = true, includeStacks = false } = options;
722
+ return {
723
+ async onRunComplete(result) {
724
+ const report = generateJsonReport(result, includeSteps, includeStacks);
725
+ await mkdir2(dirname2(outputPath), { recursive: true });
726
+ const json = pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
727
+ await writeFile2(outputPath, json, "utf-8");
728
+ }
729
+ };
730
+ }
731
+ function generateJsonReport(result, includeSteps, includeStacks) {
732
+ const { suites, totalTests, totalPassed, totalFailed, totalSkipped, totalDuration, startTime, endTime } = result;
733
+ return {
734
+ meta: {
735
+ version: "1.0.0",
736
+ startTime: startTime.toISOString(),
737
+ endTime: endTime.toISOString(),
738
+ duration: totalDuration
739
+ },
740
+ summary: {
741
+ total: totalTests,
742
+ passed: totalPassed,
743
+ failed: totalFailed,
744
+ skipped: totalSkipped,
745
+ passRate: totalTests > 0 ? Math.round(totalPassed / totalTests * 100) : 0,
746
+ a11yViolations: result.totalA11yViolations
747
+ },
748
+ suites: suites.map((suite) => ({
749
+ name: suite.name,
750
+ tests: suite.tests.length,
751
+ passed: suite.passed,
752
+ failed: suite.failed,
753
+ skipped: suite.skipped,
754
+ duration: suite.duration,
755
+ results: suite.tests.map((test) => {
756
+ const resultObj = {
757
+ id: test.testCase.id,
758
+ component: test.testCase.component,
759
+ variant: test.testCase.variant,
760
+ status: test.status,
761
+ duration: test.duration
762
+ };
763
+ if (includeSteps && test.steps.length > 0) {
764
+ resultObj.steps = test.steps.map((step) => ({
765
+ name: step.name,
766
+ status: step.status,
767
+ duration: step.duration,
768
+ error: step.error?.message
769
+ }));
770
+ }
771
+ if (test.error) {
772
+ resultObj.error = {
773
+ message: test.error.message,
774
+ stack: includeStacks ? test.error.stack : void 0
775
+ };
776
+ }
777
+ if (test.accessibility) {
778
+ resultObj.accessibility = {
779
+ violations: test.accessibility.violations.length,
780
+ passes: test.accessibility.passes,
781
+ details: test.accessibility.violations.map((v) => ({
782
+ id: v.id,
783
+ impact: v.impact,
784
+ description: v.description,
785
+ elements: v.nodes.length
786
+ }))
787
+ };
788
+ }
789
+ if (test.retryAttempt) {
790
+ resultObj.retryAttempt = test.retryAttempt;
791
+ }
792
+ return resultObj;
793
+ })
794
+ }))
795
+ };
796
+ }
797
+
798
+ // src/test/watch.ts
799
+ import { watch } from "fs";
800
+ import { resolve, relative } from "path";
801
+ import pc2 from "picocolors";
802
+ async function startWatchMode(config, configDir, runnerOptions, reporters, options = {}) {
803
+ const { debounceMs = 300, clearConsole = true, runOnStart = true } = options;
804
+ let debounceTimer = null;
805
+ let isRunning = false;
806
+ let pendingFiles = /* @__PURE__ */ new Set();
807
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
808
+ const watchPaths = /* @__PURE__ */ new Set();
809
+ for (const file of segmentFiles) {
810
+ watchPaths.add(file.absolutePath);
811
+ }
812
+ const watchDirs = /* @__PURE__ */ new Set();
813
+ for (const path of watchPaths) {
814
+ const dir = resolve(path, "..");
815
+ watchDirs.add(dir);
816
+ }
817
+ console.log();
818
+ console.log(pc2.cyan(pc2.bold("Segments Test Runner - Watch Mode")));
819
+ console.log(pc2.dim(`Watching ${watchPaths.size} files in ${watchDirs.size} directories`));
820
+ console.log(pc2.dim("Press Ctrl+C to stop"));
821
+ console.log();
822
+ const runTestsForFiles = async (changedFiles) => {
823
+ if (isRunning) {
824
+ for (const file of changedFiles) {
825
+ pendingFiles.add(file);
826
+ }
827
+ return;
828
+ }
829
+ isRunning = true;
830
+ try {
831
+ if (clearConsole) {
832
+ console.clear();
833
+ }
834
+ console.log(pc2.cyan("Changes detected, running tests..."));
835
+ console.log(pc2.dim(`Changed: ${changedFiles.map((f) => relative(configDir, f)).join(", ")}`));
836
+ console.log();
837
+ const allTests = await discoverTests(config, configDir, {});
838
+ const changedRelativePaths = changedFiles.map((f) => relative(configDir, f));
839
+ const testsToRun = changedRelativePaths.length > 0 ? allTests.filter((t) => changedRelativePaths.some((p) => t.sourceFile.includes(p))) : allTests;
840
+ if (testsToRun.length === 0) {
841
+ console.log(pc2.yellow("No tests found in changed files"));
842
+ console.log();
843
+ return;
844
+ }
845
+ await runTests(testsToRun, runnerOptions, reporters);
846
+ } catch (error) {
847
+ console.error(pc2.red("Error running tests:"), error);
848
+ } finally {
849
+ isRunning = false;
850
+ if (pendingFiles.size > 0) {
851
+ const files = Array.from(pendingFiles);
852
+ pendingFiles.clear();
853
+ await runTestsForFiles(files);
854
+ }
855
+ }
856
+ };
857
+ if (runOnStart) {
858
+ const allTests = await discoverTests(config, configDir, {});
859
+ if (allTests.length > 0) {
860
+ await runTests(allTests, runnerOptions, reporters);
861
+ } else {
862
+ console.log(pc2.yellow("No tests with play functions found"));
863
+ }
864
+ console.log();
865
+ console.log(pc2.dim("Watching for changes..."));
866
+ console.log();
867
+ }
868
+ const watchers = [];
869
+ for (const dir of watchDirs) {
870
+ try {
871
+ const watcher = watch(dir, { recursive: false }, (eventType, filename) => {
872
+ if (!filename) return;
873
+ const fullPath = resolve(dir, filename);
874
+ if (!watchPaths.has(fullPath)) return;
875
+ if (debounceTimer) {
876
+ clearTimeout(debounceTimer);
877
+ }
878
+ pendingFiles.add(fullPath);
879
+ debounceTimer = setTimeout(() => {
880
+ const files = Array.from(pendingFiles);
881
+ pendingFiles.clear();
882
+ runTestsForFiles(files);
883
+ }, debounceMs);
884
+ });
885
+ watchers.push(watcher);
886
+ } catch (error) {
887
+ console.warn(pc2.yellow(`Warning: Could not watch directory ${dir}`), error);
888
+ }
889
+ }
890
+ const cleanup = () => {
891
+ console.log();
892
+ console.log(pc2.dim("Stopping watch mode..."));
893
+ for (const watcher of watchers) {
894
+ watcher.close();
895
+ }
896
+ if (debounceTimer) {
897
+ clearTimeout(debounceTimer);
898
+ }
899
+ process.exit(0);
900
+ };
901
+ process.on("SIGINT", cleanup);
902
+ process.on("SIGTERM", cleanup);
903
+ await new Promise(() => {
904
+ });
905
+ }
906
+
907
+ // src/test/index.ts
908
+ var DEFAULT_OPTIONS = {
909
+ parallel: 4,
910
+ timeout: 3e4,
911
+ retries: 0,
912
+ bail: false,
913
+ browser: "chromium",
914
+ headless: true,
915
+ a11y: false,
916
+ visual: false,
917
+ updateSnapshots: false,
918
+ watch: false,
919
+ reporters: "console",
920
+ output: "./test-results",
921
+ port: 6006,
922
+ ci: false
923
+ };
924
+ async function runTestCommand(config, configDir, options) {
925
+ const opts = {
926
+ ...DEFAULT_OPTIONS,
927
+ ...options
928
+ };
929
+ const discoveryOptions = {
930
+ component: opts.component,
931
+ tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : void 0,
932
+ grep: opts.grep,
933
+ exclude: opts.exclude
934
+ };
935
+ const runnerOptions = {
936
+ parallel: opts.parallel,
937
+ timeout: opts.timeout,
938
+ retries: opts.retries,
939
+ bail: opts.bail,
940
+ a11y: opts.a11y,
941
+ visual: opts.visual,
942
+ updateSnapshots: opts.updateSnapshots,
943
+ outputDir: resolve2(configDir, opts.output),
944
+ browser: opts.browser,
945
+ headless: opts.headless,
946
+ serverUrl: opts.serverUrl,
947
+ port: opts.port
948
+ };
949
+ if (opts.ci) {
950
+ runnerOptions.headless = true;
951
+ }
952
+ console.log(pc3.cyan("\nDiscovering tests..."));
953
+ const testCases = await discoverTests(config, configDir, discoveryOptions);
954
+ if (testCases.length === 0) {
955
+ console.log(pc3.yellow("No tests with play functions found"));
956
+ const components = getUniqueComponents(testCases);
957
+ const tags = getUniqueTags(testCases);
958
+ if (discoveryOptions.component) {
959
+ console.log(pc3.dim(` Filtered by component: ${discoveryOptions.component}`));
960
+ }
961
+ if (discoveryOptions.tags?.length) {
962
+ console.log(pc3.dim(` Filtered by tags: ${discoveryOptions.tags.join(", ")}`));
963
+ }
964
+ if (discoveryOptions.grep) {
965
+ console.log(pc3.dim(` Filtered by pattern: ${discoveryOptions.grep}`));
966
+ }
967
+ console.log();
968
+ console.log(pc3.dim("Add play functions to your segment variants to enable testing."));
969
+ console.log();
970
+ return opts.ci ? 0 : 0;
971
+ }
972
+ console.log(pc3.dim(`Found ${testCases.length} test(s)`));
973
+ await mkdir3(runnerOptions.outputDir, { recursive: true });
974
+ const reporters = createReporters(opts.reporters, runnerOptions.outputDir, opts.ci);
975
+ if (opts.watch && !opts.ci) {
976
+ await startWatchMode(config, configDir, runnerOptions, reporters);
977
+ return 0;
978
+ }
979
+ let result;
980
+ try {
981
+ result = await runTests(testCases, runnerOptions, reporters);
982
+ } catch (error) {
983
+ console.error(pc3.red("Error running tests:"), error);
984
+ return 1;
985
+ }
986
+ if (opts.ci && result.totalFailed > 0) {
987
+ return 1;
988
+ }
989
+ return result.totalFailed > 0 ? 1 : 0;
990
+ }
991
+ function createReporters(reporterNames, outputDir, ci) {
992
+ const reporters = [];
993
+ const names = reporterNames.split(",").map((n) => n.trim().toLowerCase());
994
+ for (const name of names) {
995
+ switch (name) {
996
+ case "console":
997
+ reporters.push(
998
+ createConsoleReporter({
999
+ verbose: !ci,
1000
+ showTiming: true
1001
+ })
1002
+ );
1003
+ break;
1004
+ case "junit":
1005
+ reporters.push(
1006
+ createJUnitReporter({
1007
+ outputPath: join2(outputDir, "junit.xml"),
1008
+ suiteName: "Segments Tests"
1009
+ })
1010
+ );
1011
+ break;
1012
+ case "json":
1013
+ reporters.push(
1014
+ createJsonReporter({
1015
+ outputPath: join2(outputDir, "results.json"),
1016
+ pretty: true,
1017
+ includeSteps: true,
1018
+ includeStacks: !ci
1019
+ })
1020
+ );
1021
+ break;
1022
+ default:
1023
+ console.warn(pc3.yellow(`Unknown reporter: ${name}`));
1024
+ }
1025
+ }
1026
+ if (!names.includes("console") && reporters.length === 0) {
1027
+ reporters.push(createConsoleReporter({ verbose: !ci }));
1028
+ }
1029
+ return reporters;
1030
+ }
1031
+ async function listTests(config, configDir, options) {
1032
+ const discoveryOptions = {
1033
+ component: options.component,
1034
+ tags: options.tags ? options.tags.split(",").map((t) => t.trim()) : void 0,
1035
+ grep: options.grep,
1036
+ exclude: options.exclude
1037
+ };
1038
+ const testCases = await discoverTests(config, configDir, discoveryOptions);
1039
+ console.log();
1040
+ console.log(pc3.cyan(pc3.bold("Available Tests")));
1041
+ console.log();
1042
+ if (testCases.length === 0) {
1043
+ console.log(pc3.yellow("No tests with play functions found"));
1044
+ return;
1045
+ }
1046
+ const byComponent = /* @__PURE__ */ new Map();
1047
+ for (const test of testCases) {
1048
+ const existing = byComponent.get(test.component) || [];
1049
+ existing.push(test);
1050
+ byComponent.set(test.component, existing);
1051
+ }
1052
+ for (const [component, tests] of byComponent) {
1053
+ console.log(pc3.bold(component));
1054
+ for (const test of tests) {
1055
+ const tags = test.tags.length > 0 ? pc3.dim(` [${test.tags.join(", ")}]`) : "";
1056
+ console.log(` ${pc3.green("\u203A")} ${test.variant}${tags}`);
1057
+ }
1058
+ console.log();
1059
+ }
1060
+ console.log(pc3.dim(`Total: ${testCases.length} test(s) in ${byComponent.size} component(s)`));
1061
+ console.log();
1062
+ }
1063
+ export {
1064
+ listTests,
1065
+ runTestCommand
1066
+ };
1067
+ //# sourceMappingURL=test-SQ5ZHXWU.js.map