@bookklik/senangstart-css 0.2.10 → 0.2.12

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 (39) hide show
  1. package/.agent/skills/add-utility/SKILL.md +65 -0
  2. package/.agent/workflows/add-utility.md +2 -0
  3. package/.agent/workflows/build.md +2 -0
  4. package/.agent/workflows/dev.md +2 -0
  5. package/AGENTS.md +30 -0
  6. package/dist/senangstart-css.js +362 -151
  7. package/dist/senangstart-css.min.js +175 -174
  8. package/dist/senangstart-tw.js +4 -4
  9. package/dist/senangstart-tw.min.js +1 -1
  10. package/docs/ms/reference/visual/ring-color.md +2 -2
  11. package/docs/ms/reference/visual/ring-offset.md +3 -3
  12. package/docs/ms/reference/visual/ring.md +5 -5
  13. package/docs/public/assets/senangstart-css.min.js +175 -174
  14. package/docs/public/llms.txt +10 -10
  15. package/docs/reference/visual/ring-color.md +2 -2
  16. package/docs/reference/visual/ring-offset.md +3 -3
  17. package/docs/reference/visual/ring.md +5 -5
  18. package/package.json +1 -1
  19. package/src/cdn/tw-conversion-engine.js +4 -4
  20. package/src/cli/commands/build.js +42 -14
  21. package/src/cli/commands/dev.js +157 -93
  22. package/src/compiler/generators/css.js +371 -199
  23. package/src/compiler/tokenizer.js +25 -23
  24. package/src/core/tokenizer-core.js +46 -19
  25. package/src/definitions/visual-borders.js +10 -10
  26. package/src/utils/common.js +456 -39
  27. package/src/utils/node-io.js +82 -0
  28. package/tests/integration/dev-recovery.test.js +231 -0
  29. package/tests/unit/cli/memory-limits.test.js +169 -0
  30. package/tests/unit/compiler/css-generation-error-handling.test.js +204 -0
  31. package/tests/unit/compiler/generators/css-errors.test.js +102 -0
  32. package/tests/unit/convert-tailwind.test.js +518 -442
  33. package/tests/unit/utils/common.test.js +376 -26
  34. package/tests/unit/utils/file-timeout.test.js +154 -0
  35. package/tests/unit/utils/theme-validation.test.js +181 -0
  36. package/tests/unit/compiler/generators/css.coverage.test.js +0 -833
  37. package/tests/unit/convert-tailwind.cli.test.js +0 -95
  38. package/tests/unit/security.test.js +0 -206
  39. /package/tests/unit/{convert-tailwind.coverage.test.js → convert-tailwind-edgecases.test.js} +0 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * SenangStart CSS - Node.js I/O Utilities
