@usefragments/core 1.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.
Files changed (228) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-243QYRUF.js +2145 -0
  3. package/dist/chunk-243QYRUF.js.map +1 -0
  4. package/dist/chunk-3LLRNCPX.js +111 -0
  5. package/dist/chunk-3LLRNCPX.js.map +1 -0
  6. package/dist/chunk-EIYNNS77.js +48 -0
  7. package/dist/chunk-EIYNNS77.js.map +1 -0
  8. package/dist/chunk-JNBFJ34I.js +326 -0
  9. package/dist/chunk-JNBFJ34I.js.map +1 -0
  10. package/dist/chunk-R3X7UBHU.js +228 -0
  11. package/dist/chunk-R3X7UBHU.js.map +1 -0
  12. package/dist/chunk-SH4KPIYH.js +393 -0
  13. package/dist/chunk-SH4KPIYH.js.map +1 -0
  14. package/dist/chunk-V4VQB57N.js +56 -0
  15. package/dist/chunk-V4VQB57N.js.map +1 -0
  16. package/dist/chunk-X34IA4LR.js +319 -0
  17. package/dist/chunk-X34IA4LR.js.map +1 -0
  18. package/dist/chunk-YF65VYRY.js +493 -0
  19. package/dist/chunk-YF65VYRY.js.map +1 -0
  20. package/dist/codes/index.d.ts +40 -0
  21. package/dist/codes/index.js +23 -0
  22. package/dist/codes/index.js.map +1 -0
  23. package/dist/compiled-types/index.d.ts +5 -0
  24. package/dist/compiled-types/index.js +1 -0
  25. package/dist/compiled-types/index.js.map +1 -0
  26. package/dist/generate/index.d.ts +47 -0
  27. package/dist/generate/index.js +11 -0
  28. package/dist/generate/index.js.map +1 -0
  29. package/dist/governance-BOa3KyiJ.d.ts +3279 -0
  30. package/dist/graph/index.d.ts +66 -0
  31. package/dist/graph/index.js +17 -0
  32. package/dist/graph/index.js.map +1 -0
  33. package/dist/index.d.ts +11388 -0
  34. package/dist/index.js +8711 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/package-identity.d.ts +42 -0
  37. package/dist/package-identity.js +23 -0
  38. package/dist/package-identity.js.map +1 -0
  39. package/dist/preview/index.d.ts +484 -0
  40. package/dist/preview/index.js +1129 -0
  41. package/dist/preview/index.js.map +1 -0
  42. package/dist/react-types.d.ts +22 -0
  43. package/dist/react-types.js +1 -0
  44. package/dist/react-types.js.map +1 -0
  45. package/dist/registry.d.ts +2367 -0
  46. package/dist/registry.js +62 -0
  47. package/dist/registry.js.map +1 -0
  48. package/dist/schemas/index.d.ts +2584 -0
  49. package/dist/schemas/index.js +55 -0
  50. package/dist/schemas/index.js.map +1 -0
  51. package/dist/severity.d.ts +18 -0
  52. package/dist/severity.js +27 -0
  53. package/dist/severity.js.map +1 -0
  54. package/dist/test-utils.d.ts +14 -0
  55. package/dist/test-utils.js +63 -0
  56. package/dist/test-utils.js.map +1 -0
  57. package/dist/topology/index.d.ts +48 -0
  58. package/dist/topology/index.js +7 -0
  59. package/dist/topology/index.js.map +1 -0
  60. package/dist/types-xJ2xyp_G.d.ts +119 -0
  61. package/package.json +120 -0
  62. package/src/__tests__/contract-parser.test.ts +614 -0
  63. package/src/__tests__/dtcg-generators.test.ts +439 -0
  64. package/src/__tests__/dtcg-parser.test.ts +884 -0
  65. package/src/__tests__/preview-runtime.test.tsx +111 -0
  66. package/src/__tests__/schema.test.ts +31 -0
  67. package/src/__tests__/severity.test.ts +34 -0
  68. package/src/__tests__/style-comparison.test.ts +662 -0
  69. package/src/__tests__/tokens-compat.test.ts +51 -0
  70. package/src/agent-format.test.ts +105 -0
  71. package/src/agent-format.ts +141 -0
  72. package/src/bundle.ts +149 -0
  73. package/src/codes/__tests__/codes.test.ts +73 -0
  74. package/src/codes/codes.ts +463 -0
  75. package/src/codes/index.ts +7 -0
  76. package/src/codes/source-finding-bridge.ts +113 -0
  77. package/src/compiled-types/index.ts +293 -0
  78. package/src/component-contract.ts +231 -0
  79. package/src/component-discovery.ts +80 -0
  80. package/src/composition.test.ts +262 -0
  81. package/src/composition.ts +318 -0
  82. package/src/config-paths.test.ts +78 -0
  83. package/src/config-paths.ts +47 -0
  84. package/src/config.ts +18 -0
  85. package/src/conform-prove.test.ts +274 -0
  86. package/src/conform-prove.ts +326 -0
  87. package/src/conform.ts +160 -0
  88. package/src/constants.ts +114 -0
  89. package/src/context.ts +2 -0
  90. package/src/contract/fixture.ts +110 -0
  91. package/src/contract/hash.test.ts +86 -0
  92. package/src/contract/hash.ts +195 -0
  93. package/src/contract/index.ts +43 -0
  94. package/src/contract/preimage.test.ts +254 -0
  95. package/src/contract/preimage.ts +365 -0
  96. package/src/contract/stamp.test.ts +100 -0
  97. package/src/contract/stamp.ts +105 -0
  98. package/src/contract-parser.ts +141 -0
  99. package/src/defineFragment.test.ts +257 -0
  100. package/src/defineFragment.ts +293 -0
  101. package/src/dtcg-generators.ts +324 -0
  102. package/src/dtcg-parser.ts +1 -0
  103. package/src/dtcg.ts +237 -0
  104. package/src/facts/__snapshots__/facts.test.ts.snap +124 -0
  105. package/src/facts/builders.ts +835 -0
  106. package/src/facts/compile.ts +422 -0
  107. package/src/facts/fact-index.ts +394 -0
  108. package/src/facts/facts.test.ts +494 -0
  109. package/src/facts/ids.ts +125 -0
  110. package/src/facts/index.ts +119 -0
  111. package/src/facts/types.ts +614 -0
  112. package/src/figma.ts +263 -0
  113. package/src/fragment-types.ts +214 -0
  114. package/src/generate/generate.test.ts +594 -0
  115. package/src/generate/index.ts +438 -0
  116. package/src/governance-integrity.test.ts +186 -0
  117. package/src/governance-integrity.ts +282 -0
  118. package/src/governance.test.ts +300 -0
  119. package/src/governance.ts +605 -0
  120. package/src/governance.type-test.ts +75 -0
  121. package/src/graph/engine.ts +446 -0
  122. package/src/graph/graph.test.ts +746 -0
  123. package/src/graph/index.ts +32 -0
  124. package/src/graph/serialization.ts +55 -0
  125. package/src/graph/types.ts +178 -0
  126. package/src/index.ts +891 -0
  127. package/src/local-canonical.ts +102 -0
  128. package/src/mcp-snapshot.ts +222 -0
  129. package/src/package-identity-match.ts +33 -0
  130. package/src/package-identity.generated.ts +233 -0
  131. package/src/package-identity.test.ts +99 -0
  132. package/src/package-identity.ts +157 -0
  133. package/src/performance-presets.ts +142 -0
  134. package/src/preview/comparison-artifact.test.ts +228 -0
  135. package/src/preview/comparison-artifact.ts +181 -0
  136. package/src/preview/comparison-context.ts +93 -0
  137. package/src/preview/comparison-evidence.ts +112 -0
  138. package/src/preview/comparison-format.ts +21 -0
  139. package/src/preview/comparison-presentation.ts +187 -0
  140. package/src/preview/comparison-references.test.ts +430 -0
  141. package/src/preview/comparison-references.ts +72 -0
  142. package/src/preview/comparison-score.ts +59 -0
  143. package/src/preview/comparison-styles.ts +197 -0
  144. package/src/preview/comparison.test.ts +559 -0
  145. package/src/preview/comparison.ts +488 -0
  146. package/src/preview/index.ts +10 -0
  147. package/src/preview/types.ts +309 -0
  148. package/src/preview/validation.test.ts +290 -0
  149. package/src/preview/validation.ts +356 -0
  150. package/src/preview-runtime.tsx +144 -0
  151. package/src/raw-html-canonical.test.ts +41 -0
  152. package/src/raw-html-canonical.ts +179 -0
  153. package/src/react-types.ts +21 -0
  154. package/src/registry-install-plan.ts +344 -0
  155. package/src/registry.test.ts +245 -0
  156. package/src/registry.ts +369 -0
  157. package/src/rules/a11y-required-accessible-name.ts +93 -0
  158. package/src/rules/color-math.test.ts +72 -0
  159. package/src/rules/color-math.ts +192 -0
  160. package/src/rules/components-forbidden-prop-value.ts +108 -0
  161. package/src/rules/components-prefer-library.ts +943 -0
  162. package/src/rules/components-unknown-prop.ts +94 -0
  163. package/src/rules/composition-pattern-schema.ts +125 -0
  164. package/src/rules/composition-pattern.test.ts +320 -0
  165. package/src/rules/composition-pattern.ts +271 -0
  166. package/src/rules/emit-gate.test.ts +42 -0
  167. package/src/rules/emit-gate.ts +60 -0
  168. package/src/rules/finding.ts +54 -0
  169. package/src/rules/index.ts +235 -0
  170. package/src/rules/jsx-preferred-component.ts +68 -0
  171. package/src/rules/jsx-preferred-import-path.ts +67 -0
  172. package/src/rules/presets.ts +57 -0
  173. package/src/rules/props-invalid-value.ts +115 -0
  174. package/src/rules/rules.test.ts +3636 -0
  175. package/src/rules/spacing-resolution.ts +184 -0
  176. package/src/rules/styles-no-raw-color.ts +329 -0
  177. package/src/rules/styles-no-raw-dimensions.ts +357 -0
  178. package/src/rules/styles-no-raw-spacing.ts +236 -0
  179. package/src/rules/styles-no-raw-typography.ts +237 -0
  180. package/src/rules/tailwind-arbitrary-color.test.ts +128 -0
  181. package/src/rules/tailwind-arbitrary-color.ts +93 -0
  182. package/src/rules/tailwind-arbitrary-spacing.test.ts +157 -0
  183. package/src/rules/tailwind-arbitrary-spacing.ts +120 -0
  184. package/src/rules/tailwind-forbidden-palette.test.ts +116 -0
  185. package/src/rules/tailwind-forbidden-palette.ts +107 -0
  186. package/src/rules/tailwind-off-scale-spacing-token.test.ts +100 -0
  187. package/src/rules/tailwind-off-scale-spacing-token.ts +108 -0
  188. package/src/rules/tailwind-raw-color-via-token.test.ts +108 -0
  189. package/src/rules/tailwind-raw-color-via-token.ts +82 -0
  190. package/src/rules/tailwind-rule-utils.ts +106 -0
  191. package/src/rules/tailwind-unknown-class.test.ts +157 -0
  192. package/src/rules/tailwind-unknown-class.ts +121 -0
  193. package/src/rules/taxonomy.test.ts +238 -0
  194. package/src/rules/theme-no-theme-coupled-literal.ts +132 -0
  195. package/src/rules/tiers.ts +111 -0
  196. package/src/rules/tokens-css-vars-must-be-defined.test.ts +244 -0
  197. package/src/rules/tokens-css-vars-must-be-defined.ts +156 -0
  198. package/src/rules/tokens-require-dual-fallback.ts +78 -0
  199. package/src/rules/types.ts +27 -0
  200. package/src/rules/utils.test.ts +110 -0
  201. package/src/rules/utils.ts +599 -0
  202. package/src/schema.ts +427 -0
  203. package/src/schemas/index.ts +275 -0
  204. package/src/schemas/schemas.test.ts +124 -0
  205. package/src/severity.ts +54 -0
  206. package/src/storyAdapter.test.ts +571 -0
  207. package/src/storyAdapter.ts +761 -0
  208. package/src/storyFilters.test.ts +350 -0
  209. package/src/storyFilters.ts +253 -0
  210. package/src/storybook-csf.ts +11 -0
  211. package/src/style-comparison.ts +423 -0
  212. package/src/test-utils.ts +68 -0
  213. package/src/token-parser.ts +2 -0
  214. package/src/token-types.ts +287 -0
  215. package/src/tokens/categories.ts +131 -0
  216. package/src/tokens/color.ts +193 -0
  217. package/src/tokens/design-token-parser.test.ts +159 -0
  218. package/src/tokens/design-token-parser.ts +402 -0
  219. package/src/tokens/dtcg.ts +428 -0
  220. package/src/tokens/index.ts +130 -0
  221. package/src/tokens/lookup.ts +19 -0
  222. package/src/tokens/scss.ts +171 -0
  223. package/src/tokens/tailwind-v4.ts +187 -0
  224. package/src/tokens/types.ts +43 -0
  225. package/src/topology/index.ts +9 -0
  226. package/src/topology/resolve-area.test.ts +174 -0
  227. package/src/topology/resolve-area.ts +215 -0
  228. package/src/types.ts +1057 -0
