@shohojdhara/atomix 0.3.14 → 0.3.15

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 (173) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/build-tools/EXAMPLES.md +372 -0
  3. package/build-tools/README.md +242 -0
  4. package/build-tools/__tests__/error-handler.test.js +230 -0
  5. package/build-tools/__tests__/index.test.js +141 -0
  6. package/build-tools/__tests__/rollup-plugin.test.js +194 -0
  7. package/build-tools/__tests__/utils.test.js +161 -0
  8. package/build-tools/__tests__/vite-plugin.test.js +129 -0
  9. package/build-tools/__tests__/webpack-loader.test.js +190 -0
  10. package/build-tools/error-handler.js +308 -0
  11. package/build-tools/index.d.ts +43 -0
  12. package/build-tools/index.js +88 -0
  13. package/build-tools/package.json +67 -0
  14. package/build-tools/rollup-plugin.js +236 -0
  15. package/build-tools/types.d.ts +163 -0
  16. package/build-tools/utils.js +203 -0
  17. package/build-tools/vite-plugin.js +161 -0
  18. package/build-tools/webpack-loader.js +123 -0
  19. package/dist/atomix.css +203 -90
  20. package/dist/atomix.css.map +1 -1
  21. package/dist/atomix.min.css +3 -3
  22. package/dist/atomix.min.css.map +1 -1
  23. package/dist/build-tools/EXAMPLES.md +372 -0
  24. package/dist/build-tools/README.md +242 -0
  25. package/dist/build-tools/__tests__/error-handler.test.js +230 -0
  26. package/dist/build-tools/__tests__/index.test.js +141 -0
  27. package/dist/build-tools/__tests__/rollup-plugin.test.js +194 -0
  28. package/dist/build-tools/__tests__/utils.test.js +161 -0
  29. package/dist/build-tools/__tests__/vite-plugin.test.js +129 -0
  30. package/dist/build-tools/__tests__/webpack-loader.test.js +190 -0
  31. package/dist/build-tools/error-handler.js +308 -0
  32. package/dist/build-tools/index.d.ts +43 -0
  33. package/dist/build-tools/index.js +88 -0
  34. package/dist/build-tools/package.json +67 -0
  35. package/dist/build-tools/rollup-plugin.js +236 -0
  36. package/dist/build-tools/types.d.ts +163 -0
  37. package/dist/build-tools/utils.js +203 -0
  38. package/dist/build-tools/vite-plugin.js +161 -0
  39. package/dist/build-tools/webpack-loader.js +123 -0
  40. package/dist/charts.d.ts +1 -1
  41. package/dist/charts.js +86 -57
  42. package/dist/charts.js.map +1 -1
  43. package/dist/core.d.ts +1 -1
  44. package/dist/core.js +136 -112
  45. package/dist/core.js.map +1 -1
  46. package/dist/forms.d.ts +2 -5
  47. package/dist/forms.js +140 -128
  48. package/dist/forms.js.map +1 -1
  49. package/dist/heavy.d.ts +1 -1
  50. package/dist/heavy.js +136 -112
  51. package/dist/heavy.js.map +1 -1
  52. package/dist/index.d.ts +9 -61
  53. package/dist/index.esm.js +237 -286
  54. package/dist/index.esm.js.map +1 -1
  55. package/dist/index.js +250 -299
  56. package/dist/index.js.map +1 -1
  57. package/dist/index.min.js +1 -1
  58. package/dist/index.min.js.map +1 -1
  59. package/package.json +23 -8
  60. package/scripts/atomix-cli.js +170 -73
  61. package/scripts/cli/__tests__/README.md +81 -0
  62. package/scripts/cli/__tests__/basic.test.js +115 -0
  63. package/scripts/cli/__tests__/component-generator.test.js +332 -0
  64. package/scripts/cli/__tests__/integration.test.js +327 -0
  65. package/scripts/cli/__tests__/test-setup.js +133 -0
  66. package/scripts/cli/__tests__/token-manager.test.js +251 -0
  67. package/scripts/cli/__tests__/utils.test.js +161 -0
  68. package/scripts/cli/component-generator.js +253 -299
  69. package/scripts/cli/dependency-checker.js +355 -0
  70. package/scripts/cli/interactive-init.js +46 -5
  71. package/scripts/cli/template-manager.js +0 -2
  72. package/scripts/cli/templates/common-templates.js +636 -0
  73. package/scripts/cli/templates/composable-templates.js +148 -126
  74. package/scripts/cli/templates/index.js +23 -16
  75. package/scripts/cli/templates/project-templates.js +151 -23
  76. package/scripts/cli/templates/react-templates.js +280 -210
  77. package/scripts/cli/templates/scss-templates.js +90 -91
  78. package/scripts/cli/templates/testing-templates.js +206 -27
  79. package/scripts/cli/templates/testing-utils.js +278 -0
  80. package/scripts/cli/templates/types-templates.js +70 -56
  81. package/scripts/cli/theme-bridge.js +8 -2
  82. package/scripts/cli/token-manager.js +318 -206
  83. package/scripts/cli/utils.js +0 -1
  84. package/src/components/Accordion/Accordion.stories.tsx +369 -870
  85. package/src/components/AtomixGlass/AtomixGlass.tsx +80 -39
  86. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +103 -81
  87. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +8 -7
  88. package/src/components/AtomixGlass/glass-utils.ts +2 -2
  89. package/src/components/AtomixGlass/shader-utils.ts +5 -0
  90. package/src/components/AtomixGlass/stories/Customization.stories.tsx +131 -0
  91. package/src/components/AtomixGlass/stories/Examples.stories.tsx +2957 -2853
  92. package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -1
  93. package/src/components/AtomixGlass/stories/Overview.stories.tsx +348 -0
  94. package/src/components/AtomixGlass/stories/Performance.stories.tsx +103 -0
  95. package/src/components/AtomixGlass/stories/Playground.stories.tsx +50 -35
  96. package/src/components/AtomixGlass/stories/{ShaderVariants.stories.tsx → Shaders.stories.tsx} +1 -1
  97. package/src/components/AtomixGlass/stories/shared-components.tsx +90 -190
  98. package/src/components/Avatar/Avatar.stories.tsx +213 -1
  99. package/src/components/Badge/Badge.stories.tsx +121 -362
  100. package/src/components/Block/Block.stories.tsx +21 -12
  101. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +141 -23
  102. package/src/components/Button/Button.stories.tsx +463 -1126
  103. package/src/components/Button/Button.test.tsx +107 -0
  104. package/src/components/Button/Button.tsx +46 -50
  105. package/src/components/Button/ButtonGroup.stories.tsx +373 -217
  106. package/src/components/Callout/Callout.stories.tsx +289 -634
  107. package/src/components/Card/Card.stories.tsx +248 -68
  108. package/src/components/Chart/Chart.stories.tsx +150 -8
  109. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +151 -69
  110. package/src/components/Countdown/Countdown.stories.tsx +115 -8
  111. package/src/components/DataTable/DataTable.stories.tsx +346 -146
  112. package/src/components/DatePicker/DatePicker.stories.tsx +325 -1066
  113. package/src/components/Dropdown/Dropdown.stories.tsx +153 -33
  114. package/src/components/EdgePanel/EdgePanel.stories.tsx +230 -21
  115. package/src/components/Footer/Footer.stories.tsx +392 -328
  116. package/src/components/Form/Checkbox.stories.tsx +140 -6
  117. package/src/components/Form/Checkbox.test.tsx +63 -0
  118. package/src/components/Form/Checkbox.tsx +87 -51
  119. package/src/components/Form/Form.stories.tsx +119 -20
  120. package/src/components/Form/FormGroup.stories.tsx +127 -4
  121. package/src/components/Form/Radio.stories.tsx +140 -5
  122. package/src/components/Form/Select.stories.tsx +140 -8
  123. package/src/components/Form/Textarea.stories.tsx +149 -6
  124. package/src/components/Hero/Hero.stories.tsx +333 -32
  125. package/src/components/List/List.stories.tsx +141 -3
  126. package/src/components/Modal/Modal.stories.tsx +181 -42
  127. package/src/components/Popover/Popover.stories.tsx +448 -98
  128. package/src/components/Progress/Progress.stories.tsx +167 -5
  129. package/src/components/River/River.stories.tsx +1 -1
  130. package/src/components/SectionIntro/SectionIntro.stories.tsx +240 -48
  131. package/src/components/Spinner/Spinner.stories.tsx +102 -8
  132. package/src/components/Steps/Steps.stories.tsx +172 -43
  133. package/src/components/Tabs/Tabs.stories.tsx +136 -10
  134. package/src/components/Testimonial/Testimonial.stories.tsx +120 -3
  135. package/src/components/Todo/Todo.stories.tsx +198 -9
  136. package/src/components/Toggle/Toggle.stories.tsx +126 -39
  137. package/src/components/Tooltip/Tooltip.stories.tsx +194 -104
  138. package/src/components/Upload/Upload.stories.tsx +113 -24
  139. package/src/lib/README.md +2 -2
  140. package/src/lib/__tests__/theme-tools.test.ts +193 -0
  141. package/src/lib/composables/index.ts +2 -2
  142. package/src/lib/composables/useAtomixGlass.ts +28 -56
  143. package/src/lib/composables/useChartExport.ts +2 -7
  144. package/src/lib/composables/useDataTable.ts +46 -29
  145. package/src/lib/constants/components.ts +9 -32
  146. package/src/lib/theme/devtools/CLI.ts +1 -1
  147. package/src/lib/types/components.ts +1 -1
  148. package/src/lib/utils/__tests__/csv.test.ts +45 -0
  149. package/src/lib/utils/csv.ts +17 -0
  150. package/src/lib/utils/dataTableExport.ts +1 -10
  151. package/src/styles/01-settings/_index.scss +2 -1
  152. package/src/styles/01-settings/_settings.accordion.scss +28 -7
  153. package/src/styles/01-settings/_settings.colors.scss +11 -11
  154. package/src/styles/01-settings/_settings.typography.scss +5 -5
  155. package/src/styles/02-tools/_tools.utility-api.scss +14 -0
  156. package/src/styles/06-components/_components.accordion.scss +56 -14
  157. package/src/styles/06-components/_components.checkbox.scss +23 -17
  158. package/src/styles/99-utilities/_index.scss +2 -0
  159. package/src/styles/99-utilities/_utilities.scss +3 -1
  160. package/src/styles/99-utilities/_utilities.text-gradient.scss +45 -0
  161. package/themes/dark-complementary/README.md +98 -0
  162. package/themes/dark-complementary/index.scss +158 -0
  163. package/themes/default-light/README.md +81 -0
  164. package/themes/default-light/index.scss +154 -0
  165. package/themes/high-contrast/README.md +105 -0
  166. package/themes/high-contrast/index.scss +172 -0
  167. package/themes/test-theme/README.md +38 -0
  168. package/themes/test-theme/index.scss +47 -0
  169. package/scripts/cli/templates-original-backup.js +0 -1655
  170. package/scripts/cli/templates_backup.js +0 -684
  171. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +0 -1438
  172. package/src/lib/composables/useButton.ts +0 -93
  173. package/src/lib/composables/useCheckbox.ts +0 -70