3
+ * Node-specific helper functions (not for browser use)
4
+ */
5
+
6
+ /**
7
+ * Read file with timeout protection
8
+ * @param {string} filePath - Path to file
9
+ * @param {number} timeoutMs - Timeout in milliseconds
10
+ * @returns {Promise<string>} - File contents
11
+ * @throws {Error} - On timeout or read failure
12
+ */
13
+ export async function readFileWithTimeout(filePath, timeoutMs = 5000) {
14
+ const { promises: fsPromises, statSync } = await import('fs');
15
+
16
+ // Check file size first
17
+ let fileSize;
18
+ try {
19
+ const stats = statSync(filePath);
20
+ fileSize = stats.size;
21
+
22
+ // Reject files larger than 10MB
23
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
24
+ if (fileSize > MAX_FILE_SIZE) {
25
+ throw new Error(`File too large: ${filePath} (${Math.round(fileSize / 1024)}KB)`);
26
+ }
27
+ } catch (error) {
28
+ throw new Error(`Cannot stat file: ${filePath} - ${error.message}`);
29
+ }
30
+
31
+ return new Promise((resolve, reject) => {
32
+ const timeout = setTimeout(() => {
33
+ reject(new Error(`Read timeout for ${filePath} (exceeded ${timeoutMs}ms)`));
34
+ }, timeoutMs);
35
+
36
+ fsPromises.readFile(filePath, 'utf-8')
37
+ .then((content) => {
38
+ clearTimeout(timeout);
39
+ resolve(content);
40
+ })
41
+ .catch((error) => {
42
+ clearTimeout(timeout);
43
+ reject(new Error(`Cannot read file: ${filePath} - ${error.message}`));
44
+ });
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Batch read multiple files with timeout protection
50
+ * @param {Array<string>} filePaths - Paths to files
51
+ * @param {number} timeoutMs - Timeout per file
52
+ * @returns {Promise<Array<{path: string, content: string, error: Error|null}>>} - File contents
53
+ */
54
+ export async function readMultipleFilesWithTimeout(filePaths, timeoutMs = 5000) {
55
+ const results = [];
56
+
57
+ // Read files in batches to avoid overwhelming the system
58
+ const BATCH_SIZE = 10;
59
+
60
+ for (let i = 0; i < filePaths.length; i += BATCH_SIZE) {
61
+ const batch = filePaths.slice(i, i + BATCH_SIZE);
62
+
63
+ const batchPromises = batch.map(async (filePath) => {
64
+ try {
65
+ const content = await readFileWithTimeout(filePath, timeoutMs);
66
+ return { path: filePath, content, error: null };
67
+ } catch (error) {
68
+ return { path: filePath, content: null, error };
69
+ }
70
+ });
71
+
72
+ const batchResults = await Promise.all(batchPromises);
73
+ results.push(...batchResults);
74
+ }
75
+
76
+ return results;
77
+ }
78
+
79
+ export default {
80
+ readFileWithTimeout,
81
+ readMultipleFilesWithTimeout
82
+ };
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Dev Mode Error Recovery Tests
3
+ * Tests for error recovery mechanisms in development mode
4
+ */
5
+
6
+ import { describe, it, mock } from 'node:test';
7
+ import assert from 'node:assert';
8
+
9
+ describe('Dev Mode Error Recovery', () => {
10
+ describe('Consecutive Error Tracking', () => {
11
+ it('tracks consecutive build failures', async () => {
12
+ let errorCount = 0;
13
+ const MAX_ERRORS = 5;
14
+
15
+ const mockBuild = async () => {
16
+ errorCount++;
17
+ if (errorCount < MAX_ERRORS) {
18
+ throw new Error('Simulated build error');
19
+ }
20
+ return { css: '', tokens: [] };
21
+ };
22
+
23
+ // Simulate consecutive errors
24
+ for (let i = 0; i < MAX_ERRORS; i++) {
25
+ try {
26
+ await mockBuild();
27
+ } catch (e) {
28
+ // Expected error
29
+ }
30
+ }
31
+
32
+ assert.strictEqual(errorCount, MAX_ERRORS);
33
+ });
34
+
35
+ it('resets error count on successful build', async () => {
36
+ let errorCount = 0;
37
+ const MAX_ERRORS = 3;
38
+
39
+ const mockBuild = async (forceError = false) => {
40
+ if (forceError) {
41
+ errorCount++;
42
+ throw new Error('Simulated build error');
43
+ }
44
+
45
+ // Successful build resets count
46
+ errorCount = 0;
47
+ return { css: '', tokens: [] };
48
+ };
49
+
50
+ // Accumulate errors
51
+ for (let i = 0; i < MAX_ERRORS; i++) {
52
+ try {
53
+ await mockBuild(true);
54
+ } catch (e) {
55
+ // Expected error
56
+ }
57
+ }
58
+
59
+ // Error count should be MAX_ERRORS after failures
60
+ assert.strictEqual(errorCount, MAX_ERRORS);
61
+
62
+ // Successful build should reset count
63
+ await mockBuild(false);
64
+ assert.strictEqual(errorCount, 0);
65
+ });
66
+
67
+ it('prevents builds during cooldown period', async () => {
68
+ let errorCount = 0;
69
+ let cooldownActive = false;
70
+ const MAX_ERRORS = 5;
71
+
72
+ const mockBuild = async () => {
73
+ if (cooldownActive) {
74
+ throw new Error('Cooldown active');
75
+ }
76
+
77
+ errorCount++;
78
+ if (errorCount >= MAX_ERRORS) {
79
+ cooldownActive = true;
80
+ }
81
+ throw new Error('Simulated build error');
82
+ };
83
+
84
+ // Trigger cooldown
85
+ for (let i = 0; i < MAX_ERRORS; i++) {
86
+ try {
87
+ await mockBuild();
88
+ } catch (e) {
89
+ // Expected error
90
+ }
91
+ }
92
+
93
+ assert.strictEqual(cooldownActive, true);
94
+
95
+ // Try to build during cooldown
96
+ let cooldownError = null;
97
+ try {
98
+ await mockBuild();
99
+ } catch (e) {
100
+ cooldownError = e.message;
101
+ }
102
+
103
+ assert.strictEqual(cooldownError, 'Cooldown active');
104
+ });
105
+ });
106
+
107
+ describe('Error Messages', () => {
108
+ it('logs error count when builds fail', async () => {
109
+ const errorLog = [];
110
+ let errorCount = 0;
111
+
112
+ const mockLogger = {
113
+ error: (msg) => errorLog.push(msg),
114
+ warn: (msg) => errorLog.push(msg),
115
+ info: (msg) => errorLog.push(msg)
116
+ };
117
+
118
+ const mockBuild = async () => {
119
+ errorCount++;
120
+ mockLogger.error(`Build failed (${errorCount}/5): Test error`);
121
+ throw new Error('Test error');
122
+ };
123
+
124
+ for (let i = 0; i < 3; i++) {
125
+ try {
126
+ await mockBuild();
127
+ } catch (e) {
128
+ // Expected error
129
+ }
130
+ }
131
+
132
+ assert.ok(errorLog.some(msg => msg.includes('Build failed (1/5)')));
133
+ assert.ok(errorLog.some(msg => msg.includes('Build failed (2/5)')));
134
+ assert.ok(errorLog.some(msg => msg.includes('Build failed (3/5)')));
135
+ });
136
+
137
+ it('logs cooldown activation message', async () => {
138
+ const errorLog = [];
139
+ const COOLDOWN_DURATION = 30000;
140
+
141
+ const mockLogger = {
142
+ error: (msg) => errorLog.push(msg),
143
+ warn: (msg) => errorLog.push(msg)
144
+ };
145
+
146
+ const mockBuild = async () => {
147
+ mockLogger.warn(`Maximum consecutive errors (5) reached.`);
148
+ mockLogger.warn(`Entering ${COOLDOWN_DURATION / 1000}s cooldown to prevent resource exhaustion.`);
149
+ throw new Error('Max errors reached');
150
+ };
151
+
152
+ try {
153
+ await mockBuild();
154
+ } catch (e) {
155
+ // Expected error
156
+ }
157
+
158
+ assert.ok(errorLog.some(msg => msg.includes('Maximum consecutive errors')));
159
+ assert.ok(errorLog.some(msg => msg.includes('cooldown')));
160
+ });
161
+ });
162
+
163
+ describe('Build Lock Mechanism', () => {
164
+ it('prevents overlapping builds', async () => {
165
+ let buildInProgress = false;
166
+ let pendingBuild = false;
167
+ let buildCount = 0;
168
+
169
+ const mockBuild = async () => {
170
+ if (buildInProgress) {
171
+ pendingBuild = true;
172
+ return;
173
+ }
174
+
175
+ buildInProgress = true;
176
+ buildCount++;
177
+ await new Promise(resolve => setTimeout(resolve, 100));
178
+ buildInProgress = false;
179
+
180
+ if (pendingBuild) {
181
+ pendingBuild = false;
182
+ await mockBuild();
183
+ }
184
+ };
185
+
186
+ // Trigger multiple builds in parallel
187
+ await Promise.all([
188
+ mockBuild(),
189
+ mockBuild(),
190
+ mockBuild()
191
+ ]);
192
+
193
+ // Should execute at least 2 builds (one initial, one from pending)
194
+ assert.ok(buildCount >= 2);
195
+ });
196
+
197
+ it('processes pending builds after current build completes', async () => {
198
+ let buildInProgress = false;
199
+ let pendingBuild = false;
200
+ let buildCount = 0;
201
+
202
+ const mockBuild = async () => {
203
+ if (buildInProgress) {
204
+ pendingBuild = true;
205
+ return;
206
+ }
207
+
208
+ buildInProgress = true;
209
+ buildCount++;
210
+ await new Promise(resolve => setTimeout(resolve, 50));
211
+ buildInProgress = false;
212
+
213
+ if (pendingBuild) {
214
+ pendingBuild = false;
215
+ await mockBuild();
216
+ }
217
+ };
218
+
219
+ // Trigger build
220
+ mockBuild();
221
+
222
+ // Immediately trigger another build while first is in progress
223
+ setTimeout(() => mockBuild(), 10);
224
+
225
+ // Wait for all builds to complete
226
+ await new Promise(resolve => setTimeout(resolve, 200));
227
+
228
+ assert.strictEqual(buildCount, 2);
229
+ });
230
+ });
231
+ });
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Memory Usage Limits Tests
3
+ * Tests for memory protection and batch processing
4
+ */
5
+
6
+ import { describe, it } from 'node:test';
7
+ import assert from 'node:assert';
8
+ import { getMemoryUsage, isMemorySafe, batchProcessWithMemoryLimit, batchProcessTokens } from '../../../src/utils/common.js';
9
+
10
+ describe('Memory Usage Limits', () => {
11
+ describe('getMemoryUsage', () => {
12
+ it('returns a number', () => {
13
+ const memory = getMemoryUsage();
14
+ assert.ok(typeof memory === 'number');
15
+ });
16
+
17
+ it('returns positive value', () => {
18
+ const memory = getMemoryUsage();
19
+ assert.ok(memory >= 0);
20
+ });
21
+
22
+ it('returns value in reasonable range', () => {
23
+ const memory = getMemoryUsage();
24
+ // Should be between 0 and a few GB in typical usage
25
+ assert.ok(memory < 10000); // Less than 10GB
26
+ });
27
+ });
28
+
29
+ describe('isMemorySafe', () => {
30
+ it('returns true for normal memory usage', () => {
31
+ const safe = isMemorySafe(500);
32
+ assert.strictEqual(typeof safe, 'boolean');
33
+ });
34
+
35
+ it('uses default limit of 500MB', () => {
36
+ const safe = isMemorySafe();
37
+ assert.strictEqual(typeof safe, 'boolean');
38
+ });
39
+
40
+ it('respects custom memory limit', () => {
41
+ const safe = isMemorySafe(1000);
42
+ assert.strictEqual(typeof safe, 'boolean');
43
+ });
44
+
45
+ it('returns false when memory exceeds limit', () => {
46
+ // Use a very low limit to ensure current usage exceeds it
47
+ const safe = isMemorySafe(1);
48
+ assert.strictEqual(safe, false);
49
+ });
50
+ });
51
+
52
+ describe('batchProcessWithMemoryLimit', () => {
53
+ it('processes all items in array', async () => {
54
+ const items = [1, 2, 3, 4, 5];
55
+ const processor = (item) => item * 2;
56
+
57
+ const results = await batchProcessWithMemoryLimit(items, processor, 10, 500);
58
+ assert.deepStrictEqual(results, [2, 4, 6, 8, 10]);
59
+ });
60
+
61
+ it('processes items in batches', async () => {
62
+ const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
63
+ const processor = (item) => item * 2;
64
+
65
+ const results = await batchProcessWithMemoryLimit(items, processor, 3, 500);
66
+ assert.deepStrictEqual(results, [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
67
+ });
68
+
69
+ it('handles empty array', async () => {
70
+ const results = await batchProcessWithMemoryLimit([], (item) => item, 10, 500);
71
+ assert.deepStrictEqual(results, []);
72
+ });
73
+
74
+ it('handles large arrays without crashing', async () => {
75
+ const items = Array(100).fill(0).map((_, i) => i);
76
+ const processor = (item) => item * 2;
77
+
78
+ const results = await batchProcessWithMemoryLimit(items, processor, 20, 500);
79
+ assert.strictEqual(results.length, 100);
80
+ assert.strictEqual(results[0], 0);
81
+ assert.strictEqual(results[99], 198);
82
+ });
83
+
84
+ it('preserves item order', async () => {
85
+ const items = [5, 2, 8, 1, 9];
86
+ const processor = (item) => item;
87
+
88
+ const results = await batchProcessWithMemoryLimit(items, processor, 2, 500);
89
+ assert.deepStrictEqual(results, [5, 2, 8, 1, 9]);
90
+ });
91
+ });
92
+
93
+ describe('batchProcessTokens', () => {
94
+ it('processes token objects', async () => {
95
+ const tokens = [
96
+ { raw: 'flex', attrType: 'layout' },
97
+ { raw: 'center', attrType: 'layout' },
98
+ { raw: 'p:medium', attrType: 'space' }
99
+ ];
100
+
101
+ const processor = (token) => ({ ...token, processed: true });
102
+
103
+ const results = await batchProcessTokens(tokens, processor, 2);
104
+ assert.strictEqual(results.length, 3);
105
+ assert.ok(results.every(r => r.processed === true));
106
+ });
107
+
108
+ it('handles large token arrays', async () => {
109
+ const tokens = Array(1000).fill(null).map((_, i) => ({
110
+ raw: `test${i}`,
111
+ attrType: 'layout'
112
+ }));
113
+
114
+ const processor = (token) => ({ ...token, index: tokens.indexOf(token) });
115
+
116
+ const results = await batchProcessTokens(tokens, processor, 100);
117
+ assert.strictEqual(results.length, 1000);
118
+ });
119
+
120
+ it('preserves token data', async () => {
121
+ const tokens = [
122
+ { raw: 'flex', attrType: 'layout', property: 'flex' },
123
+ { raw: 'grid', attrType: 'layout', property: 'grid' }
124
+ ];
125
+
126
+ const processor = (token) => token;
127
+
128
+ const results = await batchProcessTokens(tokens, processor, 1);
129
+ assert.deepStrictEqual(results, tokens);
130
+ });
131
+ });
132
+
133
+ describe('Memory Error Handling', () => {
134
+ it('processes items without memory errors under normal conditions', async () => {
135
+ // This test verifies the memory limit handling works under normal conditions
136
+ // Actual memory limit errors are difficult to test in unit test environment
137
+
138
+ const items = [1, 2, 3];
139
+ const processor = (item) => item;
140
+
141
+ const results = await batchProcessWithMemoryLimit(items, processor, 1, 500);
142
+ assert.strictEqual(results.length, 3);
143
+ assert.deepStrictEqual(results, [1, 2, 3]);
144
+ });
145
+ });
146
+
147
+ describe('Batch Size Configuration', () => {
148
+ it('respects custom batch size', async () => {
149
+ const items = [1, 2, 3, 4, 5];
150
+ const batchesProcessed = [];
151
+
152
+ const processor = (item) => {
153
+ batchesProcessed.push(item);
154
+ return item * 2;
155
+ };
156
+
157
+ await batchProcessWithMemoryLimit(items, processor, 2, 500);
158
+ assert.strictEqual(batchesProcessed.length, 5);
159
+ });
160
+
161
+ it('uses default batch size of 1000', async () => {
162
+ const items = Array(500).fill(0).map((_, i) => i);
163
+ const processor = (item) => item * 2;
164
+
165
+ const results = await batchProcessTokens(items, processor);
166
+ assert.strictEqual(results.length, 500);
167
+ });
168
+ });
169
+ });
@@ -0,0 +1,204 @@
1
+ /**
2
+ * CSS Generator Error Handling Tests
3
+ * Tests for error boundaries and graceful degradation
4
+ */
5
+
6
+ import { describe, it } from 'node:test';
7
+ import assert from 'node:assert';
8
+ import { generateRule, generateCSS } from '../../../src/compiler/generators/css.js';
9
+ import { defaultConfig } from '../../../src/config/defaults.js';
10
+
11
+ describe('CSS Generator Error Handling', () => {
12
+ describe('generateRule', () => {
13
+ it('handles null token gracefully', () => {
14
+ const result = generateRule(null, defaultConfig);
15
+ assert.strictEqual(result, '');
16
+ });
17
+
18
+ it('handles undefined token gracefully', () => {
19
+ const result = generateRule(undefined, defaultConfig);
20
+ assert.strictEqual(result, '');
21
+ });
22
+
23
+ it('handles token with missing attrType', () => {
24
+ const token = {
25
+ raw: 'flex',
26
+ property: 'flex',
27
+ value: 'flex'
28
+ };
29
+ const result = generateRule(token, defaultConfig);
30
+ assert.strictEqual(result, '');
31
+ });
32
+
33
+ it('handles token with missing raw value', () => {
34
+ const token = {
35
+ attrType: 'layout',
36
+ property: 'flex',
37
+ value: 'flex'
38
+ };
39
+ const result = generateRule(token, defaultConfig);
40
+ assert.strictEqual(result, '');
41
+ });
42
+
43
+ it('handles token with invalid attrType', () => {
44
+ const token = {
45
+ raw: 'flex',
46
+ attrType: 'invalid',
47
+ property: 'flex',
48
+ value: 'flex'
49
+ };
50
+ const result = generateRule(token, defaultConfig);
51
+ assert.strictEqual(result, '');
52
+ });
53
+
54
+ it('handles malformed layout rule without crashing', () => {
55
+ const token = {
56
+ raw: 'test',
57
+ attrType: 'layout',
58
+ property: null,
59
+ value: null
60
+ };
61
+ const result = generateRule(token, defaultConfig);
62
+ assert.strictEqual(result, '');
63
+ });
64
+
65
+ it('handles malformed space rule without crashing', () => {
66
+ const token = {
67
+ raw: 'test',
68
+ attrType: 'space',
69
+ property: null,
70
+ value: null
71
+ };
72
+ const result = generateRule(token, defaultConfig);
73
+ assert.strictEqual(result, '');
74
+ });
75
+
76
+ it('handles malformed visual rule without crashing', () => {
77
+ const token = {
78
+ raw: 'test',
79
+ attrType: 'visual',
80
+ property: null,
81
+ value: null
82
+ };
83
+ const result = generateRule(token, defaultConfig);
84
+ assert.strictEqual(result, '');
85
+ });
86
+ });
87
+
88
+ describe('generateCSS', () => {
89
+ it('handles null tokens array', () => {
90
+ const result = generateCSS(null, defaultConfig);
91
+ assert.strictEqual(result, '');
92
+ });
93
+
94
+ it('handles undefined tokens array', () => {
95
+ const result = generateCSS(undefined, defaultConfig);
96
+ assert.strictEqual(result, '');
97
+ });
98
+
99
+ it('handles empty tokens array', () => {
100
+ const result = generateCSS([], defaultConfig);
101
+ assert.ok(typeof result === 'string');
102
+ assert.ok(result.includes(':root'));
103
+ assert.ok(!result.includes('[layout~="'));
104
+ });
105
+
106
+ it('handles malformed tokens without crashing', () => {
107
+ const malformedTokens = [
108
+ null,
109
+ undefined,
110
+ { raw: 'test' },
111
+ { attrType: 'layout' },
112
+ { raw: 'test', attrType: 'invalid' }
113
+ ];
114
+ const result = generateCSS(malformedTokens, defaultConfig);
115
+ assert.ok(typeof result === 'string');
116
+ assert.ok(result.includes(':root'));
117
+ });
118
+
119
+ it('handles valid tokens mixed with malformed ones', () => {
120
+ const mixedTokens = [
121
+ { raw: 'flex', attrType: 'layout', property: 'flex', value: 'flex' },
122
+ null,
123
+ { raw: 'center', attrType: 'layout', property: 'items', value: 'center' },
124
+ undefined,
125
+ { raw: 'p:medium', attrType: 'space', property: 'p', value: 'medium' }
126
+ ];
127
+ const result = generateCSS(mixedTokens, defaultConfig);
128
+ assert.ok(typeof result === 'string');
129
+ assert.ok(result.includes('[layout~="flex"]'));
130
+ assert.ok(result.includes('[layout~="center"]'));
131
+ assert.ok(result.includes('[space~="p:medium"]'));
132
+ });
133
+
134
+ it('handles null config gracefully', () => {
135
+ const result = generateCSS([], null);
136
+ assert.strictEqual(result, '');
137
+ });
138
+
139
+ it('handles invalid config gracefully', () => {
140
+ const result = generateCSS([], 'invalid');
141
+ assert.strictEqual(result, '');
142
+ });
143
+
144
+ it('handles config with missing theme', () => {
145
+ const invalidConfig = { ...defaultConfig, theme: null };
146
+ const result = generateCSS([], invalidConfig);
147
+ assert.ok(typeof result === 'string');
148
+ });
149
+
150
+ it('handles tokens with invalid state values', () => {
151
+ const invalidTokens = [
152
+ { raw: 'flex:invalid', attrType: 'layout', property: 'flex', value: 'flex', state: 'invalid-state' }
153
+ ];
154
+ const result = generateCSS(invalidTokens, defaultConfig);
155
+ assert.ok(typeof result === 'string');
156
+ });
157
+
158
+ it('handles tokens with invalid breakpoint values', () => {
159
+ const invalidTokens = [
160
+ { raw: 'invalid:flex', attrType: 'layout', property: 'flex', value: 'flex', breakpoint: 'invalid-bp' }
161
+ ];
162
+ const result = generateCSS(invalidTokens, defaultConfig);
163
+ assert.ok(typeof result === 'string');
164
+ });
165
+ });
166
+
167
+ describe('CSS Rule Validation', () => {
168
+ it('always generates valid declarations with semicolons', () => {
169
+ const token = {
170
+ raw: 'flex',
171
+ attrType: 'layout',
172
+ property: 'flex',
173
+ value: 'flex'
174
+ };
175
+ const result = generateRule(token, defaultConfig);
176
+ assert.ok(result.includes('{'));
177
+ assert.ok(result.includes('}'));
178
+ assert.ok(result.includes(';'));
179
+ assert.ok(result.endsWith('}\n'));
180
+ });
181
+
182
+ it('rejects declarations without property', () => {
183
+ const token = {
184
+ raw: 'test',
185
+ attrType: 'layout',
186
+ property: '',
187
+ value: 'flex'
188
+ };
189
+ const result = generateRule(token, defaultConfig);
190
+ assert.strictEqual(result, '');
191
+ });
192
+
193
+ it('rejects declarations without value', () => {
194
+ const token = {
195
+ raw: 'test',
196
+ attrType: 'layout',
197
+ property: 'display',
198
+ value: ''
199
+ };
200
+ const result = generateRule(token, defaultConfig);
201
+ assert.strictEqual(result, '');
202
+ });
203
+ });
204
+ });