@@ -0,0 +1,594 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ filterPlaceholders,
4
+ generateContext,
5
+ type ContextOptions,
6
+ } from "./index.js";
7
+ import { makeCompiledFragment, makeCompiledBlock } from "../test-utils.js";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // filterPlaceholders
11
+ // ---------------------------------------------------------------------------
12
+
13
+ describe("filterPlaceholders", () => {
14
+ it("returns empty array for undefined input", () => {
15
+ expect(filterPlaceholders(undefined)).toEqual([]);
16
+ });
17
+
18
+ it("returns empty array for empty array input", () => {
19
+ expect(filterPlaceholders([])).toEqual([]);
20
+ });
21
+
22
+ it("filters text matching pattern 1: '<Word> component is needed'", () => {
23
+ expect(filterPlaceholders(["Button component is needed"])).toEqual([]);
24
+ });
25
+
26
+ it("filters text matching pattern 2: 'Alternative component is more appropriate'", () => {
27
+ expect(
28
+ filterPlaceholders(["Alternative component is more appropriate"])
29
+ ).toEqual([]);
30
+ });
31
+
32
+ it("filters text matching pattern 3: 'Use <Word> when you need'", () => {
33
+ expect(filterPlaceholders(["Use Button when you need"])).toEqual([]);
34
+ });
35
+
36
+ it("keeps real usage text", () => {
37
+ const items = ["Triggering an action", "Submitting a form"];
38
+ expect(filterPlaceholders(items)).toEqual(items);
39
+ });
40
+
41
+ it("keeps text that partially matches but does not fully match patterns", () => {
42
+ const items = [
43
+ "The Button component is needed for this",
44
+ "Use wisely",
45
+ ];
46
+ expect(filterPlaceholders(items)).toEqual(items);
47
+ });
48
+
49
+ it("filters placeholder text with leading/trailing whitespace", () => {
50
+ expect(
51
+ filterPlaceholders([" Button component is needed "])
52
+ ).toEqual([]);
53
+ });
54
+ });
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Helpers
58
+ // ---------------------------------------------------------------------------
59
+
60
+ function makeFragmentPair() {
61
+ const button = makeCompiledFragment();
62
+ const input = makeCompiledFragment({
63
+ meta: {
64
+ name: "Input",
65
+ description: "A text input field",
66
+ category: "forms",
67
+ status: "stable",
68
+ tags: ["form"],
69
+ },
70
+ usage: {
71
+ when: ["Collecting text from user"],
72
+ whenNot: ["Selecting from predefined options"],
73
+ },
74
+ props: {
75
+ placeholder: {
76
+ type: "string",
77
+ description: "Placeholder text",
78
+ },
79
+ },
80
+ variants: [{ name: "Default", description: "Default input", code: "<Input />" }],
81
+ });
82
+ return [button, input];
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // generateContext — markdown format
87
+ // ---------------------------------------------------------------------------
88
+
89
+ describe("generateContext — markdown format", () => {
90
+ const fragments = makeFragmentPair();
91
+
92
+ it("contains '# Design System Reference' header", () => {
93
+ const { content } = generateContext(fragments);
94
+ expect(content).toContain("# Design System Reference");
95
+ });
96
+
97
+ it("contains '## Quick Reference' table", () => {
98
+ const { content } = generateContext(fragments);
99
+ expect(content).toContain("## Quick Reference");
100
+ expect(content).toContain("| Component | Category | Use For |");
101
+ });
102
+
103
+ it("table rows are sorted by category then name", () => {
104
+ const { content } = generateContext(fragments);
105
+ const tableRows = content
106
+ .split("\n")
107
+ .filter((l) => l.startsWith("| ") && !l.startsWith("| Component") && !l.startsWith("|---"));
108
+ expect(tableRows[0]).toContain("Button");
109
+ expect(tableRows[0]).toContain("actions");
110
+ expect(tableRows[1]).toContain("Input");
111
+ expect(tableRows[1]).toContain("forms");
112
+ });
113
+
114
+ it("non-compact output contains '## Components' section", () => {
115
+ const { content } = generateContext(fragments);
116
+ expect(content).toContain("## Components");
117
+ });
118
+
119
+ it("component sections have ### heading with name", () => {
120
+ const { content } = generateContext(fragments);
121
+ expect(content).toContain("### Button");
122
+ expect(content).toContain("### Input");
123
+ });
124
+
125
+ it("shows category and status", () => {
126
+ const { content } = generateContext(fragments);
127
+ expect(content).toContain("**Category:** actions");
128
+ expect(content).toContain("**Status:** stable");
129
+ });
130
+
131
+ it("shows description", () => {
132
+ const { content } = generateContext(fragments);
133
+ expect(content).toContain("A clickable button element");
134
+ });
135
+
136
+ it("shows 'When to use' and 'When NOT to use' lists", () => {
137
+ const { content } = generateContext(fragments);
138
+ expect(content).toContain("**When to use:**");
139
+ expect(content).toContain("- Triggering an action");
140
+ expect(content).toContain("**When NOT to use:**");
141
+ expect(content).toContain("- Navigating to another page");
142
+ });
143
+
144
+ it("shows props with formatted types", () => {
145
+ const { content } = generateContext(fragments);
146
+ expect(content).toContain('`variant`: "primary" | "secondary" | "ghost"');
147
+ expect(content).toContain('`size`: "sm" | "md" | "lg"');
148
+ expect(content).toContain("`disabled`:");
149
+ });
150
+
151
+ it("shows variant names", () => {
152
+ const { content } = generateContext(fragments);
153
+ expect(content).toContain("**Variants:** Primary, Secondary");
154
+ });
155
+
156
+ it("with code option shows code blocks for variants", () => {
157
+ const { content } = generateContext(fragments, {
158
+ include: { code: true },
159
+ });
160
+ expect(content).toContain("```tsx");
161
+ expect(content).toContain("<Button>Click</Button>");
162
+ });
163
+
164
+ it("with relations option shows related components", () => {
165
+ const seg = makeCompiledFragment({
166
+ relations: [
167
+ { component: "Link", relationship: "alternative", note: "For navigation" },
168
+ ],
169
+ });
170
+ const { content } = generateContext([seg], {
171
+ include: { relations: true },
172
+ });
173
+ expect(content).toContain("**Related:**");
174
+ expect(content).toContain("- Link (alternative): For navigation");
175
+ });
176
+
177
+ it("includes blocks section when blocks provided", () => {
178
+ const block = makeCompiledBlock();
179
+ const { content } = generateContext(fragments, {}, [block]);
180
+ expect(content).toContain("## Blocks");
181
+ expect(content).toContain("Composition patterns");
182
+ });
183
+
184
+ it("block has name, description, category, components, and code", () => {
185
+ const block = makeCompiledBlock();
186
+ const { content } = generateContext(fragments, {}, [block]);
187
+ expect(content).toContain("### Login Form");
188
+ expect(content).toContain("A standard login form");
189
+ expect(content).toContain("**Category:** authentication");
190
+ expect(content).toContain("**Components:** Input, Button");
191
+ expect(content).toContain("**Tags:** auth, form");
192
+ expect(content).toContain('<Input label="Email" />');
193
+ });
194
+ });
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // generateContext — markdown compact
198
+ // ---------------------------------------------------------------------------
199
+
200
+ describe("generateContext — markdown compact", () => {
201
+ const fragments = makeFragmentPair();
202
+
203
+ it("contains Quick Reference table", () => {
204
+ const { content } = generateContext(fragments, { compact: true });
205
+ expect(content).toContain("## Quick Reference");
206
+ expect(content).toContain("| Button |");
207
+ });
208
+
209
+ it("does NOT contain '## Components' section", () => {
210
+ const { content } = generateContext(fragments, { compact: true });
211
+ expect(content).not.toContain("## Components");
212
+ });
213
+
214
+ it("tokenEstimate equals Math.ceil(content.length / 4)", () => {
215
+ const result = generateContext(fragments, { compact: true });
216
+ expect(result.tokenEstimate).toBe(Math.ceil(result.content.length / 4));
217
+ });
218
+ });
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // generateContext — markdown include options
222
+ // ---------------------------------------------------------------------------
223
+
224
+ describe("generateContext — markdown include options", () => {
225
+ const fragments = makeFragmentPair();
226
+
227
+ it("include.props=false omits props listing", () => {
228
+ const { content } = generateContext(fragments, {
229
+ include: { props: false },
230
+ });
231
+ expect(content).not.toContain("**Props:**");
232
+ });
233
+
234
+ it("include.variants=false omits variants listing", () => {
235
+ const { content } = generateContext(fragments, {
236
+ include: { variants: false },
237
+ });
238
+ expect(content).not.toContain("**Variants:**");
239
+ });
240
+
241
+ it("include.usage=false omits when/whenNot lists", () => {
242
+ const { content } = generateContext(fragments, {
243
+ include: { usage: false },
244
+ });
245
+ expect(content).not.toContain("**When to use:**");
246
+ expect(content).not.toContain("**When NOT to use:**");
247
+ });
248
+ });
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // generateContext — JSON format
252
+ // ---------------------------------------------------------------------------
253
+
254
+ describe("generateContext — JSON format", () => {
255
+ const fragments = makeFragmentPair();
256
+ const opts: ContextOptions = { format: "json" };
257
+
258
+ it("returns valid JSON", () => {
259
+ const { content } = generateContext(fragments, opts);
260
+ expect(() => JSON.parse(content)).not.toThrow();
261
+ });
262
+
263
+ it("has version '1.0' and a valid generatedAt ISO string", () => {
264
+ const { content } = generateContext(fragments, opts);
265
+ const json = JSON.parse(content);
266
+ expect(json.version).toBe("1.0");
267
+ expect(json.generatedAt).toMatch(
268
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
269
+ );
270
+ });
271
+
272
+ it("has summary with totalComponents and categories", () => {
273
+ const { content } = generateContext(fragments, opts);
274
+ const json = JSON.parse(content);
275
+ expect(json.summary.totalComponents).toBe(2);
276
+ expect(json.summary.categories).toEqual(["actions", "forms"]);
277
+ });
278
+
279
+ it("has components keyed by name", () => {
280
+ const { content } = generateContext(fragments, opts);
281
+ const json = JSON.parse(content);
282
+ expect(json.components).toHaveProperty("Button");
283
+ expect(json.components).toHaveProperty("Input");
284
+ });
285
+
286
+ it("component has category, description, and status", () => {
287
+ const { content } = generateContext(fragments, opts);
288
+ const json = JSON.parse(content);
289
+ const btn = json.components.Button;
290
+ expect(btn.category).toBe("actions");
291
+ expect(btn.description).toBe("A clickable button element");
292
+ expect(btn.status).toBe("stable");
293
+ });
294
+
295
+ it("has whenToUse and whenNotToUse arrays", () => {
296
+ const { content } = generateContext(fragments, opts);
297
+ const json = JSON.parse(content);
298
+ const btn = json.components.Button;
299
+ expect(btn.whenToUse).toEqual(["Triggering an action", "Submitting a form"]);
300
+ expect(btn.whenNotToUse).toEqual(["Navigating to another page"]);
301
+ });
302
+
303
+ it("has props with type, description, required, and default", () => {
304
+ const { content } = generateContext(fragments, opts);
305
+ const json = JSON.parse(content);
306
+ const variantProp = json.components.Button.props.variant;
307
+ expect(variantProp.type).toBe('"primary" | "secondary" | "ghost"');
308
+ expect(variantProp.description).toBe("Visual style variant");
309
+ expect(variantProp.default).toBe("primary");
310
+ });
311
+
312
+ it("has variants as string array", () => {
313
+ const { content } = generateContext(fragments, opts);
314
+ const json = JSON.parse(content);
315
+ expect(json.components.Button.variants).toEqual(["Primary", "Secondary"]);
316
+ });
317
+
318
+ it("has blocks section when blocks provided", () => {
319
+ const block = makeCompiledBlock();
320
+ const { content } = generateContext(fragments, opts, [block]);
321
+ const json = JSON.parse(content);
322
+ expect(json.blocks).toHaveProperty("Login Form");
323
+ expect(json.summary.totalBlocks).toBe(1);
324
+ expect(json.blocks["Login Form"].components).toEqual(["Input", "Button"]);
325
+ });
326
+ });
327
+
328
+ // ---------------------------------------------------------------------------
329
+ // generateContext — JSON compact
330
+ // ---------------------------------------------------------------------------
331
+
332
+ describe("generateContext — JSON compact", () => {
333
+ it("omits whenToUse, props, and variants from components", () => {
334
+ const fragments = makeFragmentPair();
335
+ const { content } = generateContext(fragments, {
336
+ format: "json",
337
+ compact: true,
338
+ });
339
+ const json = JSON.parse(content);
340
+ const btn = json.components.Button;
341
+ expect(btn.whenToUse).toBeUndefined();
342
+ expect(btn.props).toBeUndefined();
343
+ expect(btn.variants).toBeUndefined();
344
+ // Still has basic info
345
+ expect(btn.category).toBe("actions");
346
+ expect(btn.description).toBe("A clickable button element");
347
+ });
348
+ });
349
+
350
+ // ---------------------------------------------------------------------------
351
+ // Indirect testing (formatPropType, truncate, estimateTokens)
352
+ // ---------------------------------------------------------------------------
353
+
354
+ describe("Indirect testing", () => {
355
+ it("enum prop type formatted as '\"val1\" | \"val2\"'", () => {
356
+ const seg = makeCompiledFragment({
357
+ props: {
358
+ color: {
359
+ type: "enum",
360
+ values: ["red", "blue"],
361
+ description: "Color",
362
+ },
363
+ },
364
+ });
365
+ const { content } = generateContext([seg]);
366
+ expect(content).toContain('"red" | "blue"');
367
+ });
368
+
369
+ it("prop with default formatted as 'type (default: value)'", () => {
370
+ const seg = makeCompiledFragment({
371
+ props: {
372
+ disabled: {
373
+ type: "boolean",
374
+ default: false,
375
+ description: "Disabled state",
376
+ },
377
+ },
378
+ });
379
+ const { content } = generateContext([seg]);
380
+ expect(content).toContain("boolean (default: false)");
381
+ });
382
+
383
+ it("simple prop type returned as-is", () => {
384
+ const seg = makeCompiledFragment({
385
+ props: {
386
+ label: {
387
+ type: "string",
388
+ description: "Label text",
389
+ },
390
+ },
391
+ });
392
+ const { content } = generateContext([seg]);
393
+ expect(content).toContain("`label`: string");
394
+ });
395
+
396
+ it("long useFor text truncated with '...' at 50 chars", () => {
397
+ const longText =
398
+ "This is a very long description that definitely exceeds fifty characters in length";
399
+ const seg = makeCompiledFragment({
400
+ usage: { when: [longText], whenNot: [] },
401
+ });
402
+ const { content } = generateContext([seg]);
403
+ // Quick Reference table should contain truncated text
404
+ const tableLines = content
405
+ .split("\n")
406
+ .filter((l) => l.startsWith("| ") && l.includes("Button"));
407
+ expect(tableLines[0].length).toBeLessThan(
408
+ tableLines[0].indexOf("| ", 2) + longText.length
409
+ );
410
+ expect(tableLines[0]).toContain("...");
411
+ });
412
+
413
+ it("tokenEstimate approximately equals Math.ceil(content.length / 4)", () => {
414
+ const seg = makeCompiledFragment();
415
+ const result = generateContext([seg]);
416
+ expect(result.tokenEstimate).toBe(Math.ceil(result.content.length / 4));
417
+ });
418
+ });
419
+
420
+ // ---------------------------------------------------------------------------
421
+ // Composition and contract data in markdown
422
+ // ---------------------------------------------------------------------------
423
+
424
+ describe("generateContext — composition and contract", () => {
425
+ it("includes composition pattern and sub-components in markdown", () => {
426
+ const seg = makeCompiledFragment({
427
+ ai: {
428
+ compositionPattern: "compound",
429
+ subComponents: ["Header", "Body", "Footer"],
430
+ requiredChildren: ["Body"],
431
+ commonPatterns: ["<Card><Card.Body>{content}</Card.Body></Card>"],
432
+ },
433
+ });
434
+ const { content } = generateContext([seg]);
435
+ expect(content).toContain("**Composition:** compound");
436
+ expect(content).toContain("Button.Header");
437
+ expect(content).toContain("Button.Body");
438
+ expect(content).toContain("Button.Footer");
439
+ expect(content).toContain("Required: Button.Body");
440
+ });
441
+
442
+ it("includes common patterns in markdown", () => {
443
+ const seg = makeCompiledFragment({
444
+ ai: {
445
+ compositionPattern: "compound",
446
+ subComponents: ["Body"],
447
+ commonPatterns: [
448
+ "<Card><Card.Body>{x}</Card.Body></Card>",
449
+ "<Card><Card.Header /><Card.Body>{x}</Card.Body></Card>",
450
+ ],
451
+ },
452
+ });
453
+ const { content } = generateContext([seg]);
454
+ expect(content).toContain("**Patterns:**");
455
+ expect(content).toContain("`<Card><Card.Body>{x}</Card.Body></Card>`");
456
+ expect(content).toContain("`<Card><Card.Header /><Card.Body>{x}</Card.Body></Card>`");
457
+ });
458
+
459
+ it("uses contract.propsSummary when available in markdown", () => {
460
+ const seg = makeCompiledFragment({
461
+ contract: {
462
+ propsSummary: ["variant: primary|secondary (default: primary)", "size: sm|md|lg"],
463
+ },
464
+ });
465
+ const { content } = generateContext([seg]);
466
+ expect(content).toContain("**Props:** variant: primary|secondary (default: primary), size: sm|md|lg");
467
+ });
468
+
469
+ it("includes a11yRules in markdown", () => {
470
+ const seg = makeCompiledFragment({
471
+ contract: {
472
+ a11yRules: ["Must have aria-label", "Focus visible required"],
473
+ },
474
+ });
475
+ const { content } = generateContext([seg]);
476
+ expect(content).toContain("**A11y:** Must have aria-label, Focus visible required");
477
+ });
478
+
479
+ it("includes bans in markdown", () => {
480
+ const seg = makeCompiledFragment({
481
+ contract: {
482
+ bans: [{ pattern: "onClick", message: "Use onPress instead" }],
483
+ },
484
+ });
485
+ const { content } = generateContext([seg]);
486
+ expect(content).toContain("**Banned patterns:**");
487
+ expect(content).toContain("`onClick`: Use onPress instead");
488
+ });
489
+
490
+ it("includes composition in JSON format", () => {
491
+ const seg = makeCompiledFragment({
492
+ ai: {
493
+ compositionPattern: "compound",
494
+ subComponents: ["Header", "Body"],
495
+ requiredChildren: ["Body"],
496
+ commonPatterns: ["<Card><Card.Body/></Card>"],
497
+ },
498
+ });
499
+ const { content } = generateContext([seg], { format: "json" });
500
+ const json = JSON.parse(content);
501
+ expect(json.components.Button.composition).toEqual({
502
+ pattern: "compound",
503
+ subComponents: ["Header", "Body"],
504
+ requiredChildren: ["Body"],
505
+ commonPatterns: ["<Card><Card.Body/></Card>"],
506
+ });
507
+ });
508
+
509
+ it("includes propsSummary in JSON format", () => {
510
+ const seg = makeCompiledFragment({
511
+ contract: {
512
+ propsSummary: ["variant: enum", "size: enum"],
513
+ },
514
+ });
515
+ const { content } = generateContext([seg], { format: "json" });
516
+ const json = JSON.parse(content);
517
+ expect(json.components.Button.propsSummary).toEqual(["variant: enum", "size: enum"]);
518
+ });
519
+
520
+ it("includes a11yRules and bans in JSON format", () => {
521
+ const seg = makeCompiledFragment({
522
+ contract: {
523
+ a11yRules: ["Must be focusable"],
524
+ bans: [{ pattern: "div>", message: "Use semantic elements" }],
525
+ },
526
+ });
527
+ const { content } = generateContext([seg], { format: "json" });
528
+ const json = JSON.parse(content);
529
+ expect(json.components.Button.a11yRules).toEqual(["Must be focusable"]);
530
+ expect(json.components.Button.bans).toEqual([{ pattern: "div>", message: "Use semantic elements" }]);
531
+ });
532
+
533
+ it("includes compoundChildren in markdown", () => {
534
+ const seg = makeCompiledFragment({
535
+ contract: {
536
+ compoundChildren: {
537
+ Header: { description: "Card header section" },
538
+ Body: { required: true, description: "Card body content" },
539
+ Footer: { accepts: ["children"] },
540
+ },
541
+ },
542
+ });
543
+ const { content } = generateContext([seg]);
544
+ expect(content).toContain("**Sub-components:**");
545
+ expect(content).toContain("`Button.Header`");
546
+ expect(content).toContain("`Button.Body` (required)");
547
+ expect(content).toContain("— Card body content");
548
+ expect(content).toContain("`Button.Footer`");
549
+ });
550
+
551
+ it("includes canonicalUsage in markdown", () => {
552
+ const seg = makeCompiledFragment({
553
+ contract: {
554
+ canonicalUsage: [
555
+ '<Card>\n <Card.Header>Title</Card.Header>\n <Card.Body>Content</Card.Body>\n</Card>',
556
+ ],
557
+ },
558
+ });
559
+ const { content } = generateContext([seg]);
560
+ expect(content).toContain("**Usage examples:**");
561
+ expect(content).toContain("```tsx");
562
+ expect(content).toContain("<Card.Header>Title</Card.Header>");
563
+ });
564
+
565
+ it("includes compoundChildren in JSON format", () => {
566
+ const seg = makeCompiledFragment({
567
+ contract: {
568
+ compoundChildren: {
569
+ Header: { description: "Header slot" },
570
+ Body: { required: true },
571
+ },
572
+ },
573
+ });
574
+ const { content } = generateContext([seg], { format: "json" });
575
+ const json = JSON.parse(content);
576
+ expect(json.components.Button.compoundChildren).toEqual({
577
+ Header: { description: "Header slot" },
578
+ Body: { required: true },
579
+ });
580
+ });
581
+
582
+ it("includes canonicalUsage in JSON format", () => {
583
+ const seg = makeCompiledFragment({
584
+ contract: {
585
+ canonicalUsage: ["<Card><Card.Body>...</Card.Body></Card>"],
586
+ },
587
+ });
588
+ const { content } = generateContext([seg], { format: "json" });
589
+ const json = JSON.parse(content);
590
+ expect(json.components.Button.canonicalUsage).toEqual([
591
+ "<Card><Card.Body>...</Card.Body></Card>",
592
+ ]);
593
+ });
594
+ });