@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.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON reporter - machine-readable output
|
|
3
|
+
*
|
|
4
|
+
* Outputs structured JSON for:
|
|
5
|
+
* - AI analysis and processing
|
|
6
|
+
* - Custom dashboards
|
|
7
|
+
* - Integration with other tools
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
11
|
+
import { dirname } from 'node:path';
|
|
12
|
+
import type { TestRunResult, TestReporter } from '../types.js';
|
|
13
|
+
|
|
14
|
+
export interface JsonReporterOptions {
|
|
15
|
+
/** Output file path */
|
|
16
|
+
outputPath: string;
|
|
17
|
+
/** Pretty print the JSON */
|
|
18
|
+
pretty?: boolean;
|
|
19
|
+
/** Include full step details */
|
|
20
|
+
includeSteps?: boolean;
|
|
21
|
+
/** Include stack traces in errors */
|
|
22
|
+
includeStacks?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* JSON report structure
|
|
27
|
+
*/
|
|
28
|
+
export interface JsonReport {
|
|
29
|
+
/** Report metadata */
|
|
30
|
+
meta: {
|
|
31
|
+
/** Report version */
|
|
32
|
+
version: string;
|
|
33
|
+
/** Test run start time */
|
|
34
|
+
startTime: string;
|
|
35
|
+
/** Test run end time */
|
|
36
|
+
endTime: string;
|
|
37
|
+
/** Total duration in milliseconds */
|
|
38
|
+
duration: number;
|
|
39
|
+
};
|
|
40
|
+
/** Summary statistics */
|
|
41
|
+
summary: {
|
|
42
|
+
total: number;
|
|
43
|
+
passed: number;
|
|
44
|
+
failed: number;
|
|
45
|
+
skipped: number;
|
|
46
|
+
passRate: number;
|
|
47
|
+
a11yViolations?: number;
|
|
48
|
+
};
|
|
49
|
+
/** Test suites with results */
|
|
50
|
+
suites: Array<{
|
|
51
|
+
name: string;
|
|
52
|
+
tests: number;
|
|
53
|
+
passed: number;
|
|
54
|
+
failed: number;
|
|
55
|
+
skipped: number;
|
|
56
|
+
duration: number;
|
|
57
|
+
results: Array<{
|
|
58
|
+
id: string;
|
|
59
|
+
component: string;
|
|
60
|
+
variant: string;
|
|
61
|
+
status: 'passed' | 'failed' | 'skipped';
|
|
62
|
+
duration: number;
|
|
63
|
+
steps?: Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
status: 'passed' | 'failed' | 'skipped';
|
|
66
|
+
duration: number;
|
|
67
|
+
error?: string;
|
|
68
|
+
}>;
|
|
69
|
+
error?: {
|
|
70
|
+
message: string;
|
|
71
|
+
stack?: string;
|
|
72
|
+
};
|
|
73
|
+
accessibility?: {
|
|
74
|
+
violations: number;
|
|
75
|
+
passes: number;
|
|
76
|
+
details?: Array<{
|
|
77
|
+
id: string;
|
|
78
|
+
impact: string;
|
|
79
|
+
description: string;
|
|
80
|
+
elements: number;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
retryAttempt?: number;
|
|
84
|
+
}>;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a JSON reporter
|
|
90
|
+
*/
|
|
91
|
+
export function createJsonReporter(options: JsonReporterOptions): TestReporter {
|
|
92
|
+
const { outputPath, pretty = true, includeSteps = true, includeStacks = false } = options;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
async onRunComplete(result: TestRunResult) {
|
|
96
|
+
const report = generateJsonReport(result, includeSteps, includeStacks);
|
|
97
|
+
|
|
98
|
+
// Ensure output directory exists
|
|
99
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
100
|
+
|
|
101
|
+
// Write the JSON file
|
|
102
|
+
const json = pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
|
|
103
|
+
await writeFile(outputPath, json, 'utf-8');
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate JSON report from test results
|
|
110
|
+
*/
|
|
111
|
+
function generateJsonReport(
|
|
112
|
+
result: TestRunResult,
|
|
113
|
+
includeSteps: boolean,
|
|
114
|
+
includeStacks: boolean
|
|
115
|
+
): JsonReport {
|
|
116
|
+
const { suites, totalTests, totalPassed, totalFailed, totalSkipped, totalDuration, startTime, endTime } = result;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
meta: {
|
|
120
|
+
version: '1.0.0',
|
|
121
|
+
startTime: startTime.toISOString(),
|
|
122
|
+
endTime: endTime.toISOString(),
|
|
123
|
+
duration: totalDuration,
|
|
124
|
+
},
|
|
125
|
+
summary: {
|
|
126
|
+
total: totalTests,
|
|
127
|
+
passed: totalPassed,
|
|
128
|
+
failed: totalFailed,
|
|
129
|
+
skipped: totalSkipped,
|
|
130
|
+
passRate: totalTests > 0 ? Math.round((totalPassed / totalTests) * 100) : 0,
|
|
131
|
+
a11yViolations: result.totalA11yViolations,
|
|
132
|
+
},
|
|
133
|
+
suites: suites.map((suite) => ({
|
|
134
|
+
name: suite.name,
|
|
135
|
+
tests: suite.tests.length,
|
|
136
|
+
passed: suite.passed,
|
|
137
|
+
failed: suite.failed,
|
|
138
|
+
skipped: suite.skipped,
|
|
139
|
+
duration: suite.duration,
|
|
140
|
+
results: suite.tests.map((test) => {
|
|
141
|
+
const resultObj: JsonReport['suites'][0]['results'][0] = {
|
|
142
|
+
id: test.testCase.id,
|
|
143
|
+
component: test.testCase.component,
|
|
144
|
+
variant: test.testCase.variant,
|
|
145
|
+
status: test.status,
|
|
146
|
+
duration: test.duration,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Include steps if requested
|
|
150
|
+
if (includeSteps && test.steps.length > 0) {
|
|
151
|
+
resultObj.steps = test.steps.map((step) => ({
|
|
152
|
+
name: step.name,
|
|
153
|
+
status: step.status,
|
|
154
|
+
duration: step.duration,
|
|
155
|
+
error: step.error?.message,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Include error if present
|
|
160
|
+
if (test.error) {
|
|
161
|
+
resultObj.error = {
|
|
162
|
+
message: test.error.message,
|
|
163
|
+
stack: includeStacks ? test.error.stack : undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Include accessibility results if present
|
|
168
|
+
if (test.accessibility) {
|
|
169
|
+
resultObj.accessibility = {
|
|
170
|
+
violations: test.accessibility.violations.length,
|
|
171
|
+
passes: test.accessibility.passes,
|
|
172
|
+
details: test.accessibility.violations.map((v) => ({
|
|
173
|
+
id: v.id,
|
|
174
|
+
impact: v.impact,
|
|
175
|
+
description: v.description,
|
|
176
|
+
elements: v.nodes.length,
|
|
177
|
+
})),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Include retry info if applicable
|
|
182
|
+
if (test.retryAttempt) {
|
|
183
|
+
resultObj.retryAttempt = test.retryAttempt;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return resultObj;
|
|
187
|
+
}),
|
|
188
|
+
})),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JUnit XML reporter - CI/CD integration
|
|
3
|
+
*
|
|
4
|
+
* Generates JUnit XML format compatible with:
|
|
5
|
+
* - GitHub Actions (mikepenz/action-junit-report)
|
|
6
|
+
* - Jenkins
|
|
7
|
+
* - GitLab CI
|
|
8
|
+
* - CircleCI
|
|
9
|
+
* - Azure DevOps
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
13
|
+
import { join, dirname } from 'node:path';
|
|
14
|
+
import type { TestRunResult, TestReporter, TestResult, TestSuite } from '../types.js';
|
|
15
|
+
|
|
16
|
+
export interface JUnitReporterOptions {
|
|
17
|
+
/** Output file path */
|
|
18
|
+
outputPath: string;
|
|
19
|
+
/** Suite name for the root element */
|
|
20
|
+
suiteName?: string;
|
|
21
|
+
/** Include properties in the XML */
|
|
22
|
+
includeProperties?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a JUnit XML reporter
|
|
27
|
+
*/
|
|
28
|
+
export function createJUnitReporter(options: JUnitReporterOptions): TestReporter {
|
|
29
|
+
const { outputPath, suiteName = 'Segments Tests', includeProperties = true } = options;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
async onRunComplete(result: TestRunResult) {
|
|
33
|
+
const xml = generateJUnitXml(result, suiteName, includeProperties);
|
|
34
|
+
|
|
35
|
+
// Ensure output directory exists
|
|
36
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
37
|
+
|
|
38
|
+
// Write the XML file
|
|
39
|
+
await writeFile(outputPath, xml, 'utf-8');
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate JUnit XML from test results
|
|
46
|
+
*/
|
|
47
|
+
function generateJUnitXml(
|
|
48
|
+
result: TestRunResult,
|
|
49
|
+
suiteName: string,
|
|
50
|
+
includeProperties: boolean
|
|
51
|
+
): string {
|
|
52
|
+
const lines: string[] = [];
|
|
53
|
+
|
|
54
|
+
// XML declaration
|
|
55
|
+
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
56
|
+
|
|
57
|
+
// Root testsuites element
|
|
58
|
+
lines.push(
|
|
59
|
+
`<testsuites name="${escapeXml(suiteName)}" tests="${result.totalTests}" failures="${result.totalFailed}" skipped="${result.totalSkipped}" time="${(result.totalDuration / 1000).toFixed(3)}">`
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Generate each test suite
|
|
63
|
+
for (const suite of result.suites) {
|
|
64
|
+
lines.push(generateTestSuiteXml(suite, includeProperties));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
lines.push('</testsuites>');
|
|
68
|
+
|
|
69
|
+
return lines.join('\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate XML for a single test suite
|
|
74
|
+
*/
|
|
75
|
+
function generateTestSuiteXml(suite: TestSuite, includeProperties: boolean): string {
|
|
76
|
+
const lines: string[] = [];
|
|
77
|
+
|
|
78
|
+
// Testsuite element
|
|
79
|
+
lines.push(
|
|
80
|
+
` <testsuite name="${escapeXml(suite.name)}" tests="${suite.tests.length}" failures="${suite.failed}" skipped="${suite.skipped}" time="${(suite.duration / 1000).toFixed(3)}">`
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Properties (optional)
|
|
84
|
+
if (includeProperties) {
|
|
85
|
+
lines.push(' <properties>');
|
|
86
|
+
lines.push(` <property name="component" value="${escapeXml(suite.name)}"/>`);
|
|
87
|
+
lines.push(' </properties>');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Test cases
|
|
91
|
+
for (const test of suite.tests) {
|
|
92
|
+
lines.push(generateTestCaseXml(test));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
lines.push(' </testsuite>');
|
|
96
|
+
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate XML for a single test case
|
|
102
|
+
*/
|
|
103
|
+
function generateTestCaseXml(result: TestResult): string {
|
|
104
|
+
const { testCase, status, duration, error, steps, accessibility } = result;
|
|
105
|
+
const lines: string[] = [];
|
|
106
|
+
|
|
107
|
+
const testName = testCase.variant;
|
|
108
|
+
const className = testCase.component;
|
|
109
|
+
const time = (duration / 1000).toFixed(3);
|
|
110
|
+
|
|
111
|
+
// Opening tag
|
|
112
|
+
if (status === 'passed' && !accessibility?.violations.length) {
|
|
113
|
+
// Simple passed test - self-closing tag
|
|
114
|
+
lines.push(` <testcase name="${escapeXml(testName)}" classname="${escapeXml(className)}" time="${time}"/>`);
|
|
115
|
+
} else {
|
|
116
|
+
// Test with failure, skip, or a11y info
|
|
117
|
+
lines.push(` <testcase name="${escapeXml(testName)}" classname="${escapeXml(className)}" time="${time}">`);
|
|
118
|
+
|
|
119
|
+
if (status === 'failed') {
|
|
120
|
+
// Failure element
|
|
121
|
+
const message = error?.message || 'Test failed';
|
|
122
|
+
lines.push(` <failure message="${escapeXml(message)}" type="AssertionError">`);
|
|
123
|
+
|
|
124
|
+
// Add detailed failure info
|
|
125
|
+
const details: string[] = [];
|
|
126
|
+
|
|
127
|
+
// Add step failures
|
|
128
|
+
const failedSteps = steps.filter((s) => s.status === 'failed');
|
|
129
|
+
if (failedSteps.length > 0) {
|
|
130
|
+
details.push('Failed steps:');
|
|
131
|
+
for (const step of failedSteps) {
|
|
132
|
+
details.push(` - ${step.name}: ${step.error?.message || 'Failed'}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Add stack trace if available
|
|
137
|
+
if (error?.stack) {
|
|
138
|
+
details.push('');
|
|
139
|
+
details.push('Stack trace:');
|
|
140
|
+
details.push(error.stack);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (details.length > 0) {
|
|
144
|
+
lines.push(escapeXml(details.join('\n')));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
lines.push(' </failure>');
|
|
148
|
+
} else if (status === 'skipped') {
|
|
149
|
+
lines.push(' <skipped/>');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Add system-out for accessibility violations
|
|
153
|
+
if (accessibility?.violations.length) {
|
|
154
|
+
lines.push(' <system-out>');
|
|
155
|
+
lines.push(`Accessibility violations (${accessibility.violations.length}):`);
|
|
156
|
+
for (const violation of accessibility.violations) {
|
|
157
|
+
lines.push(` [${violation.impact}] ${violation.id}: ${violation.description}`);
|
|
158
|
+
lines.push(` Help: ${violation.help}`);
|
|
159
|
+
for (const node of violation.nodes.slice(0, 3)) {
|
|
160
|
+
lines.push(` Element: ${node.html.substring(0, 100)}`);
|
|
161
|
+
}
|
|
162
|
+
if (violation.nodes.length > 3) {
|
|
163
|
+
lines.push(` ... and ${violation.nodes.length - 3} more elements`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
lines.push(' </system-out>');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
lines.push(' </testcase>');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return lines.join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Escape special XML characters
|
|
177
|
+
*/
|
|
178
|
+
function escapeXml(str: string): string {
|
|
179
|
+
return str
|
|
180
|
+
.replace(/&/g, '&')
|
|
181
|
+
.replace(/</g, '<')
|
|
182
|
+
.replace(/>/g, '>')
|
|
183
|
+
.replace(/"/g, '"')
|
|
184
|
+
.replace(/'/g, ''')
|
|
185
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ''); // Remove invalid XML characters
|
|
186
|
+
}
|