@@ -0,0 +1,327 @@
1
+ /**
2
+ * CLI Integration Tests
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+ import { mkdtemp, rm, writeFile, mkdir } from 'fs/promises';
7
+ import { existsSync } from 'fs';
8
+ import { join, resolve } from 'path';
9
+ import { tmpdir } from 'os';
10
+ import { execSync } from 'child_process';
11
+
12
+ // Mock console to avoid noise in tests
13
+ const originalConsole = global.console;
14
+
15
+ describe('CLI Integration Tests', () => {
16
+ let tempDir;
17
+
18
+ beforeEach(async () => {
19
+ tempDir = await mkdtemp(join(tmpdir(), 'atomix-cli-test-'));
20
+
21
+ // Create project structure
22
+ await mkdir(join(tempDir, 'src/styles/01-settings'), { recursive: true });
23
+ await mkdir(join(tempDir, 'src/lib/composables'), { recursive: true });
24
+ await mkdir(join(tempDir, 'src/lib/types'), { recursive: true });
25
+
26
+ // Mock console methods
27
+ global.console = {
28
+ ...originalConsole,
29
+ log: vi.fn(),
30
+ error: vi.fn(),
31
+ warn: vi.fn()
32
+ };
33
+ });
34
+
35
+ afterEach(async () => {
36
+ await rm(tempDir, { recursive: true, force: true });
37
+ global.console = originalConsole;
38
+ vi.clearAllMocks();
39
+ });
40
+
41
+ describe('atomix generate component', () => {
42
+ it('should generate a basic component', () => {
43
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
44
+
45
+ execSync(`node ${cliPath} generate component TestButton`, {
46
+ cwd: tempDir,
47
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
48
+ stdio: 'pipe'
49
+ });
50
+
51
+ const componentDir = join(tempDir, 'src', 'components', 'TestButton');
52
+ expect(existsSync(join(componentDir, 'TestButton.tsx'))).toBe(true);
53
+ expect(existsSync(join(componentDir, 'index.ts'))).toBe(true);
54
+ });
55
+
56
+ it('should generate component with stories', () => {
57
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
58
+
59
+ execSync(`node ${cliPath} generate component StoryCard --story`, {
60
+ cwd: tempDir,
61
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
62
+ stdio: 'pipe'
63
+ });
64
+
65
+ const storyFile = join(tempDir, 'src', 'components', 'StoryCard', 'StoryCard.stories.tsx');
66
+ expect(existsSync(storyFile)).toBe(true);
67
+ });
68
+
69
+ it('should generate component with test file', () => {
70
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
71
+
72
+ execSync(`node ${cliPath} generate component TestComponent --test`, {
73
+ cwd: tempDir,
74
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
75
+ stdio: 'pipe'
76
+ });
77
+
78
+ const testFile = join(tempDir, 'src', 'components', 'TestComponent', 'TestComponent.test.tsx');
79
+ expect(existsSync(testFile)).toBe(true);
80
+ });
81
+
82
+ it('should reject invalid component names', () => {
83
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
84
+
85
+ expect(() => {
86
+ execSync(`node ${cliPath} generate component invalid-name`, {
87
+ cwd: tempDir,
88
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
89
+ stdio: 'pipe'
90
+ });
91
+ }).toThrow();
92
+ });
93
+
94
+ it('should handle existing component without force flag', () => {
95
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
96
+
97
+ // Generate component first time
98
+ execSync(`node ${cliPath} generate component DuplicateButton`, {
99
+ cwd: tempDir,
100
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
101
+ stdio: 'pipe'
102
+ });
103
+
104
+ // Try to generate again without force
105
+ expect(() => {
106
+ execSync(`node ${cliPath} generate component DuplicateButton`, {
107
+ cwd: tempDir,
108
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
109
+ stdio: 'pipe'
110
+ });
111
+ }).toThrow();
112
+ });
113
+
114
+ it('should overwrite existing component with force flag', () => {
115
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
116
+
117
+ // Generate component first time
118
+ execSync(`node ${cliPath} generate component ForceButton`, {
119
+ cwd: tempDir,
120
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
121
+ stdio: 'pipe'
122
+ });
123
+
124
+ // Generate again with force
125
+ execSync(`node ${cliPath} generate component ForceButton --force`, {
126
+ cwd: tempDir,
127
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
128
+ stdio: 'pipe'
129
+ });
130
+
131
+ const componentFile = join(tempDir, 'src', 'components', 'ForceButton', 'ForceButton.tsx');
132
+ expect(existsSync(componentFile)).toBe(true);
133
+ });
134
+ });
135
+
136
+ describe('atomix generate token', () => {
137
+ it('should generate color tokens', () => {
138
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
139
+
140
+ execSync(`node ${cliPath} generate token colors`, {
141
+ cwd: tempDir,
142
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
143
+ stdio: 'pipe'
144
+ });
145
+
146
+ const tokenFile = join(tempDir, 'src', 'styles', '01-settings', '_settings.colors.custom.scss');
147
+ expect(existsSync(tokenFile)).toBe(true);
148
+ });
149
+
150
+ it('should generate spacing tokens', () => {
151
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
152
+
153
+ execSync(`node ${cliPath} generate token spacing`, {
154
+ cwd: tempDir,
155
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
156
+ stdio: 'pipe'
157
+ });
158
+
159
+ const tokenFile = join(tempDir, 'src', 'styles', '01-settings', '_settings.spacing.custom.scss');
160
+ expect(existsSync(tokenFile)).toBe(true);
161
+ });
162
+
163
+ it('should generate all token categories', () => {
164
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
165
+ const categories = ['colors', 'spacing', 'typography', 'shadows', 'radius', 'animations'];
166
+
167
+ categories.forEach(category => {
168
+ execSync(`node ${cliPath} generate token ${category}`, {
169
+ cwd: tempDir,
170
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
171
+ stdio: 'pipe'
172
+ });
173
+ });
174
+
175
+ categories.forEach(category => {
176
+ let filename;
177
+ switch (category) {
178
+ case 'colors':
179
+ filename = '_settings.colors.custom.scss';
180
+ break;
181
+ case 'spacing':
182
+ filename = '_settings.spacing.custom.scss';
183
+ break;
184
+ case 'typography':
185
+ filename = '_settings.typography.custom.scss';
186
+ break;
187
+ case 'shadows':
188
+ filename = '_settings.box-shadow.custom.scss';
189
+ break;
190
+ case 'radius':
191
+ filename = '_settings.border-radius.custom.scss';
192
+ break;
193
+ case 'animations':
194
+ filename = '_settings.animations.custom.scss';
195
+ break;
196
+ }
197
+
198
+ const tokenFile = join(tempDir, 'src', 'styles', '01-settings', filename);
199
+ expect(existsSync(tokenFile)).toBe(true);
200
+ });
201
+ });
202
+
203
+ it('should reject invalid token categories', () => {
204
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
205
+
206
+ expect(() => {
207
+ execSync(`node ${cliPath} generate token invalid-category`, {
208
+ cwd: tempDir,
209
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
210
+ stdio: 'pipe'
211
+ });
212
+ }).toThrow();
213
+ });
214
+ });
215
+
216
+ describe('atomix validate', () => {
217
+ beforeEach(async () => {
218
+ // Create some token files for validation
219
+ const stylesDir = join(tempDir, 'src', 'styles', '01-settings');
220
+ await mkdir(stylesDir, { recursive: true });
221
+
222
+ await writeFile(join(stylesDir, '_settings.colors.scss'), `
223
+ $primary: blue !default;
224
+ $secondary: red !default;
225
+ --atomix-color-primary: blue;
226
+ --atomix-color-secondary: red;
227
+ `);
228
+
229
+ await writeFile(join(stylesDir, '_settings.typography.scss'), '$font-base: sans-serif !default;');
230
+ await writeFile(join(stylesDir, '_settings.spacing.scss'), '$spacing-unit: 8px !default;');
231
+ await writeFile(join(stylesDir, '_settings.radius.scss'), '$radius-base: 4px !default;');
232
+ });
233
+
234
+ it('should validate tokens', () => {
235
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
236
+
237
+ const result = execSync(`node ${cliPath} validate --tokens`, {
238
+ cwd: tempDir,
239
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
240
+ encoding: 'utf8',
241
+ stdio: 'pipe'
242
+ });
243
+
244
+ expect(result).toContain('All validations passed');
245
+ });
246
+
247
+ it('should detect validation issues', async () => {
248
+ // Create problematic token file
249
+ const stylesDir = join(tempDir, 'src', 'styles', '01-settings');
250
+ await writeFile(join(stylesDir, '_settings.colors.scss'), `
251
+ $primary: #ffffff; // Hardcoded color and missing !default
252
+ background: #ff0000; // Another hardcoded color
253
+ `);
254
+
255
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
256
+
257
+ try {
258
+ const result = execSync(`node ${cliPath} validate --tokens`, {
259
+ cwd: tempDir,
260
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
261
+ encoding: 'utf8',
262
+ stdio: 'pipe'
263
+ });
264
+ expect(result).toContain('issues found');
265
+ } catch (error) {
266
+ const output = error.stdout ? error.stdout.toString() : error.message;
267
+ expect(output).toContain('issues found');
268
+ }
269
+ });
270
+ });
271
+
272
+ describe('atomix doctor', () => {
273
+ it('should run system diagnostics', () => {
274
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
275
+
276
+ const result = execSync(`node ${cliPath} doctor`, {
277
+ cwd: tempDir,
278
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
279
+ encoding: 'utf8',
280
+ stdio: 'pipe'
281
+ });
282
+
283
+ expect(result).toContain('Atomix Doctor');
284
+ });
285
+ });
286
+
287
+ describe('Error Handling', () => {
288
+ it('should handle missing command gracefully', () => {
289
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
290
+
291
+ expect(() => {
292
+ execSync(`node ${cliPath} non-existent-command`, {
293
+ cwd: tempDir,
294
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
295
+ stdio: 'pipe'
296
+ });
297
+ }).toThrow();
298
+ });
299
+
300
+ it('should show help for --help flag', () => {
301
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
302
+
303
+ const result = execSync(`node ${cliPath} --help`, {
304
+ cwd: tempDir,
305
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
306
+ encoding: 'utf8',
307
+ stdio: 'pipe'
308
+ });
309
+
310
+ expect(result).toContain('Atomix Design System CLI');
311
+ expect(result).toContain('Commands:');
312
+ });
313
+
314
+ it('should show version for --version flag', () => {
315
+ const cliPath = resolve(__dirname, '../../atomix-cli.js');
316
+
317
+ const result = execSync(`node ${cliPath} --version`, {
318
+ cwd: tempDir,
319
+ env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
320
+ encoding: 'utf8',
321
+ stdio: 'pipe'
322
+ });
323
+
324
+ expect(result).toMatch(/\d+\.\d+\.\d+/);
325
+ });
326
+ });
327
+ });
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Test Setup for CLI Tests
3
+ */
4
+
5
+ import { vi } from 'vitest';
6
+
7
+ // Mock external dependencies
8
+ vi.mock('ora', () => ({
9
+ default: vi.fn(() => ({
10
+ start: vi.fn(() => ({
11
+ succeed: vi.fn(),
12
+ fail: vi.fn(),
13
+ stop: vi.fn(),
14
+ text: ''
15
+ }))
16
+ }))
17
+ }));
18
+
19
+ vi.mock('inquirer', () => ({
20
+ prompt: vi.fn()
21
+ }));
22
+
23
+ vi.mock('chalk', () => ({
24
+ default: {
25
+ green: vi.fn((text) => text),
26
+ red: vi.fn((text) => text),
27
+ yellow: vi.fn((text) => text),
28
+ cyan: vi.fn((text) => text),
29
+ gray: vi.fn((text) => text),
30
+ bold: {
31
+ green: vi.fn((text) => text),
32
+ red: vi.fn((text) => text),
33
+ yellow: vi.fn((text) => text),
34
+ cyan: vi.fn((text) => text)
35
+ }
36
+ }
37
+ }));
38
+
39
+ vi.mock('boxen', () => ({
40
+ default: vi.fn((text) => text)
41
+ }));
42
+
43
+ vi.mock('chokidar', () => ({
44
+ default: vi.fn(() => ({
45
+ on: vi.fn(),
46
+ close: vi.fn()
47
+ }))
48
+ }));
49
+
50
+ // Mock file system operations
51
+ vi.mock('fs/promises', async () => {
52
+ const actual = await vi.importActual('fs/promises');
53
+ return {
54
+ ...actual,
55
+ writeFile: vi.fn(actual.writeFile),
56
+ readFile: vi.fn(actual.readFile),
57
+ mkdir: vi.fn(actual.mkdir),
58
+ access: vi.fn(actual.access),
59
+ stat: vi.fn(actual.stat),
60
+ rm: vi.fn(actual.rm)
61
+ };
62
+ });
63
+
64
+ // Mock process.cwd for consistent test environment
65
+ const originalCwd = process.cwd;
66
+
67
+ beforeEach(() => {
68
+ // Reset all mocks before each test
69
+ vi.clearAllMocks();
70
+ });
71
+
72
+ afterEach(() => {
73
+ // Restore original process.cwd
74
+ process.cwd = originalCwd;
75
+ });
76
+
77
+ // Global test utilities
78
+ global.createMockTempDir = () => '/tmp/atomix-test-' + Math.random().toString(36).substr(2, 9);
79
+
80
+ global.mockComponentStructure = (componentName, content = '') => ({
81
+ [`${componentName}.tsx`]: content || `
82
+ import React, { forwardRef } from 'react';
83
+
84
+ export const ${componentName} = forwardRef<HTMLDivElement, ${componentName}Props>(
85
+ ({ children, className = '', ...props }, ref) => {
86
+ return (
87
+ <div ref={ref} className={className} {...props}>
88
+ {children}
89
+ </div>
90
+ );
91
+ }
92
+ );
93
+
94
+ interface ${componentName}Props {
95
+ children?: React.ReactNode;
96
+ className?: string;
97
+ }
98
+ `,
99
+ 'index.ts': `export { ${componentName} } from './${componentName}';`,
100
+ [`${componentName}.stories.tsx`]: `
101
+ import type { Meta, StoryObj } from '@storybook/react';
102
+ import { ${componentName} } from './${componentName}';
103
+
104
+ const meta: Meta<typeof ${componentName}> = {
105
+ title: 'Components/${componentName}',
106
+ component: ${componentName},
107
+ parameters: {
108
+ layout: 'centered',
109
+ },
110
+ tags: ['autodocs'],
111
+ };
112
+
113
+ export default meta;
114
+ type Story = StoryObj<typeof meta>;
115
+
116
+ export const Default: Story = {
117
+ args: {
118
+ children: 'Test ${componentName}',
119
+ },
120
+ };
121
+ `,
122
+ [`${componentName}.test.tsx`]: `
123
+ import { render, screen } from '@testing-library/react';
124
+ import { ${componentName} } from './${componentName}';
125
+
126
+ describe('${componentName}', () => {
127
+ it('renders correctly', () => {
128
+ render(<${componentName}>Test</${componentName}>);
129
+ expect(screen.getByText('Test')).toBeInTheDocument();
130
+ });
131
+ });
132
+ `
133
+ });
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Token Manager Tests
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+ import {
7
+ listTokens,
8
+ validateTokens,
9
+ exportTokens,
10
+ importTokens,
11
+ fixTokens,
12
+ setProjectRoot
13
+ } from '../token-manager.js';
14
+ import { mkdtemp, rm, writeFile, readFile, mkdir } from 'fs/promises';
15
+ import { join } from 'path';
16
+ import { tmpdir } from 'os';
17
+
18
+ // Mock dependencies
19
+ vi.mock('ora', () => ({
20
+ default: vi.fn(() => ({
21
+ start: vi.fn(() => ({
22
+ succeed: vi.fn(),
23
+ fail: vi.fn(),
24
+ text: ''
25
+ }))
26
+ }))
27
+ }));
28
+
29
+ vi.mock('chalk', () => ({
30
+ default: {
31
+ green: vi.fn((text) => text),
32
+ red: vi.fn((text) => text),
33
+ yellow: vi.fn((text) => text),
34
+ cyan: vi.fn((text) => text),
35
+ gray: vi.fn((text) => text),
36
+ bold: vi.fn((obj) => obj)
37
+ }
38
+ }));
39
+
40
+ describe('Token Manager', () => {
41
+ let tempDir;
42
+ let tokenDir;
43
+
44
+ beforeEach(async () => {
45
+ const tmp = await mkdtemp(join(tmpdir(), 'atomix-test-'));
46
+ // Ensure absolute path
47
+ // On some systems/configs tmpdir might return symlinked path or weirdness
48
+ const { resolve } = await import('path');
49
+ tempDir = resolve(tmp);
50
+
51
+ tokenDir = join(tempDir, 'src', 'styles', '01-settings');
52
+ await mkdir(tokenDir, { recursive: true });
53
+ setProjectRoot(tempDir);
54
+ });
55
+
56
+ afterEach(async () => {
57
+ setProjectRoot('');
58
+ await rm(tempDir, { recursive: true, force: true });
59
+ vi.clearAllMocks();
60
+ });
61
+
62
+ describe('listTokens', () => {
63
+ it('should list tokens from valid files', async () => {
64
+ // Create test token files
65
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
66
+ $primary-500: #7AFFD7 !default;
67
+ $secondary-500: #FF5733 !default;
68
+ --atomix-color-primary: #7AFFD7;
69
+ --atomix-color-secondary: #FF5733;
70
+ `);
71
+
72
+ const result = await listTokens();
73
+
74
+ expect(result.tokens).toHaveProperty('colors');
75
+ // Check keys inside the category object
76
+ const colorTokens = result.tokens.colors.tokens;
77
+ expect(colorTokens).toHaveProperty('$primary-500');
78
+ expect(colorTokens).toHaveProperty('--atomix-color-primary');
79
+ });
80
+
81
+ it('should handle missing files gracefully', async () => {
82
+ const result = await listTokens();
83
+ expect(result.tokens).toEqual({});
84
+ expect(result.categoryCount).toBe(0);
85
+ });
86
+ });
87
+
88
+ describe('validateTokens', () => {
89
+ it('should detect hardcoded colors', async () => {
90
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
91
+ $primary: #ffffff !default;
92
+ $secondary: #000000 !default;
93
+ background: #ff0000; // Hardcoded color
94
+ `);
95
+
96
+ const result = await validateTokens();
97
+
98
+ expect(result.warnings).toContainEqual(
99
+ expect.objectContaining({
100
+ category: 'hardcoded-value',
101
+ file: expect.stringContaining('_settings.colors.scss')
102
+ })
103
+ );
104
+ });
105
+
106
+ it('should detect missing !default flags', async () => {
107
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
108
+ $primary: #7AFFD7; // Missing !default
109
+ $secondary: #FF5733 !default;
110
+ `);
111
+
112
+ const result = await validateTokens();
113
+
114
+ expect(result.issues).toContainEqual(
115
+ expect.objectContaining({
116
+ category: 'missing-default',
117
+ file: expect.stringContaining('_settings.colors.scss')
118
+ })
119
+ );
120
+ });
121
+
122
+ it('should validate naming conventions', async () => {
123
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
124
+ $Invalid-Name: #7AFFD7 !default;
125
+ $valid_name: #FF5733 !default;
126
+ `);
127
+
128
+ const result = await validateTokens();
129
+
130
+ expect(result.issues).toContainEqual(
131
+ expect.objectContaining({
132
+ category: 'naming-convention',
133
+ file: expect.stringContaining('_settings.colors.scss')
134
+ })
135
+ );
136
+ });
137
+ });
138
+
139
+ describe('exportTokens', () => {
140
+ it('should export tokens as JSON', async () => {
141
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
142
+ $primary-500: #7AFFD7 !default;
143
+ --atomix-color-primary: #7AFFD7;
144
+ `);
145
+
146
+ const outputPath = join(tempDir, 'tokens.json');
147
+ await exportTokens('json', outputPath);
148
+
149
+ const exported = JSON.parse(await readFile(outputPath, 'utf8'));
150
+ expect(exported).toHaveProperty('colors');
151
+ expect(exported.colors).toHaveProperty('$primary-500');
152
+ });
153
+
154
+ it('should export tokens as CSS custom properties', async () => {
155
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
156
+ $primary-500: #7AFFD7 !default;
157
+ --atomix-color-primary: #7AFFD7;
158
+ `);
159
+
160
+ const outputPath = join(tempDir, 'tokens.css');
161
+ await exportTokens('css', outputPath);
162
+
163
+ const css = await readFile(outputPath, 'utf8');
164
+ expect(css).toContain('--atomix-color-primary: #7AFFD7');
165
+ });
166
+
167
+ it('should export tokens as TypeScript', async () => {
168
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
169
+ $primary-500: #7AFFD7 !default;
170
+ --atomix-color-primary: #7AFFD7;
171
+ `);
172
+
173
+ const outputPath = join(tempDir, 'tokens.ts');
174
+ await exportTokens('ts', outputPath);
175
+
176
+ const ts = await readFile(outputPath, 'utf8');
177
+ expect(ts).toContain('export interface');
178
+ expect(ts).toContain('AtomixTokens');
179
+ });
180
+ });
181
+
182
+ describe('importTokens', () => {
183
+ it('should import tokens from JSON', async () => {
184
+ const jsonPath = join(tempDir, 'tokens.json');
185
+ await writeFile(jsonPath, JSON.stringify({
186
+ colors: {
187
+ '$primary-500': '#7AFFD7',
188
+ '--atomix-color-primary': '#7AFFD7'
189
+ }
190
+ }));
191
+
192
+ await importTokens(jsonPath);
193
+
194
+ // Verify the tokens were written to SCSS files
195
+ const colorsFile = await readFile(join(tokenDir, '_settings.colors.scss'), 'utf8');
196
+ expect(colorsFile).toContain('$primary-500: #7AFFD7 !default');
197
+ expect(colorsFile).toContain('--atomix-color-primary: #7AFFD7');
198
+ });
199
+
200
+ it('should import tokens from JavaScript', async () => {
201
+ const jsPath = join(tempDir, 'tokens.js');
202
+ await writeFile(jsPath, `
203
+ export const tokens = {
204
+ colors: {
205
+ '$primary-500': '#7AFFD7',
206
+ '--atomix-color-primary': '#7AFFD7'
207
+ }
208
+ };
209
+ `);
210
+
211
+ await importTokens(jsPath);
212
+
213
+ const colorsFile = await readFile(join(tokenDir, '_settings.colors.scss'), 'utf8');
214
+ expect(colorsFile).toContain('$primary-500: #7AFFD7 !default');
215
+ });
216
+
217
+ it('should handle malformed JSON gracefully', async () => {
218
+ const jsonPath = join(tempDir, 'invalid.json');
219
+ await writeFile(jsonPath, '{ invalid json }');
220
+
221
+ await expect(importTokens(jsonPath)).rejects.toThrow();
222
+ });
223
+ });
224
+
225
+ describe('fixTokens', () => {
226
+ it('should add missing !default flags', async () => {
227
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
228
+ $primary: #7AFFD7;
229
+ $secondary: #FF5733 !default;
230
+ `);
231
+
232
+ await fixTokens();
233
+
234
+ const content = await readFile(join(tokenDir, '_settings.colors.scss'), 'utf8');
235
+ expect(content).toContain('$primary: var(--atomix-color-primary) !default');
236
+ });
237
+
238
+ it('should convert hardcoded colors to variables', async () => {
239
+ await writeFile(join(tokenDir, '_settings.colors.scss'), `
240
+ $primary: #7AFFD7 !default;
241
+ background: #ffffff;
242
+ `);
243
+
244
+ await fixTokens();
245
+
246
+ const content = await readFile(join(tokenDir, '_settings.colors.scss'), 'utf8');
247
+ // Should replace hardcoded colors with variables
248
+ expect(content).toContain('var(--atomix-color-text)');
249
+ });
250
+ });
251
+ });