@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,278 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { __testing } from "../../parser.js";
|
|
3
|
+
|
|
4
|
+
const { parseStories, isCustomTemplate, extractArgsContent } = __testing;
|
|
5
|
+
|
|
6
|
+
describe("parseStories", () => {
|
|
7
|
+
describe("CSF 3.0 format", () => {
|
|
8
|
+
it("parses simple CSF 3.0 story", () => {
|
|
9
|
+
const content = `
|
|
10
|
+
export const Primary = {
|
|
11
|
+
args: {
|
|
12
|
+
variant: 'primary',
|
|
13
|
+
label: 'Click me'
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
`;
|
|
17
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
18
|
+
expect(result).toHaveLength(1);
|
|
19
|
+
expect(result[0].name).toBe("Primary");
|
|
20
|
+
expect(result[0].args).toEqual({
|
|
21
|
+
variant: "primary",
|
|
22
|
+
label: "Click me",
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("parses multiple CSF 3.0 stories", () => {
|
|
27
|
+
const content = `
|
|
28
|
+
export const Primary = {
|
|
29
|
+
args: { variant: 'primary' }
|
|
30
|
+
};
|
|
31
|
+
export const Secondary = {
|
|
32
|
+
args: { variant: 'secondary' }
|
|
33
|
+
};
|
|
34
|
+
export const Disabled = {
|
|
35
|
+
args: { variant: 'primary', disabled: true }
|
|
36
|
+
};
|
|
37
|
+
`;
|
|
38
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
39
|
+
expect(result).toHaveLength(3);
|
|
40
|
+
expect(result.map((s) => s.name)).toEqual(["Primary", "Secondary", "Disabled"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("parses story with type annotation", () => {
|
|
44
|
+
const content = `
|
|
45
|
+
export const Primary: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
size: 'large'
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
`;
|
|
51
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
52
|
+
expect(result).toHaveLength(1);
|
|
53
|
+
expect(result[0].args.size).toBe("large");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("skips default and meta exports", () => {
|
|
57
|
+
const content = `
|
|
58
|
+
export default { title: 'Button' };
|
|
59
|
+
export const meta = { title: 'Button' };
|
|
60
|
+
export const Primary = { args: { label: 'Click' } };
|
|
61
|
+
`;
|
|
62
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
63
|
+
expect(result).toHaveLength(1);
|
|
64
|
+
expect(result[0].name).toBe("Primary");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("detects custom render function", () => {
|
|
68
|
+
const content = `
|
|
69
|
+
export const WithRender = {
|
|
70
|
+
args: { label: 'Test' },
|
|
71
|
+
render: (args) => <div><Button {...args} /></div>
|
|
72
|
+
};
|
|
73
|
+
`;
|
|
74
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
75
|
+
expect(result).toHaveLength(1);
|
|
76
|
+
expect(result[0].hasCustomRender).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("handles story without args", () => {
|
|
80
|
+
const content = `
|
|
81
|
+
export const Default = {};
|
|
82
|
+
`;
|
|
83
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
84
|
+
expect(result).toHaveLength(1);
|
|
85
|
+
expect(result[0].args).toEqual({});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("CSF 2.0 format", () => {
|
|
90
|
+
it("parses Template.bind({}) pattern", () => {
|
|
91
|
+
const content = `
|
|
92
|
+
const Template = (args) => <Button {...args} />;
|
|
93
|
+
export const Primary = Template.bind({});
|
|
94
|
+
Primary.args = {
|
|
95
|
+
variant: 'primary'
|
|
96
|
+
};
|
|
97
|
+
`;
|
|
98
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
99
|
+
expect(result).toHaveLength(1);
|
|
100
|
+
expect(result[0].name).toBe("Primary");
|
|
101
|
+
expect(result[0].args).toEqual({ variant: "primary" });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("parses multiple CSF 2.0 stories", () => {
|
|
105
|
+
const content = `
|
|
106
|
+
const Template = (args) => <Button {...args} />;
|
|
107
|
+
export const Primary = Template.bind({});
|
|
108
|
+
Primary.args = { variant: 'primary' };
|
|
109
|
+
export const Secondary = Template.bind({});
|
|
110
|
+
Secondary.args = { variant: 'secondary' };
|
|
111
|
+
`;
|
|
112
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
113
|
+
expect(result).toHaveLength(2);
|
|
114
|
+
expect(result[0].args.variant).toBe("primary");
|
|
115
|
+
expect(result[1].args.variant).toBe("secondary");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("handles CSF 2.0 story without args", () => {
|
|
119
|
+
const content = `
|
|
120
|
+
const Template = (args) => <Button {...args} />;
|
|
121
|
+
export const Default = Template.bind({});
|
|
122
|
+
`;
|
|
123
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
124
|
+
expect(result).toHaveLength(1);
|
|
125
|
+
expect(result[0].args).toEqual({});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("spread resolution", () => {
|
|
130
|
+
it("resolves spread from const declarations", () => {
|
|
131
|
+
const content = `
|
|
132
|
+
const defaultArgs = { size: 'medium', disabled: false };
|
|
133
|
+
export const Primary = {
|
|
134
|
+
args: {
|
|
135
|
+
...defaultArgs,
|
|
136
|
+
variant: 'primary'
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
`;
|
|
140
|
+
const constDeclarations = new Map([
|
|
141
|
+
["defaultArgs", { size: "medium", disabled: false }],
|
|
142
|
+
]);
|
|
143
|
+
const result = parseStories(content, "Button", [], constDeclarations);
|
|
144
|
+
expect(result[0].args.size).toBe("medium");
|
|
145
|
+
expect(result[0].args.disabled).toBe(false);
|
|
146
|
+
expect(result[0].args.variant).toBe("primary");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("explicit args override spread values", () => {
|
|
150
|
+
const content = `
|
|
151
|
+
export const Primary = {
|
|
152
|
+
args: {
|
|
153
|
+
...defaultArgs,
|
|
154
|
+
size: 'large'
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
`;
|
|
158
|
+
const constDeclarations = new Map([
|
|
159
|
+
["defaultArgs", { size: "small", disabled: false }],
|
|
160
|
+
]);
|
|
161
|
+
const result = parseStories(content, "Button", [], constDeclarations);
|
|
162
|
+
expect(result[0].args.size).toBe("large");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("story description", () => {
|
|
167
|
+
it("extracts description from CSF 3.0 parameters", () => {
|
|
168
|
+
const content = `
|
|
169
|
+
export const Primary = {
|
|
170
|
+
args: { label: 'Click' },
|
|
171
|
+
parameters: {
|
|
172
|
+
docs: {
|
|
173
|
+
description: {
|
|
174
|
+
story: 'The primary button style'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
`;
|
|
180
|
+
const result = parseStories(content, "Button", [], new Map());
|
|
181
|
+
expect(result[0].description).toBe("The primary button style");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("isCustomTemplate", () => {
|
|
187
|
+
it("returns false for simple spread template", () => {
|
|
188
|
+
const content = `const Template = (args) => <Button {...args} />
|
|
189
|
+
export const Primary = Template.bind({});`;
|
|
190
|
+
const result = isCustomTemplate(content, "Template");
|
|
191
|
+
expect(result).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("returns false for parenthesized simple template", () => {
|
|
195
|
+
const content = `const Template = (args) => (<Button {...args} />)
|
|
196
|
+
export const Primary = Template.bind({});`;
|
|
197
|
+
const result = isCustomTemplate(content, "Template");
|
|
198
|
+
expect(result).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("detects hooks in single-line template body", () => {
|
|
202
|
+
// Hooks on same line as arrow are detected
|
|
203
|
+
const content = `const Template = (args) => { const [v] = useState(); return <C {...args} />; }
|
|
204
|
+
export const Primary = Template.bind({});`;
|
|
205
|
+
const result = isCustomTemplate(content, "Template");
|
|
206
|
+
expect(result).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("returns false for multiline block templates (regex limitation)", () => {
|
|
210
|
+
// Due to regex multiline flag, multiline block templates may not be fully captured
|
|
211
|
+
// This documents current behavior - the regex matches up to the first line end
|
|
212
|
+
const content = `const Template = (args) => {
|
|
213
|
+
const [value, setValue] = useState('');
|
|
214
|
+
return <Input {...args} />;
|
|
215
|
+
}
|
|
216
|
+
export const Primary = Template.bind({});`;
|
|
217
|
+
const result = isCustomTemplate(content, "Template");
|
|
218
|
+
// Current implementation returns false for multiline blocks
|
|
219
|
+
expect(result).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("returns false when template not found", () => {
|
|
223
|
+
const content = `const Other = (args) => <Button {...args} />
|
|
224
|
+
export const Primary = Other.bind({});`;
|
|
225
|
+
const result = isCustomTemplate(content, "Template");
|
|
226
|
+
expect(result).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("detects custom logic in inline templates", () => {
|
|
230
|
+
// Templates with complex logic on a single line ARE detected
|
|
231
|
+
const content = `const Template = (args) => { useEffect(() => {}, []); return <C {...args} />; }
|
|
232
|
+
export const Primary = Template.bind({});`;
|
|
233
|
+
const result = isCustomTemplate(content, "Template");
|
|
234
|
+
expect(result).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("extractArgsContent", () => {
|
|
239
|
+
it("extracts args content from assignment", () => {
|
|
240
|
+
const content = `
|
|
241
|
+
Primary.args = {
|
|
242
|
+
variant: 'primary',
|
|
243
|
+
label: 'Click me'
|
|
244
|
+
};
|
|
245
|
+
`;
|
|
246
|
+
const result = extractArgsContent(content, "Primary");
|
|
247
|
+
expect(result).toContain("variant");
|
|
248
|
+
expect(result).toContain("primary");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("handles nested braces in args", () => {
|
|
252
|
+
const content = `
|
|
253
|
+
Primary.args = {
|
|
254
|
+
style: { color: 'red', padding: { top: 10 } },
|
|
255
|
+
label: 'Click'
|
|
256
|
+
};
|
|
257
|
+
`;
|
|
258
|
+
const result = extractArgsContent(content, "Primary");
|
|
259
|
+
expect(result).toContain("style");
|
|
260
|
+
expect(result).toContain("color");
|
|
261
|
+
expect(result).toContain("padding");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("returns null when no args found", () => {
|
|
265
|
+
const content = `export const Primary = Template.bind({});`;
|
|
266
|
+
const result = extractArgsContent(content, "Primary");
|
|
267
|
+
expect(result).toBeNull();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("handles different story names", () => {
|
|
271
|
+
const content = `
|
|
272
|
+
MyCustomStory.args = { foo: 'bar' };
|
|
273
|
+
`;
|
|
274
|
+
const result = extractArgsContent(content, "MyCustomStory");
|
|
275
|
+
expect(result).toContain("foo");
|
|
276
|
+
expect(result).toContain("bar");
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
__testing,
|
|
4
|
+
storyNameToTitle,
|
|
5
|
+
extractCategory,
|
|
6
|
+
parseStoryContent,
|
|
7
|
+
} from "../../parser.js";
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
findMatchingBrace,
|
|
11
|
+
findMatchingBraceInContent,
|
|
12
|
+
splitAtTopLevelCommas,
|
|
13
|
+
calculateConfidence,
|
|
14
|
+
} = __testing;
|
|
15
|
+
|
|
16
|
+
describe("findMatchingBrace", () => {
|
|
17
|
+
describe("curly braces", () => {
|
|
18
|
+
it("finds matching closing brace for simple object", () => {
|
|
19
|
+
const content = "{ key: 'value' }";
|
|
20
|
+
const result = findMatchingBrace(content, 0, "{", "}");
|
|
21
|
+
expect(result).toBe(15);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("finds matching brace with nested objects", () => {
|
|
25
|
+
const content = "{ a: { b: 'c' } }";
|
|
26
|
+
const result = findMatchingBrace(content, 0, "{", "}");
|
|
27
|
+
expect(result).toBe(16);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("finds inner brace correctly", () => {
|
|
31
|
+
const content = "{ a: { b: 'c' } }";
|
|
32
|
+
const result = findMatchingBrace(content, 5, "{", "}");
|
|
33
|
+
expect(result).toBe(14);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns -1 when no matching brace found", () => {
|
|
37
|
+
const content = "{ unclosed";
|
|
38
|
+
const result = findMatchingBrace(content, 0, "{", "}");
|
|
39
|
+
expect(result).toBe(-1);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("square brackets", () => {
|
|
44
|
+
it("finds matching bracket for array", () => {
|
|
45
|
+
const content = "[1, 2, 3]";
|
|
46
|
+
const result = findMatchingBrace(content, 0, "[", "]");
|
|
47
|
+
expect(result).toBe(8);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("finds matching bracket with nested arrays", () => {
|
|
51
|
+
const content = "[[1, 2], [3, 4]]";
|
|
52
|
+
const result = findMatchingBrace(content, 0, "[", "]");
|
|
53
|
+
expect(result).toBe(15);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("string handling", () => {
|
|
58
|
+
it("ignores braces inside single-quoted strings", () => {
|
|
59
|
+
const content = "{ text: 'hello { world }' }";
|
|
60
|
+
const result = findMatchingBrace(content, 0, "{", "}");
|
|
61
|
+
expect(result).toBe(26);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("ignores braces inside double-quoted strings", () => {
|
|
65
|
+
const content = '{ text: "hello { world }" }';
|
|
66
|
+
const result = findMatchingBrace(content, 0, "{", "}");
|
|
67
|
+
expect(result).toBe(26);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("ignores braces inside template literals", () => {
|
|
71
|
+
const content = "{ text: `hello { world }` }";
|
|
72
|
+
const result = findMatchingBrace(content, 0, "{", "}");
|
|
73
|
+
expect(result).toBe(26);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("handles escaped quotes in strings", () => {
|
|
77
|
+
const content = `{ text: 'it\\'s a { test }' }`;
|
|
78
|
+
const result = findMatchingBrace(content, 0, "{", "}");
|
|
79
|
+
expect(result).toBe(27);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("findMatchingBraceInContent", () => {
|
|
85
|
+
it("finds matching brace with same behavior as findMatchingBrace", () => {
|
|
86
|
+
const content = "{ a: { b: 'c' } }";
|
|
87
|
+
const result = findMatchingBraceInContent(content, 0, "{", "}");
|
|
88
|
+
expect(result).toBe(16);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("handles complex content with multiple string types", () => {
|
|
92
|
+
const content = `{
|
|
93
|
+
single: 'quote',
|
|
94
|
+
double: "quote",
|
|
95
|
+
template: \`literal\`,
|
|
96
|
+
nested: { inner: true }
|
|
97
|
+
}`;
|
|
98
|
+
const result = findMatchingBraceInContent(content, 0, "{", "}");
|
|
99
|
+
expect(result).toBeGreaterThan(0);
|
|
100
|
+
expect(content[result]).toBe("}");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("splitAtTopLevelCommas", () => {
|
|
105
|
+
it("splits simple comma-separated values", () => {
|
|
106
|
+
const result = splitAtTopLevelCommas("a, b, c");
|
|
107
|
+
expect(result).toEqual(["a", " b", " c"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("does not split commas inside nested braces", () => {
|
|
111
|
+
const result = splitAtTopLevelCommas("a: { x: 1, y: 2 }, b");
|
|
112
|
+
expect(result).toEqual(["a: { x: 1, y: 2 }", " b"]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("does not split commas inside arrays", () => {
|
|
116
|
+
const result = splitAtTopLevelCommas("arr: [1, 2, 3], other");
|
|
117
|
+
expect(result).toEqual(["arr: [1, 2, 3]", " other"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("does not split commas inside parentheses", () => {
|
|
121
|
+
const result = splitAtTopLevelCommas("fn(a, b), result");
|
|
122
|
+
expect(result).toEqual(["fn(a, b)", " result"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("does not split commas inside strings", () => {
|
|
126
|
+
const result = splitAtTopLevelCommas("text: 'hello, world', other");
|
|
127
|
+
expect(result).toEqual(["text: 'hello, world'", " other"]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("handles empty input", () => {
|
|
131
|
+
const result = splitAtTopLevelCommas("");
|
|
132
|
+
expect(result).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("handles single item without comma", () => {
|
|
136
|
+
const result = splitAtTopLevelCommas("single");
|
|
137
|
+
expect(result).toEqual(["single"]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("handles deeply nested structures", () => {
|
|
141
|
+
const result = splitAtTopLevelCommas("a: { b: { c: [1, 2] } }, d");
|
|
142
|
+
expect(result).toHaveLength(2);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("calculateConfidence", () => {
|
|
147
|
+
it("returns 1.0 for perfect parsing", () => {
|
|
148
|
+
const meta = { title: "Components/Button", componentName: "Button", componentImport: "./Button" };
|
|
149
|
+
const argTypes = { variant: { control: "select" } };
|
|
150
|
+
const stories = [{ name: "Primary", args: {} }];
|
|
151
|
+
const warnings: string[] = [];
|
|
152
|
+
|
|
153
|
+
const result = calculateConfidence(meta, argTypes, stories, warnings);
|
|
154
|
+
expect(result).toBe(1.0);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("penalizes for warnings", () => {
|
|
158
|
+
const meta = { title: "Components/Button", componentName: "Button" };
|
|
159
|
+
const argTypes = {};
|
|
160
|
+
const stories = [{ name: "Primary", args: {} }];
|
|
161
|
+
const warnings = ["Warning 1", "Warning 2"];
|
|
162
|
+
|
|
163
|
+
const result = calculateConfidence(meta, argTypes, stories, warnings);
|
|
164
|
+
expect(result).toBeLessThan(1.0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("penalizes for unknown component name", () => {
|
|
168
|
+
const meta = { title: "Components/Button", componentName: "Unknown" };
|
|
169
|
+
const argTypes = {};
|
|
170
|
+
const stories = [{ name: "Primary", args: {} }];
|
|
171
|
+
const warnings: string[] = [];
|
|
172
|
+
|
|
173
|
+
const result = calculateConfidence(meta, argTypes, stories, warnings);
|
|
174
|
+
expect(result).toBeLessThan(1.0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("penalizes for missing component import", () => {
|
|
178
|
+
const meta = { title: "Components/Button", componentName: "Button" };
|
|
179
|
+
const argTypes = { variant: {} };
|
|
180
|
+
const stories = [{ name: "Primary", args: {} }];
|
|
181
|
+
const warnings: string[] = [];
|
|
182
|
+
|
|
183
|
+
const result = calculateConfidence(meta, argTypes, stories, warnings);
|
|
184
|
+
expect(result).toBeLessThan(1.0);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("penalizes for no stories", () => {
|
|
188
|
+
const meta = { title: "Components/Button", componentName: "Button", componentImport: "./Button" };
|
|
189
|
+
const argTypes = { variant: {} };
|
|
190
|
+
const stories: any[] = [];
|
|
191
|
+
const warnings: string[] = [];
|
|
192
|
+
|
|
193
|
+
const result = calculateConfidence(meta, argTypes, stories, warnings);
|
|
194
|
+
expect(result).toBeLessThan(1.0);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("penalizes for custom render stories", () => {
|
|
198
|
+
const meta = { title: "Components/Button", componentName: "Button", componentImport: "./Button" };
|
|
199
|
+
const argTypes = { variant: {} };
|
|
200
|
+
const stories = [
|
|
201
|
+
{ name: "Primary", args: {}, hasCustomRender: true },
|
|
202
|
+
{ name: "Secondary", args: {} },
|
|
203
|
+
];
|
|
204
|
+
const warnings: string[] = [];
|
|
205
|
+
|
|
206
|
+
const result = calculateConfidence(meta, argTypes, stories, warnings);
|
|
207
|
+
expect(result).toBeLessThan(1.0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("clamps result to [0, 1] range", () => {
|
|
211
|
+
const meta = { title: "", componentName: "Unknown" };
|
|
212
|
+
const argTypes = {};
|
|
213
|
+
const stories: any[] = [];
|
|
214
|
+
const warnings = ["w1", "w2", "w3", "w4", "w5", "w6", "w7", "w8", "w9", "w10"];
|
|
215
|
+
|
|
216
|
+
const result = calculateConfidence(meta, argTypes, stories, warnings);
|
|
217
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
|
218
|
+
expect(result).toBeLessThanOrEqual(1);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("storyNameToTitle", () => {
|
|
223
|
+
it("converts PascalCase to Title Case", () => {
|
|
224
|
+
expect(storyNameToTitle("Primary")).toBe("Primary");
|
|
225
|
+
expect(storyNameToTitle("PrimaryButton")).toBe("Primary Button");
|
|
226
|
+
expect(storyNameToTitle("DisabledState")).toBe("Disabled State");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("handles multiple consecutive capitals", () => {
|
|
230
|
+
expect(storyNameToTitle("HTMLElement")).toBe("H T M L Element");
|
|
231
|
+
expect(storyNameToTitle("URLParser")).toBe("U R L Parser");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("handles single word", () => {
|
|
235
|
+
expect(storyNameToTitle("Default")).toBe("Default");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("handles already spaced input", () => {
|
|
239
|
+
expect(storyNameToTitle("Primary Button")).toBe("Primary Button");
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("extractCategory", () => {
|
|
244
|
+
it("extracts category from two-segment path", () => {
|
|
245
|
+
expect(extractCategory("Components/Button")).toBe("components");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("extracts category from multi-segment path", () => {
|
|
249
|
+
expect(extractCategory("Design System/Forms/Input")).toBe("forms");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("returns lowercase category", () => {
|
|
253
|
+
expect(extractCategory("Actions/Button")).toBe("actions");
|
|
254
|
+
expect(extractCategory("UI/Forms/Select")).toBe("forms");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("returns 'components' for single segment", () => {
|
|
258
|
+
expect(extractCategory("Button")).toBe("components");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("handles deeply nested paths", () => {
|
|
262
|
+
expect(extractCategory("A/B/C/D/E")).toBe("d");
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("parseStoryContent (integration)", () => {
|
|
267
|
+
it("parses complete story file", () => {
|
|
268
|
+
const content = `
|
|
269
|
+
import { Button } from './Button';
|
|
270
|
+
|
|
271
|
+
export default {
|
|
272
|
+
title: 'Components/Button',
|
|
273
|
+
component: Button,
|
|
274
|
+
tags: ['autodocs'],
|
|
275
|
+
argTypes: {
|
|
276
|
+
variant: {
|
|
277
|
+
control: 'select',
|
|
278
|
+
options: ['primary', 'secondary']
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export const Primary = {
|
|
284
|
+
args: {
|
|
285
|
+
variant: 'primary',
|
|
286
|
+
label: 'Click me'
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const Secondary = {
|
|
291
|
+
args: {
|
|
292
|
+
variant: 'secondary',
|
|
293
|
+
label: 'Click me'
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
`;
|
|
297
|
+
|
|
298
|
+
const result = parseStoryContent(content, "Button.stories.tsx");
|
|
299
|
+
|
|
300
|
+
expect(result.meta.title).toBe("Components/Button");
|
|
301
|
+
expect(result.meta.componentName).toBe("Button");
|
|
302
|
+
expect(result.meta.componentImport).toBe("./Button");
|
|
303
|
+
expect(result.meta.tags).toEqual(["autodocs"]);
|
|
304
|
+
|
|
305
|
+
expect(result.argTypes.variant).toBeDefined();
|
|
306
|
+
expect(result.argTypes.variant.control).toBe("select");
|
|
307
|
+
expect(result.argTypes.variant.options).toEqual(["primary", "secondary"]);
|
|
308
|
+
|
|
309
|
+
expect(result.stories).toHaveLength(2);
|
|
310
|
+
expect(result.stories[0].name).toBe("Primary");
|
|
311
|
+
expect(result.stories[0].args.variant).toBe("primary");
|
|
312
|
+
expect(result.stories[1].name).toBe("Secondary");
|
|
313
|
+
|
|
314
|
+
expect(result.confidence).toBeGreaterThan(0.5);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("parses CSF 2.0 format file", () => {
|
|
318
|
+
const content = `
|
|
319
|
+
import { Input } from './Input';
|
|
320
|
+
|
|
321
|
+
export default {
|
|
322
|
+
title: 'Forms/Input',
|
|
323
|
+
component: Input
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const Template = (args) => <Input {...args} />;
|
|
327
|
+
|
|
328
|
+
export const Default = Template.bind({});
|
|
329
|
+
Default.args = {
|
|
330
|
+
placeholder: 'Enter text'
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const Disabled = Template.bind({});
|
|
334
|
+
Disabled.args = {
|
|
335
|
+
placeholder: 'Disabled input',
|
|
336
|
+
disabled: true
|
|
337
|
+
};
|
|
338
|
+
`;
|
|
339
|
+
|
|
340
|
+
const result = parseStoryContent(content, "Input.stories.tsx");
|
|
341
|
+
|
|
342
|
+
expect(result.meta.title).toBe("Forms/Input");
|
|
343
|
+
expect(result.stories).toHaveLength(2);
|
|
344
|
+
expect(result.stories[0].args.placeholder).toBe("Enter text");
|
|
345
|
+
expect(result.stories[1].args.disabled).toBe(true);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("handles file with spread args", () => {
|
|
349
|
+
const content = `
|
|
350
|
+
const defaultArgs = {
|
|
351
|
+
size: 'medium',
|
|
352
|
+
disabled: false
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
export default { title: 'Components/Button' };
|
|
356
|
+
|
|
357
|
+
export const Primary = {
|
|
358
|
+
args: {
|
|
359
|
+
...defaultArgs,
|
|
360
|
+
variant: 'primary'
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
`;
|
|
364
|
+
|
|
365
|
+
const result = parseStoryContent(content, "Button.stories.tsx");
|
|
366
|
+
|
|
367
|
+
expect(result.stories[0].args.size).toBe("medium");
|
|
368
|
+
expect(result.stories[0].args.disabled).toBe(false);
|
|
369
|
+
expect(result.stories[0].args.variant).toBe("primary");
|
|
370
|
+
});
|
|
371
|
+
});
|