Skip to content

Commit b070bbb

Browse files
committed
v3.3.4
1 parent 5ca2b36 commit b070bbb

File tree

8 files changed

+71
-182
lines changed

8 files changed

+71
-182
lines changed

packages/core/__tests__/unit/cli.test.cjs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -88,32 +88,6 @@ function runCLIScript(targetDir, timeout = 30000) {
8888
});
8989
}
9090

91-
// test('CLI script creates package.json if it does not exist', async () => {
92-
// const testDir = createTestDir();
93-
94-
// try {
95-
// // Ensure no package.json exists
96-
// const packageJsonPath = path.join(testDir, 'package.json');
97-
// assert(!fs.existsSync(packageJsonPath), 'package.json should not exist initially');
98-
99-
// // Run CLI (this will timeout on npm install, but that's ok for this test)
100-
// const result = await runCLIScript(testDir, 5000).catch(err => {
101-
// // We expect this to timeout/fail during npm install
102-
// return { timedOut: true };
103-
// });
104-
105-
// // Check that package.json was created
106-
// assert(fs.existsSync(packageJsonPath), 'package.json should be created');
107-
108-
// const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
109-
// assert(packageJson.name, 'package.json should have a name field');
110-
// assert(packageJson.version, 'package.json should have a version field');
111-
112-
// console.log('✅ package.json created successfully');
113-
114-
// } finally {
115-
// cleanupTestDir(testDir);
116-
// }
11791
// });
11892

