@shohojdhara/atomix 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atomix.config.ts +12 -0
- package/build-tools/webpack-loader.js +5 -4
- package/dist/atomix.css +230 -83
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/webpack-loader.js +5 -4
- package/dist/charts.d.ts +24 -23
- package/dist/charts.js +271 -369
- package/dist/charts.js.map +1 -1
- package/dist/config.d.ts +624 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +3 -2
- package/dist/core.js +342 -382
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +4 -6
- package/dist/forms.js +233 -334
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +11 -2
- package/dist/heavy.js +406 -445
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +109 -65
- package/dist/index.esm.js +654 -748
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +621 -717
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/layout.js +59 -60
- package/dist/layout.js.map +1 -1
- package/dist/theme.js +4 -4
- package/dist/theme.js.map +1 -1
- package/package.json +24 -9
- package/scripts/atomix-cli.js +15 -1
- package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
- package/scripts/cli/__tests__/detector.test.js +50 -0
- package/scripts/cli/__tests__/template-engine.test.js +23 -0
- package/scripts/cli/__tests__/test-setup.js +1 -133
- package/scripts/cli/commands/doctor.js +15 -3
- package/scripts/cli/commands/generate.js +113 -51
- package/scripts/cli/internal/ai-engine.js +30 -10
- package/scripts/cli/internal/complexity-utils.js +60 -0
- package/scripts/cli/internal/component-validator.js +49 -16
- package/scripts/cli/internal/generator.js +89 -36
- package/scripts/cli/internal/hook-generator.js +5 -2
- package/scripts/cli/internal/itcss-generator.js +16 -12
- package/scripts/cli/templates/next-templates.js +81 -30
- package/scripts/cli/templates/storybook-templates.js +12 -2
- package/scripts/cli/utils/detector.js +45 -7
- package/scripts/cli/utils/diagnostics.js +78 -0
- package/scripts/cli/utils/telemetry.js +13 -0
- package/src/components/Accordion/Accordion.stories.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +188 -128
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +63 -91
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +9 -6
- package/src/components/AtomixGlass/glass-utils.ts +51 -1
- package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +52 -46
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
- package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
- package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
- package/src/components/AtomixGlass/stories/types.ts +3 -3
- package/src/components/Button/Button.tsx +114 -57
- package/src/components/Callout/Callout.tsx +4 -4
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/DonutChart.tsx +11 -8
- package/src/components/EdgePanel/EdgePanel.tsx +119 -115
- package/src/components/Form/Select.tsx +4 -4
- package/src/components/List/List.tsx +4 -4
- package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
- package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
- package/src/components/ProductReview/ProductReview.tsx +4 -2
- package/src/components/Rating/Rating.tsx +4 -2
- package/src/components/SectionIntro/SectionIntro.tsx +4 -2
- package/src/components/Steps/Steps.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +5 -5
- package/src/components/Testimonial/Testimonial.tsx +4 -2
- package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
- package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
- package/src/layouts/CssGrid/CssGrid.tsx +215 -0
- package/src/layouts/CssGrid/index.ts +8 -0
- package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
- package/src/layouts/CssGrid/scripts/index.js +43 -0
- package/src/layouts/Grid/scripts/Container.js +139 -0
- package/src/layouts/Grid/scripts/Grid.js +184 -0
- package/src/layouts/Grid/scripts/GridCol.js +273 -0
- package/src/layouts/Grid/scripts/Row.js +154 -0
- package/src/layouts/Grid/scripts/index.js +48 -0
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
- package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
- package/src/lib/composables/useAccordion.ts +5 -5
- package/src/lib/composables/useAtomixGlass.ts +111 -74
- package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
- package/src/lib/composables/useBarChart.ts +2 -2
- package/src/lib/composables/useChart.ts +3 -2
- package/src/lib/composables/useChartToolbar.ts +48 -66
- package/src/lib/composables/useDataTable.ts +1 -1
- package/src/lib/composables/useDatePicker.ts +2 -2
- package/src/lib/composables/useEdgePanel.ts +45 -54
- package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
- package/src/lib/composables/usePhotoViewer.ts +2 -3
- package/src/lib/composables/usePieChart.ts +1 -1
- package/src/lib/composables/usePopover.ts +151 -139
- package/src/lib/composables/useSideMenu.ts +28 -41
- package/src/lib/composables/useSlider.ts +2 -6
- package/src/lib/composables/useTooltip.ts +2 -2
- package/src/lib/config/index.ts +39 -0
- package/src/lib/constants/components.ts +1 -0
- package/src/lib/theme/devtools/Comparator.tsx +1 -1
- package/src/lib/theme/devtools/Inspector.tsx +1 -1
- package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
- package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
- package/src/lib/types/components.ts +1 -0
- package/src/styles/01-settings/_index.scss +1 -0
- package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
- package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
- package/src/styles/02-tools/_tools.glass.scss +6 -0
- package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
- package/src/styles/06-components/_components.atomix-glass.scss +160 -99
- package/scripts/cli/__tests__/README.md +0 -81
- package/scripts/cli/__tests__/basic.test.js +0 -116
- package/scripts/cli/__tests__/clean.test.js +0 -278
- package/scripts/cli/__tests__/component-generator.test.js +0 -332
- package/scripts/cli/__tests__/component-validator.test.js +0 -433
- package/scripts/cli/__tests__/generator.test.js +0 -613
- package/scripts/cli/__tests__/glass-motion.test.js +0 -256
- package/scripts/cli/__tests__/integration.test.js +0 -938
- package/scripts/cli/__tests__/migrate.test.js +0 -74
- package/scripts/cli/__tests__/security.test.js +0 -206
- package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
- package/scripts/cli/__tests__/token-manager.test.js +0 -251
- package/scripts/cli/__tests__/token-provider.test.js +0 -361
- package/scripts/cli/__tests__/utils.test.js +0 -165
- package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
- package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
- package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
- package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
- package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
- package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
- package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
- package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
- package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
- package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
- package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
- package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
- package/src/components/TypedButton/TypedButton.tsx +0 -39
- package/src/components/TypedButton/index.ts +0 -2
- package/src/lib/composables/useBreadcrumb.ts +0 -81
- package/src/lib/composables/useChartInteractions.ts +0 -123
- package/src/lib/composables/useChartPerformance.ts +0 -347
- package/src/lib/composables/useDropdown.ts +0 -338
- package/src/lib/composables/useModal.ts +0 -110
- package/src/lib/composables/useTypedButton.ts +0 -66
- package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
- package/src/lib/utils/displacement-generator.ts +0 -92
- package/src/lib/utils/memoryMonitor.ts +0 -191
- package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
- package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
- package/src/styles/06-components/_components.testbutton.scss +0 -212
- package/src/styles/06-components/_components.testtypecheck.scss +0 -212
- package/src/styles/06-components/_components.typedbutton.scss +0 -212
|
@@ -1,938 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Integration Tests
|
|
3
|
-
* Comprehensive test coverage for generate command with options, validation, and edge cases
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
-
import { mkdtemp, rm, writeFile, mkdir, readFile } from 'fs/promises';
|
|
8
|
-
import { existsSync, readFileSync } from 'fs';
|
|
9
|
-
import { join, resolve } from 'path';
|
|
10
|
-
import { tmpdir } from 'os';
|
|
11
|
-
import { execSync } from 'child_process';
|
|
12
|
-
|
|
13
|
-
// Mock console to avoid noise in tests
|
|
14
|
-
const originalConsole = global.console;
|
|
15
|
-
|
|
16
|
-
describe('CLI Integration Tests', () => {
|
|
17
|
-
let tempDir;
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
tempDir = await mkdtemp(join(tmpdir(), 'atomix-cli-test-'));
|
|
21
|
-
|
|
22
|
-
// Create project structure with React indicators for proper framework detection
|
|
23
|
-
await mkdir(join(tempDir, 'src/styles/01-settings'), { recursive: true });
|
|
24
|
-
await mkdir(join(tempDir, 'src/lib/composables'), { recursive: true });
|
|
25
|
-
await mkdir(join(tempDir, 'src/lib/types'), { recursive: true });
|
|
26
|
-
await writeFile(join(tempDir, 'package.json'), JSON.stringify({
|
|
27
|
-
name: 'test-app',
|
|
28
|
-
version: '1.0.0',
|
|
29
|
-
dependencies: {
|
|
30
|
-
react: '^18.0.0'
|
|
31
|
-
}
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
// Mock console methods
|
|
35
|
-
global.console = {
|
|
36
|
-
...originalConsole,
|
|
37
|
-
log: vi.fn(),
|
|
38
|
-
error: vi.fn(),
|
|
39
|
-
warn: vi.fn()
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(async () => {
|
|
44
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
45
|
-
global.console = originalConsole;
|
|
46
|
-
vi.clearAllMocks();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('atomix generate component', () => {
|
|
50
|
-
it('should generate a basic component', () => {
|
|
51
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
52
|
-
|
|
53
|
-
execSync(`node ${cliPath} generate component TestButton`, {
|
|
54
|
-
cwd: tempDir,
|
|
55
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
56
|
-
stdio: 'pipe'
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const componentDir = join(tempDir, 'src', 'components', 'TestButton');
|
|
60
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
61
|
-
const hasComponentFile = existsSync(join(componentDir, 'TestButton.tsx')) ||
|
|
62
|
-
existsSync(join(componentDir, 'TestButton.jsx')) ||
|
|
63
|
-
existsSync(join(componentDir, 'TestButton.html'));
|
|
64
|
-
expect(hasComponentFile).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should reject invalid component names', () => {
|
|
68
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
69
|
-
|
|
70
|
-
let threw = false;
|
|
71
|
-
try {
|
|
72
|
-
execSync(`node ${cliPath} generate component 123Invalid`, {
|
|
73
|
-
cwd: tempDir,
|
|
74
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
75
|
-
stdio: 'pipe'
|
|
76
|
-
});
|
|
77
|
-
} catch {
|
|
78
|
-
threw = true;
|
|
79
|
-
}
|
|
80
|
-
expect(threw).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should create component directory when generating', () => {
|
|
84
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
85
|
-
execSync(`node ${cliPath} generate component DuplicateButton`, {
|
|
86
|
-
cwd: tempDir,
|
|
87
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
88
|
-
stdio: 'pipe'
|
|
89
|
-
});
|
|
90
|
-
const componentDir = join(tempDir, 'src', 'components', 'DuplicateButton');
|
|
91
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('atomix generate component --complexity', () => {
|
|
96
|
-
it('should generate simple component', () => {
|
|
97
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
98
|
-
|
|
99
|
-
execSync(`node ${cliPath} generate component SimpleInput --complexity simple`, {
|
|
100
|
-
cwd: tempDir,
|
|
101
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
102
|
-
stdio: 'pipe'
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const componentDir = join(tempDir, 'src', 'components', 'SimpleInput');
|
|
106
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should generate complex component', () => {
|
|
110
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
111
|
-
|
|
112
|
-
execSync(`node ${cliPath} generate component ComplexCard --complexity complex`, {
|
|
113
|
-
cwd: tempDir,
|
|
114
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
115
|
-
stdio: 'pipe'
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const componentDir = join(tempDir, 'src', 'components', 'ComplexCard');
|
|
119
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should handle invalid complexity level', () => {
|
|
123
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
124
|
-
|
|
125
|
-
let threw = false;
|
|
126
|
-
try {
|
|
127
|
-
execSync(`node ${cliPath} generate component Test --complexity extreme`, {
|
|
128
|
-
cwd: tempDir,
|
|
129
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
130
|
-
stdio: 'pipe'
|
|
131
|
-
});
|
|
132
|
-
} catch (error) {
|
|
133
|
-
threw = true;
|
|
134
|
-
// Verify error message contains helpful suggestions
|
|
135
|
-
if (error.stderr) {
|
|
136
|
-
const errorMsg = error.stderr.toString();
|
|
137
|
-
expect(errorMsg.toLowerCase()).toContain('complexity');
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
expect(threw).toBe(true);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('atomix generate component --features', () => {
|
|
145
|
-
it('should generate with storybook by default', () => {
|
|
146
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
147
|
-
|
|
148
|
-
execSync(`node ${cliPath} generate component StoryButton`, {
|
|
149
|
-
cwd: tempDir,
|
|
150
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
151
|
-
stdio: 'pipe'
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const componentDir = join(tempDir, 'src', 'components', 'StoryButton');
|
|
155
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
156
|
-
// Story file should be created by default
|
|
157
|
-
expect(existsSync(join(componentDir, 'StoryButton.stories.tsx'))).toBe(true);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should generate with tests when using complexity complex', () => {
|
|
161
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
162
|
-
|
|
163
|
-
execSync(`node ${cliPath} generate component ButtonWithTests --complexity complex`, {
|
|
164
|
-
cwd: tempDir,
|
|
165
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
166
|
-
stdio: 'pipe'
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const componentDir = join(tempDir, 'src', 'components', 'ButtonWithTests');
|
|
170
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
171
|
-
// Complex components may include tests
|
|
172
|
-
expect(existsSync(join(componentDir, 'ButtonWithTests.test.tsx')) || existsSync(join(componentDir, 'ButtonWithTests.tsx'))).toBe(true);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('should generate hook by default', () => {
|
|
176
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
177
|
-
|
|
178
|
-
execSync(`node ${cliPath} generate component WithHook`, {
|
|
179
|
-
cwd: tempDir,
|
|
180
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
181
|
-
stdio: 'pipe'
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// Hook is generated by default to composables directory
|
|
185
|
-
const hookFile = join(tempDir, 'src', 'lib', 'composables', 'useWithHook.ts');
|
|
186
|
-
expect(existsSync(hookFile)).toBe(true);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('should generate styles by default', () => {
|
|
190
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
191
|
-
|
|
192
|
-
execSync(`node ${cliPath} generate component WithStyles`, {
|
|
193
|
-
cwd: tempDir,
|
|
194
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
195
|
-
stdio: 'pipe'
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// Styles are generated by default (check for any settings file)
|
|
199
|
-
const settingsDir = join(tempDir, 'src', 'styles', '01-settings');
|
|
200
|
-
const hasSettingsFile = existsSync(settingsDir) &&
|
|
201
|
-
existsSync(join(settingsDir, '_settings.withstyles.scss')) ||
|
|
202
|
-
existsSync(join(settingsDir, '_settings.with-styles.scss'));
|
|
203
|
-
expect(hasSettingsFile).toBe(true);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('Feature Flag Combinations', () => {
|
|
208
|
-
it('should generate with default features (storybook, hook, styles)', () => {
|
|
209
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
210
|
-
|
|
211
|
-
execSync(`node ${cliPath} generate component DefaultFeatures`, {
|
|
212
|
-
cwd: tempDir,
|
|
213
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
214
|
-
stdio: 'pipe'
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const componentDir = join(tempDir, 'src', 'components', 'DefaultFeatures');
|
|
218
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
219
|
-
expect(existsSync(join(componentDir, 'DefaultFeatures.tsx'))).toBe(true);
|
|
220
|
-
expect(existsSync(join(componentDir, 'DefaultFeatures.stories.tsx'))).toBe(true);
|
|
221
|
-
expect(existsSync(join(tempDir, 'src', 'lib', 'composables', 'useDefaultFeatures.ts'))).toBe(true);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should support --complexity flag', () => {
|
|
225
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
226
|
-
|
|
227
|
-
execSync(`node ${cliPath} generate component SimpleComponent --complexity simple`, {
|
|
228
|
-
cwd: tempDir,
|
|
229
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
230
|
-
stdio: 'pipe'
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const componentDir = join(tempDir, 'src', 'components', 'SimpleComponent');
|
|
234
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('should support --validate flag', () => {
|
|
238
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
239
|
-
|
|
240
|
-
execSync(`node ${cliPath} generate component ValidatedComponent --validate`, {
|
|
241
|
-
cwd: tempDir,
|
|
242
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
243
|
-
stdio: 'pipe'
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const componentDir = join(tempDir, 'src', 'components', 'ValidatedComponent');
|
|
247
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should support --path flag for custom output location', () => {
|
|
251
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
252
|
-
const customPath = join(tempDir, 'custom', 'components');
|
|
253
|
-
|
|
254
|
-
execSync(`node ${cliPath} generate component CustomPath --path ${customPath}`, {
|
|
255
|
-
cwd: tempDir,
|
|
256
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
257
|
-
stdio: 'pipe'
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
const componentDir = join(customPath, 'CustomPath');
|
|
261
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
262
|
-
expect(existsSync(join(componentDir, 'CustomPath.tsx'))).toBe(true);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('should support --prompt flag for AI generation', () => {
|
|
266
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
267
|
-
|
|
268
|
-
// This would test AI generation if AI is configured
|
|
269
|
-
// For now, just verify the flag is accepted
|
|
270
|
-
let errorOutput = '';
|
|
271
|
-
try {
|
|
272
|
-
execSync(`node ${cliPath} generate component AITest --prompt "A simple button"`, {
|
|
273
|
-
cwd: tempDir,
|
|
274
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true', ATOMIX_AI_MOCK: 'true' },
|
|
275
|
-
stdio: 'pipe'
|
|
276
|
-
});
|
|
277
|
-
} catch (error) {
|
|
278
|
-
errorOutput = error.stderr ? error.stderr.toString() : '';
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// May fail if AI not configured, but flag should be recognized
|
|
282
|
-
expect(errorOutput).not.toMatch(/unknown option.*--prompt/i);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe('atomix generate component --interactive', () => {
|
|
287
|
-
it('should generate component via interactive prompts', async () => {
|
|
288
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
289
|
-
|
|
290
|
-
// Interactive mode with default selections (pressing Enter for defaults)
|
|
291
|
-
const mockInput = '\n\n\n';
|
|
292
|
-
|
|
293
|
-
execSync(`echo -e "InteractiveButton${mockInput}" | node ${cliPath} generate component --interactive`, {
|
|
294
|
-
cwd: tempDir,
|
|
295
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
296
|
-
stdio: 'pipe'
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const componentDir = join(tempDir, 'src', 'components', 'InteractiveButton');
|
|
300
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
301
|
-
expect(existsSync(join(componentDir, 'InteractiveButton.tsx'))).toBe(true);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('should validate component name in interactive mode', () => {
|
|
305
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
306
|
-
|
|
307
|
-
// Invalid name starting with number should be rejected
|
|
308
|
-
let threw = false;
|
|
309
|
-
try {
|
|
310
|
-
execSync(`echo -e "123Invalid\n" | node ${cliPath} generate component --interactive`, {
|
|
311
|
-
cwd: tempDir,
|
|
312
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
313
|
-
stdio: 'pipe'
|
|
314
|
-
});
|
|
315
|
-
} catch (error) {
|
|
316
|
-
threw = true;
|
|
317
|
-
if (error.stderr) {
|
|
318
|
-
const errorMsg = error.stderr.toString();
|
|
319
|
-
expect(errorMsg.toLowerCase()).toMatch(/(pascalcase|invalid|name)/);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
// May timeout or fail, both are acceptable
|
|
323
|
-
expect(threw || true).toBe(true);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
it('should accept complexity selection in interactive mode', async () => {
|
|
327
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
328
|
-
|
|
329
|
-
// Select simple complexity (first option)
|
|
330
|
-
execSync(`echo -e "SimpleComponent\n0\n\n" | node ${cliPath} generate component --interactive`, {
|
|
331
|
-
cwd: tempDir,
|
|
332
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
333
|
-
stdio: 'pipe'
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
const componentDir = join(tempDir, 'src', 'components', 'SimpleComponent');
|
|
337
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('should allow feature toggling in interactive mode', async () => {
|
|
341
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
342
|
-
|
|
343
|
-
// Enable tests feature
|
|
344
|
-
execSync(`echo -e "ComponentWithTests\n\n \n" | node ${cliPath} generate component --interactive`, {
|
|
345
|
-
cwd: tempDir,
|
|
346
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
347
|
-
stdio: 'pipe'
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
const componentDir = join(tempDir, 'src', 'components', 'ComponentWithTests');
|
|
351
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
352
|
-
expect(existsSync(join(componentDir, 'ComponentWithTests.test.tsx'))).toBe(true);
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe('atomix generate token', () => {
|
|
357
|
-
it('should run generate token (creates component by current implementation)', () => {
|
|
358
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
359
|
-
execSync(`node ${cliPath} generate token colors`, {
|
|
360
|
-
cwd: tempDir,
|
|
361
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
362
|
-
stdio: 'pipe'
|
|
363
|
-
});
|
|
364
|
-
const componentDir = join(tempDir, 'src', 'components', 'Colors');
|
|
365
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it('should reject invalid token category name for component validation', () => {
|
|
369
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
370
|
-
expect(() => {
|
|
371
|
-
execSync(`node ${cliPath} generate token 123invalid`, {
|
|
372
|
-
cwd: tempDir,
|
|
373
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
374
|
-
stdio: 'pipe'
|
|
375
|
-
});
|
|
376
|
-
}).toThrow();
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
describe('atomix generate component - Output Quality Verification', () => {
|
|
381
|
-
|
|
382
|
-
it('should generate component with TypeScript type definitions', () => {
|
|
383
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
384
|
-
|
|
385
|
-
execSync(`node ${cliPath} generate component TypedButton`, {
|
|
386
|
-
cwd: tempDir,
|
|
387
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
388
|
-
stdio: 'pipe'
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
const componentFile = join(tempDir, 'src', 'components', 'TypedButton', 'TypedButton.tsx');
|
|
392
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
393
|
-
|
|
394
|
-
// Verify TypeScript types are imported and used
|
|
395
|
-
expect(content).toMatch(/import\s+type\s+.*TypedButtonProps/);
|
|
396
|
-
expect(content).toContain('forwardRef<');
|
|
397
|
-
// Verify type is used in forwardRef generic
|
|
398
|
-
expect(content).toMatch(/forwardRef<\s*\w+\s*,\s*\w+Props\s*>/);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('should generate component with forwardRef implementation', () => {
|
|
402
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
403
|
-
|
|
404
|
-
execSync(`node ${cliPath} generate component RefForwardingButton`, {
|
|
405
|
-
cwd: tempDir,
|
|
406
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
407
|
-
stdio: 'pipe'
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
const componentFile = join(tempDir, 'src', 'components', 'RefForwardingButton', 'RefForwardingButton.tsx');
|
|
411
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
412
|
-
|
|
413
|
-
expect(content).toContain('forwardRef');
|
|
414
|
-
expect(content).toMatch(/forwardRef\s*<\s*\w+/);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
it('should generate component with displayName property', () => {
|
|
418
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
419
|
-
|
|
420
|
-
execSync(`node ${cliPath} generate component DisplayNamedComponent`, {
|
|
421
|
-
cwd: tempDir,
|
|
422
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
423
|
-
stdio: 'pipe'
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
const componentFile = join(tempDir, 'src', 'components', 'DisplayNamedComponent', 'DisplayNamedComponent.tsx');
|
|
427
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
428
|
-
|
|
429
|
-
expect(content).toContain('displayName');
|
|
430
|
-
expect(content).toMatch(/\.displayName\s*=\s*['"]DisplayNamedComponent['"]/);
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
it('should generate component without hardcoded hex colors', () => {
|
|
434
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
435
|
-
|
|
436
|
-
execSync(`node ${cliPath} generate component ThemedComponent`, {
|
|
437
|
-
cwd: tempDir,
|
|
438
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
439
|
-
stdio: 'pipe'
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
const componentFile = join(tempDir, 'src', 'components', 'ThemedComponent', 'ThemedComponent.tsx');
|
|
443
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
444
|
-
|
|
445
|
-
// Should not contain hex color codes (basic check)
|
|
446
|
-
expect(content).not.toMatch(/#[0-9a-fA-F]{6}/);
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
it('should generate component with JSDoc documentation', () => {
|
|
450
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
451
|
-
|
|
452
|
-
execSync(`node ${cliPath} generate component DocumentedComponent`, {
|
|
453
|
-
cwd: tempDir,
|
|
454
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
455
|
-
stdio: 'pipe'
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
const componentFile = join(tempDir, 'src', 'components', 'DocumentedComponent', 'DocumentedComponent.tsx');
|
|
459
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
460
|
-
|
|
461
|
-
expect(content).toContain('/**');
|
|
462
|
-
expect(content).toContain('*/');
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
it('should generate composable hook with proper typing', () => {
|
|
466
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
467
|
-
|
|
468
|
-
execSync(`node ${cliPath} generate component HookComponent`, {
|
|
469
|
-
cwd: tempDir,
|
|
470
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
471
|
-
stdio: 'pipe'
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
const hookFile = join(tempDir, 'src', 'lib', 'composables', 'useHookComponent.ts');
|
|
475
|
-
const content = readFileSync(hookFile, 'utf8');
|
|
476
|
-
|
|
477
|
-
expect(content).toContain('export function useHookComponent');
|
|
478
|
-
// Should import and use typed props
|
|
479
|
-
expect(content).toMatch(/import\s+.*HookComponentProps/);
|
|
480
|
-
// Should have parameter type annotation
|
|
481
|
-
expect(content).toMatch(/initialProps\??:\s*Partial<HookComponentProps>/);
|
|
482
|
-
// Should return typed object
|
|
483
|
-
expect(content).toMatch(/return\s*\{/);
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('should generate Storybook story with args and argTypes', () => {
|
|
487
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
488
|
-
|
|
489
|
-
execSync(`node ${cliPath} generate component StoryComponent`, {
|
|
490
|
-
cwd: tempDir,
|
|
491
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
492
|
-
stdio: 'pipe'
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
const storyFile = join(tempDir, 'src', 'components', 'StoryComponent', 'StoryComponent.stories.tsx');
|
|
496
|
-
const content = readFileSync(storyFile, 'utf8');
|
|
497
|
-
|
|
498
|
-
expect(content).toContain('args:');
|
|
499
|
-
expect(content).toContain('argTypes:');
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
it('should generate test file with basic structure when complexity is complex', () => {
|
|
503
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
504
|
-
|
|
505
|
-
execSync(`node ${cliPath} generate component TestableComponent --complexity complex`, {
|
|
506
|
-
cwd: tempDir,
|
|
507
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
508
|
-
stdio: 'pipe'
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
const componentDir = join(tempDir, 'src', 'components', 'TestableComponent');
|
|
512
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
513
|
-
|
|
514
|
-
const testFile = join(componentDir, 'TestableComponent.test.tsx');
|
|
515
|
-
if (existsSync(testFile)) {
|
|
516
|
-
const content = readFileSync(testFile, 'utf8');
|
|
517
|
-
expect(content).toMatch(/(describe|it|test)\s*\(/);
|
|
518
|
-
expect(content).toContain('TestableComponent');
|
|
519
|
-
}
|
|
520
|
-
// Test file may or may not exist depending on complexity level
|
|
521
|
-
expect(true).toBe(true);
|
|
522
|
-
});
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
describe('Error Scenarios', () => {
|
|
526
|
-
it('should handle existing component directory', () => {
|
|
527
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
528
|
-
|
|
529
|
-
// Generate first component
|
|
530
|
-
execSync(`node ${cliPath} generate component ExistingComponent`, {
|
|
531
|
-
cwd: tempDir,
|
|
532
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
533
|
-
stdio: 'pipe'
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// Try to generate again - should either overwrite or fail gracefully
|
|
537
|
-
// Current implementation may overwrite, this documents expected behavior
|
|
538
|
-
expect(() => {
|
|
539
|
-
execSync(`node ${cliPath} generate component ExistingComponent`, {
|
|
540
|
-
cwd: tempDir,
|
|
541
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
542
|
-
stdio: 'pipe'
|
|
543
|
-
});
|
|
544
|
-
}).not.toThrow();
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
it('should handle permission errors', () => {
|
|
548
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
549
|
-
|
|
550
|
-
// Create read-only directory (Unix-like systems)
|
|
551
|
-
const readOnlyDir = join(tempDir, 'readonly');
|
|
552
|
-
mkdir(readOnlyDir, { recursive: true });
|
|
553
|
-
|
|
554
|
-
// This test is platform-specific and may not work on all systems
|
|
555
|
-
expect(true).toBe(true);
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
it('should handle invalid output path', () => {
|
|
559
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
560
|
-
|
|
561
|
-
let threw = false;
|
|
562
|
-
try {
|
|
563
|
-
execSync(`node ${cliPath} generate component Test --path /nonexistent/path/that/does/not/exist`, {
|
|
564
|
-
cwd: tempDir,
|
|
565
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
566
|
-
stdio: 'pipe'
|
|
567
|
-
});
|
|
568
|
-
} catch (error) {
|
|
569
|
-
threw = true;
|
|
570
|
-
// Verify error message contains helpful suggestions
|
|
571
|
-
if (error.stderr) {
|
|
572
|
-
const errorMsg = error.stderr.toString();
|
|
573
|
-
expect(errorMsg.toLowerCase()).toContain('path');
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
expect(threw).toBe(true);
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
it('should display actionable error suggestions', () => {
|
|
580
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
581
|
-
|
|
582
|
-
let errorOutput = '';
|
|
583
|
-
try {
|
|
584
|
-
execSync(`node ${cliPath} generate component 123InvalidName`, {
|
|
585
|
-
cwd: tempDir,
|
|
586
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
587
|
-
stdio: 'pipe'
|
|
588
|
-
});
|
|
589
|
-
} catch (error) {
|
|
590
|
-
errorOutput = error.stderr ? error.stderr.toString() : '';
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// Error output should contain suggestions
|
|
594
|
-
expect(errorOutput).toBeTruthy();
|
|
595
|
-
// Suggestions typically include "Use PascalCase" etc.
|
|
596
|
-
expect(errorOutput.length).toBeGreaterThan(0);
|
|
597
|
-
});
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
describe('atomix generate component - Design Token Integration', () => {
|
|
601
|
-
beforeEach(async () => {
|
|
602
|
-
// Create design tokens fixture
|
|
603
|
-
const tokensDir = join(tempDir, 'design-tokens');
|
|
604
|
-
await mkdir(tokensDir, { recursive: true });
|
|
605
|
-
await writeFile(
|
|
606
|
-
join(tokensDir, 'tokens.json'),
|
|
607
|
-
JSON.stringify({
|
|
608
|
-
color: {
|
|
609
|
-
primary: { value: '#007bff', name: 'Primary Blue' },
|
|
610
|
-
secondary: { value: '#6c757d', name: 'Secondary Gray' }
|
|
611
|
-
},
|
|
612
|
-
spacing: {
|
|
613
|
-
sm: { value: '8px', name: 'Small Spacing' },
|
|
614
|
-
md: { value: '16px', name: 'Medium Spacing' },
|
|
615
|
-
lg: { value: '24px', name: 'Large Spacing' }
|
|
616
|
-
}
|
|
617
|
-
})
|
|
618
|
-
);
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
it('should load and reference design tokens in generated component', () => {
|
|
622
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
623
|
-
|
|
624
|
-
execSync(`node ${cliPath} generate component TokenAwareComponent`, {
|
|
625
|
-
cwd: tempDir,
|
|
626
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
627
|
-
stdio: 'pipe'
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
const componentFile = join(tempDir, 'src', 'components', 'TokenAwareComponent', 'TokenAwareComponent.tsx');
|
|
631
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
632
|
-
|
|
633
|
-
// Should reference design tokens or CSS variables
|
|
634
|
-
expect(content).toMatch(/(token\.|var\(--|theme\.|\$[a-zA-Z])/);
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
it('should generate ITCSS settings file with SCSS variables', () => {
|
|
638
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
639
|
-
|
|
640
|
-
execSync(`node ${cliPath} generate component ScssComponent`, {
|
|
641
|
-
cwd: tempDir,
|
|
642
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
643
|
-
stdio: 'pipe'
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
const settingsFiles = [
|
|
647
|
-
join(tempDir, 'src', 'styles', '01-settings', '_settings.scsscomponent.scss'),
|
|
648
|
-
join(tempDir, 'src', 'styles', '01-settings', '_settings.scss-component.scss'),
|
|
649
|
-
join(tempDir, 'src', 'styles', '01-settings', '_settings.scss_component.scss')
|
|
650
|
-
];
|
|
651
|
-
|
|
652
|
-
const existingFile = settingsFiles.find(f => existsSync(f));
|
|
653
|
-
expect(existingFile).toBeTruthy();
|
|
654
|
-
|
|
655
|
-
if (existingFile) {
|
|
656
|
-
const content = readFileSync(existingFile, 'utf8');
|
|
657
|
-
expect(content).toMatch(/\$[a-zA-Z]/); // SCSS variables
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
it('should generate ITCSS component styles layer', () => {
|
|
662
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
663
|
-
|
|
664
|
-
execSync(`node ${cliPath} generate component StyledComponent`, {
|
|
665
|
-
cwd: tempDir,
|
|
666
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
667
|
-
stdio: 'pipe'
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
const componentsFiles = [
|
|
671
|
-
join(tempDir, 'src', 'styles', '06-components', '_components.styledcomponent.scss'),
|
|
672
|
-
join(tempDir, 'src', 'styles', '06-components', '_components.styled-component.scss'),
|
|
673
|
-
join(tempDir, 'src', 'styles', '06-components', '_components.styled_component.scss')
|
|
674
|
-
];
|
|
675
|
-
|
|
676
|
-
const existingFile = componentsFiles.find(f => existsSync(f));
|
|
677
|
-
expect(existingFile).toBeTruthy();
|
|
678
|
-
|
|
679
|
-
if (existingFile) {
|
|
680
|
-
const content = readFileSync(existingFile, 'utf8');
|
|
681
|
-
// Should contain CSS class definitions
|
|
682
|
-
expect(content).toMatch(/\.[a-zA-Z]/);
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
describe('atomix validate', () => {
|
|
688
|
-
beforeEach(async () => {
|
|
689
|
-
const stylesDir = join(tempDir, 'src', 'styles', '01-settings');
|
|
690
|
-
await mkdir(stylesDir, { recursive: true });
|
|
691
|
-
await writeFile(join(stylesDir, '_settings.colors.scss'), '$primary: blue !default;\n$secondary: red !default;');
|
|
692
|
-
await writeFile(join(stylesDir, '_settings.typography.scss'), '$font-base: sans-serif !default;');
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
it('should run full validation audit', () => {
|
|
696
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
697
|
-
const result = execSync(`node ${cliPath} validate`, {
|
|
698
|
-
cwd: tempDir,
|
|
699
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
700
|
-
encoding: 'utf8',
|
|
701
|
-
stdio: 'pipe'
|
|
702
|
-
});
|
|
703
|
-
expect(result).toMatch(/No issues found|Summary:|Quality Audit|issues?/);
|
|
704
|
-
});
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
describe('atomix doctor', () => {
|
|
708
|
-
it('should run system diagnostics', () => {
|
|
709
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
710
|
-
const result = execSync(`node ${cliPath} doctor`, {
|
|
711
|
-
cwd: tempDir,
|
|
712
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
713
|
-
encoding: 'utf8',
|
|
714
|
-
stdio: 'pipe'
|
|
715
|
-
});
|
|
716
|
-
expect(result).toContain('Atomix Diagnostic Report');
|
|
717
|
-
});
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
describe('Error Handling', () => {
|
|
721
|
-
it('should handle missing command gracefully', () => {
|
|
722
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
723
|
-
|
|
724
|
-
expect(() => {
|
|
725
|
-
execSync(`node ${cliPath} non-existent-command`, {
|
|
726
|
-
cwd: tempDir,
|
|
727
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
728
|
-
stdio: 'pipe'
|
|
729
|
-
});
|
|
730
|
-
}).toThrow();
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
it('should show help for --help flag', () => {
|
|
734
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
735
|
-
|
|
736
|
-
const result = execSync(`node ${cliPath} --help`, {
|
|
737
|
-
cwd: tempDir,
|
|
738
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
739
|
-
encoding: 'utf8',
|
|
740
|
-
stdio: 'pipe'
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
expect(result).toContain('Atomix Design System CLI');
|
|
744
|
-
expect(result).toContain('Commands:');
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
it('should show version for --version flag', () => {
|
|
748
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
749
|
-
|
|
750
|
-
const result = execSync(`node ${cliPath} --version`, {
|
|
751
|
-
cwd: tempDir,
|
|
752
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
753
|
-
encoding: 'utf8',
|
|
754
|
-
stdio: 'pipe'
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
expect(result).toMatch(/\d+\.\d+\.\d+/);
|
|
758
|
-
});
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
describe('Framework-Specific Generation', () => {
|
|
762
|
-
it('should detect React project and generate .tsx files', async () => {
|
|
763
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
764
|
-
|
|
765
|
-
// Create React project indicator
|
|
766
|
-
await writeFile(join(tempDir, 'package.json'), JSON.stringify({
|
|
767
|
-
name: 'react-app',
|
|
768
|
-
dependencies: { react: '^18.0.0' }
|
|
769
|
-
}));
|
|
770
|
-
|
|
771
|
-
execSync(`node ${cliPath} generate component ReactComponent`, {
|
|
772
|
-
cwd: tempDir,
|
|
773
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
774
|
-
stdio: 'pipe'
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
const componentFile = join(tempDir, 'src', 'components', 'ReactComponent', 'ReactComponent.tsx');
|
|
778
|
-
expect(existsSync(componentFile)).toBe(true);
|
|
779
|
-
|
|
780
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
781
|
-
expect(content).toContain('import React');
|
|
782
|
-
expect(content).toContain('forwardRef');
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
it('should detect Next.js project and generate compatible components', async () => {
|
|
786
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
787
|
-
|
|
788
|
-
// Create Next.js project indicator
|
|
789
|
-
await writeFile(join(tempDir, 'package.json'), JSON.stringify({
|
|
790
|
-
name: 'next-app',
|
|
791
|
-
dependencies: { next: '^14.0.0', react: '^18.0.0' }
|
|
792
|
-
}));
|
|
793
|
-
|
|
794
|
-
execSync(`node ${cliPath} generate component NextComponent`, {
|
|
795
|
-
cwd: tempDir,
|
|
796
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
797
|
-
stdio: 'pipe'
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
const componentFile = join(tempDir, 'src', 'components', 'NextComponent', 'NextComponent.tsx');
|
|
801
|
-
expect(existsSync(componentFile)).toBe(true);
|
|
802
|
-
|
|
803
|
-
const content = readFileSync(componentFile, 'utf8');
|
|
804
|
-
// Next.js components may use 'use client' directive
|
|
805
|
-
expect(content).toMatch(/('use client'|export.*)/);
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
it('should detect vanilla JS project and generate .html/.js files', async () => {
|
|
809
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
810
|
-
|
|
811
|
-
// Create vanilla project indicator (no React/Next)
|
|
812
|
-
await writeFile(join(tempDir, 'package.json'), JSON.stringify({
|
|
813
|
-
name: 'vanilla-app'
|
|
814
|
-
}));
|
|
815
|
-
|
|
816
|
-
execSync(`node ${cliPath} generate component VanillaComponent`, {
|
|
817
|
-
cwd: tempDir,
|
|
818
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
819
|
-
stdio: 'pipe'
|
|
820
|
-
});
|
|
821
|
-
|
|
822
|
-
const componentDir = join(tempDir, 'src', 'components', 'VanillaComponent');
|
|
823
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
824
|
-
|
|
825
|
-
// Should generate HTML or JS instead of TSX
|
|
826
|
-
const hasHtml = existsSync(join(componentDir, 'VanillaComponent.html'));
|
|
827
|
-
const hasJs = existsSync(join(componentDir, 'VanillaComponent.js'));
|
|
828
|
-
expect(hasHtml || hasJs).toBe(true);
|
|
829
|
-
});
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
describe('Edge Cases and Security', () => {
|
|
833
|
-
it('should handle very long component names', () => {
|
|
834
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
835
|
-
const longName = 'VeryLongComponentNameThatTestsFileNameLimitsAndPathHandlingCapabilities';
|
|
836
|
-
|
|
837
|
-
execSync(`node ${cliPath} generate component ${longName}`, {
|
|
838
|
-
cwd: tempDir,
|
|
839
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
840
|
-
stdio: 'pipe'
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
const componentDir = join(tempDir, 'src', 'components', longName);
|
|
844
|
-
expect(existsSync(componentDir)).toBe(true);
|
|
845
|
-
});
|
|
846
|
-
|
|
847
|
-
it('should reject component names with special characters', () => {
|
|
848
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
849
|
-
|
|
850
|
-
let threw = false;
|
|
851
|
-
try {
|
|
852
|
-
execSync(`node ${cliPath} generate component "Special@Component"`, {
|
|
853
|
-
cwd: tempDir,
|
|
854
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
855
|
-
stdio: 'pipe'
|
|
856
|
-
});
|
|
857
|
-
} catch (error) {
|
|
858
|
-
threw = true;
|
|
859
|
-
if (error.stderr) {
|
|
860
|
-
const errorMsg = error.stderr.toString();
|
|
861
|
-
expect(errorMsg.toLowerCase()).toMatch(/(invalid|special|character)/);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
expect(threw).toBe(true);
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
it('should handle component names with spaces in quotes', () => {
|
|
868
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
869
|
-
|
|
870
|
-
// Spaces should be rejected or sanitized
|
|
871
|
-
let threw = false;
|
|
872
|
-
try {
|
|
873
|
-
execSync(`node ${cliPath} generate component "Invalid Name"`, {
|
|
874
|
-
cwd: tempDir,
|
|
875
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
876
|
-
stdio: 'pipe'
|
|
877
|
-
});
|
|
878
|
-
} catch (error) {
|
|
879
|
-
threw = true;
|
|
880
|
-
}
|
|
881
|
-
expect(threw).toBe(true);
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
it('should prevent path traversal attacks', () => {
|
|
885
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
886
|
-
|
|
887
|
-
let threw = false;
|
|
888
|
-
try {
|
|
889
|
-
execSync(`node ${cliPath} generate component Test --path ../../etc`, {
|
|
890
|
-
cwd: tempDir,
|
|
891
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
892
|
-
stdio: 'pipe'
|
|
893
|
-
});
|
|
894
|
-
} catch (error) {
|
|
895
|
-
threw = true;
|
|
896
|
-
if (error.stderr) {
|
|
897
|
-
const errorMsg = error.stderr.toString();
|
|
898
|
-
expect(errorMsg.toLowerCase()).toMatch(/(path|traversal|security|outside)/);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
expect(threw).toBe(true);
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
it('should handle reserved JavaScript words', () => {
|
|
905
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
906
|
-
|
|
907
|
-
// Reserved words should be rejected or handled gracefully
|
|
908
|
-
let threw = false;
|
|
909
|
-
try {
|
|
910
|
-
execSync(`node ${cliPath} generate component "div"`, {
|
|
911
|
-
cwd: tempDir,
|
|
912
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
913
|
-
stdio: 'pipe'
|
|
914
|
-
});
|
|
915
|
-
} catch (error) {
|
|
916
|
-
threw = true;
|
|
917
|
-
}
|
|
918
|
-
// May or may not throw depending on implementation
|
|
919
|
-
expect(threw || true).toBe(true);
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
it('should handle camelCase component names by converting to PascalCase', () => {
|
|
923
|
-
const cliPath = resolve(__dirname, '../../atomix-cli.js');
|
|
924
|
-
|
|
925
|
-
execSync(`node ${cliPath} generate component camelCaseComponent`, {
|
|
926
|
-
cwd: tempDir,
|
|
927
|
-
env: { ...process.env, ATOMIX_SKIP_DEP_CHECK: 'true' },
|
|
928
|
-
stdio: 'pipe'
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
// Should create directory with PascalCase
|
|
932
|
-
const pascalCaseDir = join(tempDir, 'src', 'components', 'CamelCaseComponent');
|
|
933
|
-
const camelCaseDir = join(tempDir, 'src', 'components', 'camelCaseComponent');
|
|
934
|
-
|
|
935
|
-
expect(existsSync(pascalCaseDir) || existsSync(camelCaseDir)).toBe(true);
|
|
936
|
-
});
|
|
937
|
-
});
|
|
938
|
-
});
|