@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.
- package/CHANGELOG.md +20 -0
- package/build-tools/EXAMPLES.md +372 -0
- package/build-tools/README.md +242 -0
- package/build-tools/__tests__/error-handler.test.js +230 -0
- package/build-tools/__tests__/index.test.js +141 -0
- package/build-tools/__tests__/rollup-plugin.test.js +194 -0
- package/build-tools/__tests__/utils.test.js +161 -0
- package/build-tools/__tests__/vite-plugin.test.js +129 -0
- package/build-tools/__tests__/webpack-loader.test.js +190 -0
- package/build-tools/error-handler.js +308 -0
- package/build-tools/index.d.ts +43 -0
- package/build-tools/index.js +88 -0
- package/build-tools/package.json +67 -0
- package/build-tools/rollup-plugin.js +236 -0
- package/build-tools/types.d.ts +163 -0
- package/build-tools/utils.js +203 -0
- package/build-tools/vite-plugin.js +161 -0
- package/build-tools/webpack-loader.js +123 -0
- package/dist/atomix.css +203 -90
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +3 -3
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/EXAMPLES.md +372 -0
- package/dist/build-tools/README.md +242 -0
- package/dist/build-tools/__tests__/error-handler.test.js +230 -0
- package/dist/build-tools/__tests__/index.test.js +141 -0
- package/dist/build-tools/__tests__/rollup-plugin.test.js +194 -0
- package/dist/build-tools/__tests__/utils.test.js +161 -0
- package/dist/build-tools/__tests__/vite-plugin.test.js +129 -0
- package/dist/build-tools/__tests__/webpack-loader.test.js +190 -0
- package/dist/build-tools/error-handler.js +308 -0
- package/dist/build-tools/index.d.ts +43 -0
- package/dist/build-tools/index.js +88 -0
- package/dist/build-tools/package.json +67 -0
- package/dist/build-tools/rollup-plugin.js +236 -0
- package/dist/build-tools/types.d.ts +163 -0
- package/dist/build-tools/utils.js +203 -0
- package/dist/build-tools/vite-plugin.js +161 -0
- package/dist/build-tools/webpack-loader.js +123 -0
- package/dist/charts.d.ts +1 -1
- package/dist/charts.js +86 -57
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +136 -112
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +2 -5
- package/dist/forms.js +140 -128
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +1 -1
- package/dist/heavy.js +136 -112
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +9 -61
- package/dist/index.esm.js +237 -286
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +250 -299
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +23 -8
- package/scripts/atomix-cli.js +170 -73
- package/scripts/cli/__tests__/README.md +81 -0
- package/scripts/cli/__tests__/basic.test.js +115 -0
- package/scripts/cli/__tests__/component-generator.test.js +332 -0
- package/scripts/cli/__tests__/integration.test.js +327 -0
- package/scripts/cli/__tests__/test-setup.js +133 -0
- package/scripts/cli/__tests__/token-manager.test.js +251 -0
- package/scripts/cli/__tests__/utils.test.js +161 -0
- package/scripts/cli/component-generator.js +253 -299
- package/scripts/cli/dependency-checker.js +355 -0
- package/scripts/cli/interactive-init.js +46 -5
- package/scripts/cli/template-manager.js +0 -2
- package/scripts/cli/templates/common-templates.js +636 -0
- package/scripts/cli/templates/composable-templates.js +148 -126
- package/scripts/cli/templates/index.js +23 -16
- package/scripts/cli/templates/project-templates.js +151 -23
- package/scripts/cli/templates/react-templates.js +280 -210
- package/scripts/cli/templates/scss-templates.js +90 -91
- package/scripts/cli/templates/testing-templates.js +206 -27
- package/scripts/cli/templates/testing-utils.js +278 -0
- package/scripts/cli/templates/types-templates.js +70 -56
- package/scripts/cli/theme-bridge.js +8 -2
- package/scripts/cli/token-manager.js +318 -206
- package/scripts/cli/utils.js +0 -1
- package/src/components/Accordion/Accordion.stories.tsx +369 -870
- package/src/components/AtomixGlass/AtomixGlass.tsx +80 -39
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +103 -81
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +8 -7
- package/src/components/AtomixGlass/glass-utils.ts +2 -2
- package/src/components/AtomixGlass/shader-utils.ts +5 -0
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +131 -0
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +2957 -2853
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -1
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +348 -0
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +103 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +50 -35
- package/src/components/AtomixGlass/stories/{ShaderVariants.stories.tsx → Shaders.stories.tsx} +1 -1
- package/src/components/AtomixGlass/stories/shared-components.tsx +90 -190
- package/src/components/Avatar/Avatar.stories.tsx +213 -1
- package/src/components/Badge/Badge.stories.tsx +121 -362
- package/src/components/Block/Block.stories.tsx +21 -12
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +141 -23
- package/src/components/Button/Button.stories.tsx +463 -1126
- package/src/components/Button/Button.test.tsx +107 -0
- package/src/components/Button/Button.tsx +46 -50
- package/src/components/Button/ButtonGroup.stories.tsx +373 -217
- package/src/components/Callout/Callout.stories.tsx +289 -634
- package/src/components/Card/Card.stories.tsx +248 -68
- package/src/components/Chart/Chart.stories.tsx +150 -8
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +151 -69
- package/src/components/Countdown/Countdown.stories.tsx +115 -8
- package/src/components/DataTable/DataTable.stories.tsx +346 -146
- package/src/components/DatePicker/DatePicker.stories.tsx +325 -1066
- package/src/components/Dropdown/Dropdown.stories.tsx +153 -33
- package/src/components/EdgePanel/EdgePanel.stories.tsx +230 -21
- package/src/components/Footer/Footer.stories.tsx +392 -328
- package/src/components/Form/Checkbox.stories.tsx +140 -6
- package/src/components/Form/Checkbox.test.tsx +63 -0
- package/src/components/Form/Checkbox.tsx +87 -51
- package/src/components/Form/Form.stories.tsx +119 -20
- package/src/components/Form/FormGroup.stories.tsx +127 -4
- package/src/components/Form/Radio.stories.tsx +140 -5
- package/src/components/Form/Select.stories.tsx +140 -8
- package/src/components/Form/Textarea.stories.tsx +149 -6
- package/src/components/Hero/Hero.stories.tsx +333 -32
- package/src/components/List/List.stories.tsx +141 -3
- package/src/components/Modal/Modal.stories.tsx +181 -42
- package/src/components/Popover/Popover.stories.tsx +448 -98
- package/src/components/Progress/Progress.stories.tsx +167 -5
- package/src/components/River/River.stories.tsx +1 -1
- package/src/components/SectionIntro/SectionIntro.stories.tsx +240 -48
- package/src/components/Spinner/Spinner.stories.tsx +102 -8
- package/src/components/Steps/Steps.stories.tsx +172 -43
- package/src/components/Tabs/Tabs.stories.tsx +136 -10
- package/src/components/Testimonial/Testimonial.stories.tsx +120 -3
- package/src/components/Todo/Todo.stories.tsx +198 -9
- package/src/components/Toggle/Toggle.stories.tsx +126 -39
- package/src/components/Tooltip/Tooltip.stories.tsx +194 -104
- package/src/components/Upload/Upload.stories.tsx +113 -24
- package/src/lib/README.md +2 -2
- package/src/lib/__tests__/theme-tools.test.ts +193 -0
- package/src/lib/composables/index.ts +2 -2
- package/src/lib/composables/useAtomixGlass.ts +28 -56
- package/src/lib/composables/useChartExport.ts +2 -7
- package/src/lib/composables/useDataTable.ts +46 -29
- package/src/lib/constants/components.ts +9 -32
- package/src/lib/theme/devtools/CLI.ts +1 -1
- package/src/lib/types/components.ts +1 -1
- package/src/lib/utils/__tests__/csv.test.ts +45 -0
- package/src/lib/utils/csv.ts +17 -0
- package/src/lib/utils/dataTableExport.ts +1 -10
- package/src/styles/01-settings/_index.scss +2 -1
- package/src/styles/01-settings/_settings.accordion.scss +28 -7
- package/src/styles/01-settings/_settings.colors.scss +11 -11
- package/src/styles/01-settings/_settings.typography.scss +5 -5
- package/src/styles/02-tools/_tools.utility-api.scss +14 -0
- package/src/styles/06-components/_components.accordion.scss +56 -14
- package/src/styles/06-components/_components.checkbox.scss +23 -17
- package/src/styles/99-utilities/_index.scss +2 -0
- package/src/styles/99-utilities/_utilities.scss +3 -1
- package/src/styles/99-utilities/_utilities.text-gradient.scss +45 -0
- package/themes/dark-complementary/README.md +98 -0
- package/themes/dark-complementary/index.scss +158 -0
- package/themes/default-light/README.md +81 -0
- package/themes/default-light/index.scss +154 -0
- package/themes/high-contrast/README.md +105 -0
- package/themes/high-contrast/index.scss +172 -0
- package/themes/test-theme/README.md +38 -0
- package/themes/test-theme/index.scss +47 -0
- package/scripts/cli/templates-original-backup.js +0 -1655
- package/scripts/cli/templates_backup.js +0 -684
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +0 -1438
- package/src/lib/composables/useButton.ts +0 -93
- 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
|
+
});
|