11993
test('CLI script uses existing package.json if it exists', async () => {

packages/core/__tests__/unit/index.test.cjs

Lines changed: 6 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ function getAttrFile() {
1414
}
1515

1616
// Helper to create temp directory and run test
17-
async function runTest(files, callback) {
17+
async function runTest(files, callback, cache = true) {
1818
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zero-ui-test'));
1919
const originalCwd = process.cwd();
2020

2121
try {
2222
process.chdir(testDir);
2323

2424
// Clear the global file cache to prevent stale entries from previous tests
25-
try {
26-
const astParsing = require('../../dist/postcss/ast-parsing.js');
27-
if (astParsing.clearCache) {
25+
if (cache) {
26+
try {
27+
const astParsing = require('../../dist/postcss/ast-parsing.js');
2828
astParsing.clearCache();
29+
} catch {
30+
// Cache clearing is best-effort
2931
}
30-
} catch {
31-
// Cache clearing is best-effort
3232
}
3333

3434
// Create test files
@@ -1150,145 +1150,3 @@ test('generated variants for initial value without setterFn', async () => {
11501150
}
11511151
);
11521152
});
1153-
/*
1154-
The following tests are for advanced edge cases
1155-
--------------------------------------------------------------------------------------------
1156-
----------------------------------------------
1157-
----------------------------------------------
1158-
----------------------------------------------
1159-
----------------------------------------------
1160-
----------------------------------------------
1161-
--------------------------------------------------------------------------------------------
1162-
----------------------------------------------
1163-
----------------------------------------------
1164-
----------------------------------------------
1165-
----------------------------------------------
1166-
----------------------------------------------
1167-
----------------------------------------------
1168-
*/
1169-
1170-
test.skip('handles all common setter patterns - full coverage sanity check - COMPLEX', async () => {
1171-
await runTest(
1172-
{
1173-
'app/component.tsx': `
1174-
import { useUI } from '@react-zero-ui/core';
1175-
1176-
const DARK = 'dark';
1177-
const LIGHT = 'light';
1178-
1179-
function Component() {
1180-
const [theme, setTheme] = useUI<'light' | 'dark' | 'contrast' | 'neon' | 'retro'>('theme', 'light');
1181-
const [size, setSize] = useUI<'sm' | 'lg'>('size', 'sm');
1182-
1183-
setTheme('dark'); // direct
1184-
setTheme(DARK); // identifier
1185-
setTheme(() => 'light'); // arrow fn
1186-
setTheme(prev => prev === 'light' ? 'dark' : 'light'); // updater
1187-
setTheme(prev => { if (a) return 'neon'; return 'retro'; }); // block
1188-
setTheme(userPref || 'contrast'); // logical fallback
1189-
setSize(SIZES.SMALL); // object constant
1190-
1191-
return (
1192-
<>
1193-
<button onClick={() => setTheme('contrast')}>Contrast</button>
1194-
<select onChange={e => setTheme(e.target.value)}>
1195-
<option value="neon">Neon</option>
1196-
<option value="retro">Retro</option>
1197-
</select>
1198-
</>
1199-
);
1200-
}
1201-
1202-
const SIZES = {
1203-
SMALL: 'sm',
1204-
LARGE: 'lg'
1205-
};
1206-
`,
1207-
},
1208-
(result) => {
1209-
console.log('\n📄 Full coverage test:');
1210-
1211-
// ✅ things that MUST be included
1212-
assert(result.css.includes('@custom-variant theme-dark'));
1213-
assert(result.css.includes('@custom-variant theme-light'));
1214-
assert(result.css.includes('@custom-variant theme-contrast'));
1215-
assert(result.css.includes('@custom-variant theme-neon'));
1216-
assert(result.css.includes('@custom-variant theme-retro'));
1217-
assert(result.css.includes('@custom-variant size-sm'));
1218-
assert(result.css.includes('@custom-variant size-lg'));
1219-
1220-
// ❌ known misses: test exposes what won't work without resolution
1221-
// assert(result.css.includes('@custom-variant theme-dynamic-from-e-target'));
1222-
}
1223-
);
1224-
});
1225-
1226-
test('performance with large files and many variants', async () => {
1227-
// Generate a large file with many useUI calls
1228-
const generateLargeFile = () => {
1229-
let content = `import { useUI } from '@react-zero-ui/core';\n\n`;
1230-
1231-
// Create many components with different state keys
1232-
for (let i = 0; i < 50; i++) {
1233-
const toggleInitial = i % 2 === 0 ? "'true'" : "'false'";
1234-
content += `
1235-
function Component${i}() {
1236-
const [state${i}, setState${i}] = useUI('state-${i}', 'initial-${i}');
1237-
const [toggle${i}, setToggle${i}] = useUI('toggle-${i}', ${toggleInitial});
1238-
1239-
1240-
1241-
return <div className="state-${i}-initial-${i}:bg-blue-500 state-${i}-true:bg-red-500 state-${i}-false:bg-green-500 toggle-${i}-true:bg-yellow-500 toggle-${i}-false:bg-purple-500 toggle-${i}-test:bg-orange-500">Component ${i}</div>;
1242-
}
1243-
`;
1244-
}
1245-
1246-
return content;
1247-
};
1248-
1249-
const startTime = Date.now();
1250-
1251-
await runTest({ 'app/large-file.jsx': generateLargeFile() }, (result) => {
1252-
const endTime = Date.now();
1253-
const duration = endTime - startTime;
1254-
1255-
console.log(`\n⏱️ Large file processing took: ${duration}ms`);
1256-
1257-
// Should handle large files reasonably quickly (< 5 seconds)
1258-
assert(duration < 5000, `Processing took too long: ${duration}ms`);
1259-
1260-
// Should still extract all variants correctly
1261-
assert(result.css.includes('@custom-variant state-0-initial-0'));
1262-
assert(result.css.includes('@custom-variant state-49-initial-49'));
1263-
assert(result.css.includes('@custom-variant toggle-0-true'));
1264-
assert(result.css.includes('@custom-variant toggle-0-false'));
1265-
});
1266-
});
1267-
1268-
test('caching works correctly', async () => {
1269-
const testFiles = {
1270-
'app/cached.jsx': `
1271-
import { useUI } from '@react-zero-ui/core';
1272-
1273-
function Component() {
1274-
const [theme, setTheme] = useUI('theme', 'light');
1275-
return <div>Test</div>;
1276-
}
1277-
`,
1278-
};
1279-
1280-
// First run
1281-
const start1 = Date.now();
1282-
await runTest(testFiles, () => {});
1283-
const duration1 = Date.now() - start1;
1284-
1285-
// Second run with same files (should be faster due to caching)
1286-
const start2 = Date.now();
1287-
await runTest(testFiles, () => {});
1288-
const duration2 = Date.now() - start2;
1289-
1290-
console.log(`\n📊 First run: ${duration1}ms, Second run: ${duration2}ms`);
1291-
1292-
// Note: This test might be flaky in CI, but useful for development
1293-
// Second run should generally be faster, but timing can vary
1294-
});

packages/core/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@
7070
"!*.test.cjs"
7171
],
7272
"dependencies": {
73+
"@babel/core": "^7.28.0",
7374
"@babel/code-frame": "^7.27.1",
7475
"@babel/generator": "^7.28.0",
7576
"@babel/parser": "^7.28.0",
77+
"@babel/preset-typescript": "^7.27.1",
7678
"@babel/traverse": "^7.28.0",
7779
"@babel/types": "^7.28.0",
7880
"fast-glob": "^3.3.3",
@@ -81,6 +83,7 @@
8183
"devDependencies": {
8284
"@playwright/test": "^1.54.0",
8385
"@types/babel__code-frame": "^7.0.6",
86+
"@types/babel__core": "^7.20.5",
8487
"@types/babel__generator": "^7.27.0",
8588
"@types/babel__traverse": "^7.20.7",
8689
"@types/react": "^19.1.8",
@@ -117,4 +120,4 @@
117120
"react optimization",
118121
"high performance react"
119122
]
120-
}
123+
}

packages/core/src/postcss/ast-parsing.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
// src/core/postcss/ast-parsing.cts
1+
// src/core/postcss/ast-parsing.ts
22
import os from 'os';
3-
import { parse, ParserOptions } from '@babel/parser';
3+
import { TransformOptions, transformSync, type BabelFileResult } from '@babel/core';
4+
import tsPreset from '@babel/preset-typescript';
5+
import { type ParserOptions } from '@babel/parser';
46
import * as t from '@babel/types';
57
import { CONFIG } from '../config.js';
68
import * as fs from 'fs';
@@ -250,7 +252,7 @@ export async function processVariants(changedFiles: string[] | null = null): Pro
250252
const code = fs.readFileSync(fp, 'utf8');
251253

252254
// AST Parse the file
253-
const ast = parse(code, PARSE_OPTS(fp));
255+
const ast = parseJsLike(code, fp);
254256

255257
// Collect the useUI hooks
256258
const hooks = collectUseUIHooks(ast, code);
@@ -318,6 +320,29 @@ export async function processVariants(changedFiles: string[] | null = null): Pro
318320
return { finalVariants, initialGlobalValues, sourceFiles: srcFiles };
319321
}
320322

323+
const cache = new Map<string, t.File>();
324+
325+
const defaultOpts: Partial<TransformOptions> = {
326+
presets: [[tsPreset, { isTSX: true, allowDeclareFields: true, allExtensions: true }]],
327+
parserOpts: { sourceType: 'module', plugins: ['jsx', 'decorators-legacy', 'typescript', 'topLevelAwait'] },
328+
ast: true,
329+
code: false,
330+
};
331+
332+
export function parseJsLike(code: string, filename: string, opts: TransformOptions = defaultOpts): t.File {
333+
const key = filename + '\0' + code.length;
334+
if (cache.has(key)) return cache.get(key)!;
335+
336+
const result = transformSync(code, { ...opts, filename }) as BabelFileResult & { ast: t.File }; // <— narrow type
337+
338+
if (!result.ast) {
339+
throw new Error(`[Zero-UI] Failed to parse file: ${filename}`);
340+
}
341+
342+
cache.set(key, result.ast);
343+
return result.ast;
344+
}
345+
321346
/**
322347
* @param nodeOrPath - The node or node path to throw the code frame for.
323348
* @param filename - The filename to include in the code frame.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module '@babel/preset-typescript' {
2+
import { PluginItem } from '@babel/core';
3+
const preset: PluginItem;
4+
export default preset;
5+
}

packages/core/src/postcss/resolvers.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,6 @@ test('literalFromNode should resolve ArrayExpression values via static index acc
396396

397397
const opts: ResolveOpts = { throwOnFail: true, source: code };
398398
const result = literalFromNode(found.node as t.Expression, found.path, opts);
399-
console.log('result: ', result);
400399

401400
assert.strictEqual(result, expected, `Failed: ${description}`);
402401
}

packages/core/src/postcss/test-utilities.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import path from 'path';
22
import fs from 'fs';
33
import os from 'os';
4-
import assert from 'assert';
5-
import { literalFromNode, ResolveOpts } from './resolvers.js';
6-
import * as t from '@babel/types';
74

85
// Helper to create temp directory and run test
96
export async function runTest(files: Record<string, string>, callback: () => Promise<void>) {

pnpm-lock.yaml

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)