@usefragments/core 1.10.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
- package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
- package/dist/chunk-7ULGH74M.js +66 -0
- package/dist/chunk-7ULGH74M.js.map +1 -0
- package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
- package/dist/chunk-BMPYIUZE.js.map +1 -0
- package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
- package/dist/chunk-ML5S6QNU.js.map +1 -0
- package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
- package/dist/chunk-MZ2FS7U4.js.map +1 -0
- package/dist/chunk-PWIJMOI4.js +202 -0
- package/dist/chunk-PWIJMOI4.js.map +1 -0
- package/dist/chunk-RYFULE43.js +578 -0
- package/dist/chunk-RYFULE43.js.map +1 -0
- package/dist/codes/index.d.ts +2 -2
- package/dist/codes/index.js +3 -2
- package/dist/compiled-types/index.d.ts +327 -2
- package/dist/compiled-types/index.js +1 -1
- package/dist/generate/index.d.ts +3 -2
- package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
- package/dist/governance-telemetry.d.ts +6 -0
- package/dist/governance-telemetry.js +1 -1
- package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
- package/dist/index.d.ts +3280 -1305
- package/dist/index.js +2414 -350
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +228 -0
- package/dist/manifest.js +24 -0
- package/dist/manifest.js.map +1 -0
- package/dist/preview/index.js +45 -1
- package/dist/preview/index.js.map +1 -1
- package/dist/preview-runtime.d.ts +1 -2
- package/dist/preview-runtime.js +150 -14
- package/dist/preview-runtime.js.map +1 -1
- package/dist/react-types.d.ts +1 -2
- package/dist/registry.d.ts +1412 -203
- package/dist/registry.js +30 -3
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +3 -2
- package/dist/storyAdapter.d.ts +1 -2
- package/dist/storyAdapter.js +11 -49
- package/dist/storyAdapter.js.map +1 -1
- package/dist/test-utils.d.ts +3 -2
- package/dist/topology/index.d.ts +1 -1
- package/dist/topology/index.js +1 -1
- package/package.json +8 -2
- package/src/__tests__/contract-parser.test.ts +318 -277
- package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
- package/src/__tests__/preview-runtime.test.tsx +30 -8
- package/src/__tests__/schema.test.ts +191 -14
- package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
- package/src/analysis-plan/coverage.ts +181 -0
- package/src/analysis-plan/digest.ts +141 -0
- package/src/analysis-plan/index.ts +34 -0
- package/src/analysis-plan/types.ts +207 -0
- package/src/approved-contract-tokens.test.ts +39 -0
- package/src/approved-contract-tokens.ts +18 -0
- package/src/codes/__tests__/codes.test.ts +13 -0
- package/src/codes/codes.ts +40 -0
- package/src/compiled-types/index.ts +640 -39
- package/src/compiled-types/parse.test.ts +145 -4
- package/src/component-contract.ts +95 -53
- package/src/composition.ts +7 -13
- package/src/constants.ts +3 -6
- package/src/contract/hash.test.ts +20 -0
- package/src/contract/hash.ts +66 -9
- package/src/contract/index.ts +24 -1
- package/src/contract/manifest.test.ts +94 -0
- package/src/contract/manifest.ts +68 -0
- package/src/contract/preimage.test.ts +219 -1
- package/src/contract/preimage.ts +326 -6
- package/src/contract/stamp.test.ts +3 -0
- package/src/contract/stamp.ts +1 -1
- package/src/contract-parser.ts +54 -30
- package/src/defineFragment.test.ts +476 -91
- package/src/defineFragment.ts +204 -114
- package/src/domain-ids.test.ts +35 -0
- package/src/domain-ids.ts +61 -0
- package/src/evaluation/evaluate.test.ts +522 -0
- package/src/evaluation/evaluate.ts +690 -0
- package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
- package/src/evaluation/index.ts +58 -0
- package/src/evaluation/receipt.ts +753 -0
- package/src/evaluation/types.ts +406 -0
- package/src/facts/builders.ts +2 -0
- package/src/facts/compile.ts +29 -6
- package/src/facts/fact-index.ts +13 -3
- package/src/facts/fact-integrity-v1.test.ts +172 -0
- package/src/facts/facts.test.ts +15 -0
- package/src/facts/ids.ts +46 -3
- package/src/facts/index.ts +14 -1
- package/src/facts/integrity.ts +134 -0
- package/src/facts/types.ts +36 -0
- package/src/governance-integrity.test.ts +1 -0
- package/src/governance-integrity.ts +5 -3
- package/src/governance-telemetry.ts +8 -0
- package/src/governance.ts +70 -8
- package/src/index.ts +230 -37
- package/src/preview/validation.test.ts +62 -0
- package/src/preview/validation.ts +48 -2
- package/src/preview-runtime.tsx +227 -20
- package/src/registry-install-plan.ts +200 -109
- package/src/registry-shards.test.ts +263 -0
- package/src/registry.ts +237 -0
- package/src/repository-binding.test.ts +50 -0
- package/src/repository-binding.ts +96 -0
- package/src/rules/families.test.ts +36 -0
- package/src/rules/finding.ts +7 -2
- package/src/rules/index.ts +17 -1
- package/src/rules/rule-config.test.ts +66 -0
- package/src/rules/rule-config.ts +73 -0
- package/src/rules/rules.test.ts +26 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
- package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
- package/src/schema.ts +293 -113
- package/src/schemas/index.ts +1 -1
- package/src/storyAdapter.test.ts +68 -12
- package/src/storyAdapter.ts +44 -75
- package/src/topology/resolve-area.ts +1 -1
- package/src/types.ts +258 -40
- package/dist/chunk-3LLRNCPX.js.map +0 -1
- package/dist/chunk-RANPUC6C.js +0 -72
- package/dist/chunk-RANPUC6C.js.map +0 -1
- package/dist/chunk-XN3LSDPY.js.map +0 -1
- package/dist/chunk-YF65VYRY.js.map +0 -1
- package/src/fragment-types.ts +0 -214
- package/src/react-create-element.test.ts +0 -22
- package/src/react-create-element.ts +0 -12
- package/src/storyFilters.test.ts +0 -350
- package/src/storyFilters.ts +0 -253
package/src/fragment-types.ts
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript types for Fragment JSON files.
|
|
3
|
-
* These types correspond to the JSON schemas in ./schema/
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Figma design links and mappings
|
|
8
|
-
*/
|
|
9
|
-
export interface FragmentFigma {
|
|
10
|
-
/** Figma file URL */
|
|
11
|
-
file?: string;
|
|
12
|
-
/** Default Figma node ID for this component */
|
|
13
|
-
nodeId?: string;
|
|
14
|
-
/** Mapping of variant names to Figma node IDs */
|
|
15
|
-
variants?: Record<string, string>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Anti-pattern with optional alternative
|
|
20
|
-
*/
|
|
21
|
-
export interface FragmentDoNotItem {
|
|
22
|
-
/** What not to do */
|
|
23
|
-
text: string;
|
|
24
|
-
/** Component name to use instead */
|
|
25
|
-
instead?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Usage pattern with code example
|
|
30
|
-
*/
|
|
31
|
-
export interface FragmentPattern {
|
|
32
|
-
/** Pattern name */
|
|
33
|
-
name: string;
|
|
34
|
-
/** Code example */
|
|
35
|
-
code: string;
|
|
36
|
-
/** When to use this pattern */
|
|
37
|
-
description?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Usage guidelines for AI agents and developers
|
|
42
|
-
*/
|
|
43
|
-
export interface FragmentUsage {
|
|
44
|
-
/** Scenarios when this component should be used */
|
|
45
|
-
when?: string[];
|
|
46
|
-
/** Anti-patterns and what to use instead */
|
|
47
|
-
doNot?: (string | FragmentDoNotItem)[];
|
|
48
|
-
/** Common usage patterns with code examples */
|
|
49
|
-
patterns?: FragmentPattern[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Accessibility requirements and guidelines
|
|
54
|
-
*/
|
|
55
|
-
export interface FragmentAccessibility {
|
|
56
|
-
/** ARIA role this component implements */
|
|
57
|
-
role?: string;
|
|
58
|
-
/** Accessibility requirements */
|
|
59
|
-
requirements?: string[];
|
|
60
|
-
/** Keyboard interaction patterns (key -> description) */
|
|
61
|
-
keyboard?: Record<string, string>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Relationships to other components
|
|
66
|
-
*/
|
|
67
|
-
export interface FragmentRelated {
|
|
68
|
-
/** Similar components that might be alternatives */
|
|
69
|
-
similar?: string[];
|
|
70
|
-
/** Components commonly used together with this one */
|
|
71
|
-
composedWith?: string[];
|
|
72
|
-
/** Parent components or patterns where this is commonly used */
|
|
73
|
-
usedIn?: string[];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Administrative metadata
|
|
78
|
-
*/
|
|
79
|
-
export interface FragmentMeta {
|
|
80
|
-
/** Team or person responsible for this component */
|
|
81
|
-
owner?: string;
|
|
82
|
-
/** Component lifecycle status */
|
|
83
|
-
status?: "draft" | "experimental" | "beta" | "stable" | "deprecated";
|
|
84
|
-
/** Version when this component was introduced */
|
|
85
|
-
since?: string;
|
|
86
|
-
/** Version when this component was deprecated */
|
|
87
|
-
deprecatedSince?: string;
|
|
88
|
-
/** Why this component was deprecated and what to use instead */
|
|
89
|
-
deprecatedReason?: string;
|
|
90
|
-
/** Tags for categorization and search */
|
|
91
|
-
tags?: string[];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Fragment JSON file structure (.fragment.json)
|
|
96
|
-
* Contains enrichment metadata for a component
|
|
97
|
-
*/
|
|
98
|
-
export interface Fragment {
|
|
99
|
-
/** JSON Schema reference */
|
|
100
|
-
$schema?: string;
|
|
101
|
-
/** Component name (must match the component export name) */
|
|
102
|
-
name: string;
|
|
103
|
-
/** Brief description of the component's purpose */
|
|
104
|
-
description?: string;
|
|
105
|
-
/** Figma design links and mappings */
|
|
106
|
-
figma?: FragmentFigma;
|
|
107
|
-
/** Usage guidelines for AI agents and developers */
|
|
108
|
-
usage?: FragmentUsage;
|
|
109
|
-
/** Accessibility requirements and guidelines */
|
|
110
|
-
accessibility?: FragmentAccessibility;
|
|
111
|
-
/** Relationships to other components */
|
|
112
|
-
related?: FragmentRelated;
|
|
113
|
-
/** Administrative metadata */
|
|
114
|
-
meta?: FragmentMeta;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Prop entry in the registry
|
|
119
|
-
*/
|
|
120
|
-
export interface RegistryPropEntry {
|
|
121
|
-
/** TypeScript type (e.g., 'string', 'boolean', enum values) */
|
|
122
|
-
type?: string;
|
|
123
|
-
/** Simplified type category */
|
|
124
|
-
typeKind?: "string" | "number" | "boolean" | "enum" | "object" | "array" | "function" | "node" | "element" | "union" | "unknown";
|
|
125
|
-
/** For enum types, the allowed values */
|
|
126
|
-
options?: string[];
|
|
127
|
-
/** Default value if specified */
|
|
128
|
-
default?: unknown;
|
|
129
|
-
/** Whether this prop is required */
|
|
130
|
-
required?: boolean;
|
|
131
|
-
/** Prop description from JSDoc or TypeScript */
|
|
132
|
-
description?: string;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Component entry in the registry (simplified - focuses on paths and enrichment)
|
|
137
|
-
*/
|
|
138
|
-
export interface RegistryComponentEntry {
|
|
139
|
-
/** Relative path to the component source file */
|
|
140
|
-
path: string;
|
|
141
|
-
/** Relative path to the .fragment.json file (if exists) */
|
|
142
|
-
fragmentPath?: string;
|
|
143
|
-
/** Relative path to the .stories.tsx file (if exists) */
|
|
144
|
-
storyPath?: string;
|
|
145
|
-
/** Component category (inferred from directory or fragment) */
|
|
146
|
-
category?: string;
|
|
147
|
-
/** Component lifecycle status (from fragment) */
|
|
148
|
-
status?: "draft" | "experimental" | "beta" | "stable" | "deprecated";
|
|
149
|
-
/** Component description (from fragment or JSDoc) */
|
|
150
|
-
description?: string;
|
|
151
|
-
/** Has human-authored enrichment (fragment file exists with content beyond skeleton) */
|
|
152
|
-
hasEnrichment?: boolean;
|
|
153
|
-
/** Extracted prop definitions - only included if config.registry.includeProps is true */
|
|
154
|
-
props?: Record<string, RegistryPropEntry>;
|
|
155
|
-
/** Named exports from the component file */
|
|
156
|
-
exports?: string[];
|
|
157
|
-
/** Component dependencies (other components used) */
|
|
158
|
-
dependencies?: string[];
|
|
159
|
-
/** Merged fragment enrichment data - only included if config.registry.embedFragments is true */
|
|
160
|
-
fragment?: Fragment;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Minimal component index (.fragments/index.json)
|
|
165
|
-
* Ultra-light name → path mapping for quick lookups
|
|
166
|
-
*/
|
|
167
|
-
export interface FragmentIndex {
|
|
168
|
-
/** Schema version */
|
|
169
|
-
version: string;
|
|
170
|
-
/** When this index was generated */
|
|
171
|
-
generatedAt: string;
|
|
172
|
-
/** Simple name → path mapping */
|
|
173
|
-
components: Record<string, string>;
|
|
174
|
-
/** Categories for grouping */
|
|
175
|
-
categories?: Record<string, string[]>;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Registry file structure (.fragments/registry.json)
|
|
180
|
-
* Component index with resolved paths and optional metadata
|
|
181
|
-
*/
|
|
182
|
-
export interface FragmentRegistry {
|
|
183
|
-
/** JSON Schema reference */
|
|
184
|
-
$schema?: string;
|
|
185
|
-
/** Schema version */
|
|
186
|
-
version: string;
|
|
187
|
-
/** When this registry was generated */
|
|
188
|
-
generatedAt: string;
|
|
189
|
-
/** Component count for quick reference */
|
|
190
|
-
componentCount: number;
|
|
191
|
-
/** Component index keyed by component name */
|
|
192
|
-
components: Record<string, RegistryComponentEntry>;
|
|
193
|
-
/** Components grouped by category */
|
|
194
|
-
categories?: Record<string, string[]>;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Context file options for generation
|
|
199
|
-
*/
|
|
200
|
-
export interface FragmentContextOptions {
|
|
201
|
-
/** Output format */
|
|
202
|
-
format?: "markdown" | "json";
|
|
203
|
-
/** Compact mode - minimal output for token efficiency */
|
|
204
|
-
compact?: boolean;
|
|
205
|
-
/** What to include in the output */
|
|
206
|
-
include?: {
|
|
207
|
-
/** Include prop details (default: true) */
|
|
208
|
-
props?: boolean;
|
|
209
|
-
/** Include code examples (default: false) */
|
|
210
|
-
code?: boolean;
|
|
211
|
-
/** Include related components (default: true) */
|
|
212
|
-
relations?: boolean;
|
|
213
|
-
};
|
|
214
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
describe("getCreateElement", () => {
|
|
4
|
-
afterEach(() => {
|
|
5
|
-
vi.resetModules();
|
|
6
|
-
vi.doUnmock("node:module");
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it("throws a clear error when react cannot be resolved", async () => {
|
|
10
|
-
vi.doMock("node:module", () => ({
|
|
11
|
-
createRequire: () => () => {
|
|
12
|
-
throw new Error("Cannot find module 'react'");
|
|
13
|
-
},
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
const { getCreateElement } = await import("./react-create-element.js");
|
|
17
|
-
|
|
18
|
-
expect(() => getCreateElement()).toThrow(
|
|
19
|
-
'@usefragments/core: story rendering requires the optional peer dependency "react" (>=18). Install react to use storyModuleToFragment default render paths.',
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
|
|
3
|
-
export function getCreateElement(): typeof import("react")["createElement"] {
|
|
4
|
-
try {
|
|
5
|
-
return (createRequire(import.meta.url)("react") as typeof import("react")).createElement;
|
|
6
|
-
} catch (error) {
|
|
7
|
-
throw new Error(
|
|
8
|
-
'@usefragments/core: story rendering requires the optional peer dependency "react" (>=18). Install react to use storyModuleToFragment default render paths.',
|
|
9
|
-
{ cause: error instanceof Error ? error : undefined },
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
}
|
package/src/storyFilters.test.ts
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for Storybook adapter filtering.
|
|
3
|
-
* Tests per-file heuristics, cross-file sub-component detection, and config precedence.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect } from 'vitest';
|
|
7
|
-
import {
|
|
8
|
-
checkStoryExclusion,
|
|
9
|
-
detectSubComponentPaths,
|
|
10
|
-
isForceIncluded,
|
|
11
|
-
isConfigExcluded,
|
|
12
|
-
type ExclusionResult,
|
|
13
|
-
} from './storyFilters.js';
|
|
14
|
-
import type { StorybookFilterConfig } from './types.js';
|
|
15
|
-
|
|
16
|
-
const DEFAULTS: StorybookFilterConfig = {
|
|
17
|
-
excludeDeprecated: true,
|
|
18
|
-
excludeTests: true,
|
|
19
|
-
excludeSvgIcons: true,
|
|
20
|
-
excludeSubComponents: true,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// detectSubComponentPaths
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
describe('detectSubComponentPaths', () => {
|
|
28
|
-
it('identifies sub-components when one story matches the directory name', () => {
|
|
29
|
-
const files = [
|
|
30
|
-
{ relativePath: 'src/components/Form/Form.stories.tsx' },
|
|
31
|
-
{ relativePath: 'src/components/Form/Checkbox.stories.tsx' },
|
|
32
|
-
{ relativePath: 'src/components/Form/RadioGroup.stories.tsx' },
|
|
33
|
-
];
|
|
34
|
-
const result = detectSubComponentPaths(files);
|
|
35
|
-
|
|
36
|
-
expect(result.size).toBe(2);
|
|
37
|
-
expect(result.get('src/components/Form/Checkbox.stories.tsx')).toBe('Form');
|
|
38
|
-
expect(result.get('src/components/Form/RadioGroup.stories.tsx')).toBe('Form');
|
|
39
|
-
expect(result.has('src/components/Form/Form.stories.tsx')).toBe(false);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('treats story in its own sub-directory as standalone', () => {
|
|
43
|
-
const files = [
|
|
44
|
-
{ relativePath: 'src/components/Form/Form.stories.tsx' },
|
|
45
|
-
{ relativePath: 'src/components/Button/Button.stories.tsx' },
|
|
46
|
-
];
|
|
47
|
-
const result = detectSubComponentPaths(files);
|
|
48
|
-
expect(result.size).toBe(0);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('keeps single story in a directory as standalone', () => {
|
|
52
|
-
const files = [
|
|
53
|
-
{ relativePath: 'src/components/Button/Button.stories.tsx' },
|
|
54
|
-
];
|
|
55
|
-
const result = detectSubComponentPaths(files);
|
|
56
|
-
expect(result.size).toBe(0);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('keeps all stories when no story matches the directory name', () => {
|
|
60
|
-
const files = [
|
|
61
|
-
{ relativePath: 'src/components/Inputs/Checkbox.stories.tsx' },
|
|
62
|
-
{ relativePath: 'src/components/Inputs/RadioGroup.stories.tsx' },
|
|
63
|
-
{ relativePath: 'src/components/Inputs/Toggle.stories.tsx' },
|
|
64
|
-
];
|
|
65
|
-
const result = detectSubComponentPaths(files);
|
|
66
|
-
expect(result.size).toBe(0);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('detects three stories with one primary → two sub-components', () => {
|
|
70
|
-
const files = [
|
|
71
|
-
{ relativePath: 'src/components/Table/Table.stories.tsx' },
|
|
72
|
-
{ relativePath: 'src/components/Table/TableHeader.stories.tsx' },
|
|
73
|
-
{ relativePath: 'src/components/Table/TableRow.stories.tsx' },
|
|
74
|
-
];
|
|
75
|
-
const result = detectSubComponentPaths(files);
|
|
76
|
-
|
|
77
|
-
expect(result.size).toBe(2);
|
|
78
|
-
expect(result.get('src/components/Table/TableHeader.stories.tsx')).toBe('Table');
|
|
79
|
-
expect(result.get('src/components/Table/TableRow.stories.tsx')).toBe('Table');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('ignores non-story file patterns', () => {
|
|
83
|
-
const files = [
|
|
84
|
-
{ relativePath: 'src/components/Form/Form.fragment.tsx' },
|
|
85
|
-
{ relativePath: 'src/components/Form/Checkbox.fragment.tsx' },
|
|
86
|
-
];
|
|
87
|
-
const result = detectSubComponentPaths(files);
|
|
88
|
-
expect(result.size).toBe(0);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// checkStoryExclusion
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
describe('checkStoryExclusion', () => {
|
|
97
|
-
const base = {
|
|
98
|
-
componentName: 'Button',
|
|
99
|
-
variantCount: 3,
|
|
100
|
-
filePath: 'src/components/Button/Button.stories.tsx',
|
|
101
|
-
config: DEFAULTS,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
it('keeps a normal component', () => {
|
|
105
|
-
const result = checkStoryExclusion({
|
|
106
|
-
...base,
|
|
107
|
-
storybookTitle: 'Components/Actions/Button',
|
|
108
|
-
});
|
|
109
|
-
expect(result.excluded).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('excludes deprecated titles', () => {
|
|
113
|
-
const result = checkStoryExclusion({
|
|
114
|
-
...base,
|
|
115
|
-
storybookTitle: 'Deprecated/OldButton',
|
|
116
|
-
});
|
|
117
|
-
expect(result).toEqual<ExclusionResult>({
|
|
118
|
-
excluded: true,
|
|
119
|
-
reason: 'deprecated',
|
|
120
|
-
detail: expect.stringContaining('Deprecated'),
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('excludes titles ending with /test', () => {
|
|
125
|
-
const result = checkStoryExclusion({
|
|
126
|
-
...base,
|
|
127
|
-
storybookTitle: 'Components/Button/Test',
|
|
128
|
-
});
|
|
129
|
-
expect(result.excluded).toBe(true);
|
|
130
|
-
expect(result.reason).toBe('test-story');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('excludes titles ending with /tests', () => {
|
|
134
|
-
const result = checkStoryExclusion({
|
|
135
|
-
...base,
|
|
136
|
-
storybookTitle: 'Components/Button/Tests',
|
|
137
|
-
});
|
|
138
|
-
expect(result.excluded).toBe(true);
|
|
139
|
-
expect(result.reason).toBe('test-story');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('excludes *.test.stories.* file paths', () => {
|
|
143
|
-
const result = checkStoryExclusion({
|
|
144
|
-
...base,
|
|
145
|
-
filePath: 'src/components/Button/Button.test.stories.tsx',
|
|
146
|
-
});
|
|
147
|
-
expect(result.excluded).toBe(true);
|
|
148
|
-
expect(result.reason).toBe('test-story');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('excludes SVG icons by displayName', () => {
|
|
152
|
-
const result = checkStoryExclusion({
|
|
153
|
-
...base,
|
|
154
|
-
componentName: 'HomeLarge',
|
|
155
|
-
componentDisplayName: 'SvgHomeLarge',
|
|
156
|
-
});
|
|
157
|
-
expect(result.excluded).toBe(true);
|
|
158
|
-
expect(result.reason).toBe('svg-icon');
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('excludes SVG icons by function name', () => {
|
|
162
|
-
const result = checkStoryExclusion({
|
|
163
|
-
...base,
|
|
164
|
-
componentName: 'HomeLarge',
|
|
165
|
-
componentFunctionName: 'SvgHomeLarge',
|
|
166
|
-
});
|
|
167
|
-
expect(result.excluded).toBe(true);
|
|
168
|
-
expect(result.reason).toBe('svg-icon');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('excludes SVG icons by component name', () => {
|
|
172
|
-
const result = checkStoryExclusion({
|
|
173
|
-
...base,
|
|
174
|
-
componentName: 'SvgArrowRight',
|
|
175
|
-
});
|
|
176
|
-
expect(result.excluded).toBe(true);
|
|
177
|
-
expect(result.reason).toBe('svg-icon');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('excludes hidden-tagged stories', () => {
|
|
181
|
-
const result = checkStoryExclusion({
|
|
182
|
-
...base,
|
|
183
|
-
tags: ['hidden'],
|
|
184
|
-
});
|
|
185
|
-
expect(result.excluded).toBe(true);
|
|
186
|
-
expect(result.reason).toBe('tag-excluded');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('excludes internal-tagged stories', () => {
|
|
190
|
-
const result = checkStoryExclusion({
|
|
191
|
-
...base,
|
|
192
|
-
tags: ['internal'],
|
|
193
|
-
});
|
|
194
|
-
expect(result.excluded).toBe(true);
|
|
195
|
-
expect(result.reason).toBe('tag-excluded');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('excludes no-fragment-tagged stories', () => {
|
|
199
|
-
const result = checkStoryExclusion({
|
|
200
|
-
...base,
|
|
201
|
-
tags: ['no-fragment'],
|
|
202
|
-
});
|
|
203
|
-
expect(result.excluded).toBe(true);
|
|
204
|
-
expect(result.reason).toBe('tag-excluded');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('excludes stories with zero variants', () => {
|
|
208
|
-
const result = checkStoryExclusion({
|
|
209
|
-
...base,
|
|
210
|
-
variantCount: 0,
|
|
211
|
-
});
|
|
212
|
-
expect(result.excluded).toBe(true);
|
|
213
|
-
expect(result.reason).toBe('empty-variants');
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
// Config precedence
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
describe('config precedence', () => {
|
|
222
|
-
it('force-includes a component despite SVG icon rule', () => {
|
|
223
|
-
const result = checkStoryExclusion({
|
|
224
|
-
componentName: 'SvgHomeLarge',
|
|
225
|
-
variantCount: 1,
|
|
226
|
-
filePath: 'src/icons/SvgHomeLarge.stories.tsx',
|
|
227
|
-
config: { ...DEFAULTS, include: ['SvgHomeLarge'] },
|
|
228
|
-
});
|
|
229
|
-
expect(result.excluded).toBe(false);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('force-includes a component despite deprecated rule', () => {
|
|
233
|
-
const result = checkStoryExclusion({
|
|
234
|
-
componentName: 'OldButton',
|
|
235
|
-
storybookTitle: 'Deprecated/OldButton',
|
|
236
|
-
variantCount: 1,
|
|
237
|
-
filePath: 'src/components/OldButton.stories.tsx',
|
|
238
|
-
config: { ...DEFAULTS, include: ['OldButton'] },
|
|
239
|
-
});
|
|
240
|
-
expect(result.excluded).toBe(false);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('keeps deprecated when excludeDeprecated is false', () => {
|
|
244
|
-
const result = checkStoryExclusion({
|
|
245
|
-
componentName: 'OldButton',
|
|
246
|
-
storybookTitle: 'Deprecated/OldButton',
|
|
247
|
-
variantCount: 1,
|
|
248
|
-
filePath: 'src/components/OldButton.stories.tsx',
|
|
249
|
-
config: { ...DEFAULTS, excludeDeprecated: false },
|
|
250
|
-
});
|
|
251
|
-
expect(result.excluded).toBe(false);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('keeps test stories when excludeTests is false', () => {
|
|
255
|
-
const result = checkStoryExclusion({
|
|
256
|
-
componentName: 'Button',
|
|
257
|
-
storybookTitle: 'Components/Button/Tests',
|
|
258
|
-
variantCount: 1,
|
|
259
|
-
filePath: 'src/components/Button.stories.tsx',
|
|
260
|
-
config: { ...DEFAULTS, excludeTests: false },
|
|
261
|
-
});
|
|
262
|
-
expect(result.excluded).toBe(false);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('keeps SVG icons when excludeSvgIcons is false', () => {
|
|
266
|
-
const result = checkStoryExclusion({
|
|
267
|
-
componentName: 'SvgArrowRight',
|
|
268
|
-
variantCount: 1,
|
|
269
|
-
filePath: 'src/icons/SvgArrowRight.stories.tsx',
|
|
270
|
-
config: { ...DEFAULTS, excludeSvgIcons: false },
|
|
271
|
-
});
|
|
272
|
-
expect(result.excluded).toBe(false);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('config-excludes a component by name', () => {
|
|
276
|
-
const result = checkStoryExclusion({
|
|
277
|
-
componentName: 'JCB',
|
|
278
|
-
variantCount: 1,
|
|
279
|
-
filePath: 'src/components/JCB.stories.tsx',
|
|
280
|
-
config: { ...DEFAULTS, exclude: ['JCB'] },
|
|
281
|
-
});
|
|
282
|
-
expect(result.excluded).toBe(true);
|
|
283
|
-
expect(result.reason).toBe('config-excluded');
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('config-excludes with wildcard pattern', () => {
|
|
287
|
-
const result = checkStoryExclusion({
|
|
288
|
-
componentName: 'SvgHomeLarge',
|
|
289
|
-
variantCount: 1,
|
|
290
|
-
filePath: 'src/icons/SvgHomeLarge.stories.tsx',
|
|
291
|
-
config: { ...DEFAULTS, exclude: ['Svg*'] },
|
|
292
|
-
});
|
|
293
|
-
expect(result.excluded).toBe(true);
|
|
294
|
-
expect(result.reason).toBe('config-excluded');
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('include wins over exclude', () => {
|
|
298
|
-
const result = checkStoryExclusion({
|
|
299
|
-
componentName: 'SvgHomeLarge',
|
|
300
|
-
variantCount: 1,
|
|
301
|
-
filePath: 'src/icons/SvgHomeLarge.stories.tsx',
|
|
302
|
-
config: { ...DEFAULTS, include: ['SvgHomeLarge'], exclude: ['Svg*'] },
|
|
303
|
-
});
|
|
304
|
-
expect(result.excluded).toBe(false);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// ---------------------------------------------------------------------------
|
|
309
|
-
// isForceIncluded / isConfigExcluded helpers
|
|
310
|
-
// ---------------------------------------------------------------------------
|
|
311
|
-
|
|
312
|
-
describe('isForceIncluded', () => {
|
|
313
|
-
it('returns false with no include patterns', () => {
|
|
314
|
-
expect(isForceIncluded('Button', {})).toBe(false);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('matches exact name', () => {
|
|
318
|
-
expect(isForceIncluded('Button', { include: ['Button'] })).toBe(true);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('matches wildcard prefix', () => {
|
|
322
|
-
expect(isForceIncluded('SvgArrowRight', { include: ['Svg*'] })).toBe(true);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it('does not match non-matching pattern', () => {
|
|
326
|
-
expect(isForceIncluded('Button', { include: ['Svg*'] })).toBe(false);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('matches suffix wildcard', () => {
|
|
330
|
-
expect(isForceIncluded('BigButton', { include: ['*Button'] })).toBe(true);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('matches contains wildcard', () => {
|
|
334
|
-
expect(isForceIncluded('MyButtonGroup', { include: ['*Button*'] })).toBe(true);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
describe('isConfigExcluded', () => {
|
|
339
|
-
it('returns false with no exclude patterns', () => {
|
|
340
|
-
expect(isConfigExcluded('Button', {})).toBe(false);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it('matches exact name', () => {
|
|
344
|
-
expect(isConfigExcluded('JCB', { exclude: ['JCB'] })).toBe(true);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('matches wildcard', () => {
|
|
348
|
-
expect(isConfigExcluded('SvgHomeLarge', { exclude: ['Svg*'] })).toBe(true);
|
|
349
|
-
});
|
|
350
|
-
});
|