Skip to content

Commit d4c544c

Browse files
authored
Add mocha tests to cli package (#8047)
2 parents 3d39bca + 681270b commit d4c544c

27 files changed

+682
-2
lines changed

packages/tools/kolibri-cli/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/tools/kolibri-cli/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
"start": "rimraf test && cpy \"../../samples/react/src/components\" test/src && cpy \"../../samples/react/public/*.html\" test/ && ts-node src/index.ts migrate --ignore-uncommitted-changes --test-tasks test",
3030
"restart": "pnpm reset && pnpm start",
3131
"unused": "knip",
32-
"watch": "nodemon --ignore package.json src/index.ts migrate --ignore-uncommitted-changes --test-tasks test"
32+
"watch": "nodemon --ignore package.json src/index.ts migrate --ignore-uncommitted-changes --test-tasks test",
33+
"test": "pnpm test:unit",
34+
"test:unit": "TS_NODE_PROJECT=tsconfig.test.json mocha --require ts-node/register test/**/*.ts"
3335
},
3436
"type": "commonjs",
3537
"dependencies": {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import assert from 'node:assert';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { Command } from 'commander';
6+
import generateScss from '../src/generate-scss';
7+
import info from '../src/info';
8+
import migrate from '../src/migrate';
9+
import { getRemoveMode, setRemoveMode } from '../src/migrate/shares/reuse';
10+
import { TaskRunner } from '../src/migrate/runner/task-runner';
11+
12+
describe('CLI interface', () => {
13+
it('runs generate-scss command', () => {
14+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kolibri-cli-'));
15+
const cwd = process.cwd();
16+
process.chdir(tmpDir);
17+
18+
const typedBem = require('typed-bem/scss');
19+
const original = typedBem.generateBemScssFile;
20+
const calls: string[] = [];
21+
typedBem.generateBemScssFile = (_: unknown, name: string) => { calls.push(name); };
22+
23+
const program = new Command();
24+
generateScss(program);
25+
program.parse(['node', 'cli', 'generate-scss']);
26+
27+
typedBem.generateBemScssFile = original;
28+
process.chdir(cwd);
29+
30+
assert.deepStrictEqual(calls, ['alert', 'icon']);
31+
});
32+
33+
it('runs info command', () => {
34+
const program = new Command();
35+
info(program);
36+
let output = '';
37+
const original = console.log;
38+
console.log = (str: string) => { output += str; };
39+
program.parse(['node', 'cli', 'info']);
40+
console.log = original;
41+
assert.ok(output.includes('Operating System'));
42+
});
43+
44+
it('runs migrate command with options', () => {
45+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kolibri-cli-'));
46+
fs.writeFileSync(
47+
path.join(tmpDir, 'package.json'),
48+
JSON.stringify({ dependencies: { '@public-ui/components': '0.0.0' }, devDependencies: { '@public-ui/kolibri-cli': '0.0.0' } }),
49+
);
50+
fs.writeFileSync(path.join(tmpDir, 'pnpm-lock.yaml'), '');
51+
const cwd = process.cwd();
52+
process.chdir(tmpDir);
53+
54+
55+
const childProc = require('child_process');
56+
const execOrig = childProc.exec;
57+
(childProc as any).exec = (_: string, cb: (err: null, out: string) => void) => cb(null, '');
58+
59+
let runCalled = false;
60+
const runOrig = TaskRunner.prototype.run;
61+
TaskRunner.prototype.run = function () {
62+
runCalled = true;
63+
};
64+
const getStatusOrig = TaskRunner.prototype.getStatus;
65+
TaskRunner.prototype.getStatus = () => ({ total: 0, done: 0, pending: 0, nextVersion: '0.0.0', config: { migrate: { tasks: {} } } });
66+
const getPendingOrig = TaskRunner.prototype.getPendingMinVersion;
67+
TaskRunner.prototype.getPendingMinVersion = () => '0.0.0';
68+
69+
const program = new Command();
70+
migrate(program);
71+
program.parse([
72+
'node',
73+
'cli',
74+
'migrate',
75+
'.',
76+
'--ignore-uncommitted-changes',
77+
'--overwrite-current-version',
78+
'0.0.0',
79+
'--overwrite-target-version',
80+
'0.0.0',
81+
'--remove-mode',
82+
'delete',
83+
'--test-tasks',
84+
]);
85+
86+
(childProc as any).exec = execOrig;
87+
TaskRunner.prototype.run = runOrig;
88+
TaskRunner.prototype.getStatus = getStatusOrig;
89+
TaskRunner.prototype.getPendingMinVersion = getPendingOrig;
90+
process.chdir(cwd);
91+
92+
assert.ok(runCalled);
93+
assert.equal(getRemoveMode(), 'delete');
94+
setRemoveMode('prefix');
95+
});
96+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import assert from 'node:assert';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import child_process from 'node:child_process';
6+
7+
import { TaskRunner } from '../src/migrate/runner/task-runner';
8+
import { RenameTagNameTask } from '../src/migrate/runner/tasks/common/RenameTagNameTask';
9+
import { RenamePropertyNameTask } from '../src/migrate/runner/tasks/common/RenamePropertyNameTask';
10+
import { RemovePropertyNameTask } from '../src/migrate/runner/tasks/common/RemovePropertyNameTask';
11+
import { setRemoveMode } from '../src/migrate/shares/reuse';
12+
13+
function createStubModules(baseDir: string) {
14+
const reactTypes = path.join(baseDir, 'node_modules', '@types', 'react');
15+
fs.mkdirSync(reactTypes, { recursive: true });
16+
fs.writeFileSync(path.join(reactTypes, 'index.d.ts'), 'export {};');
17+
const reactRuntime = path.join(baseDir, 'node_modules', 'react');
18+
fs.mkdirSync(reactRuntime, { recursive: true });
19+
fs.writeFileSync(path.join(reactRuntime, 'jsx-runtime.d.ts'), 'export {};');
20+
const publicUiReact = path.join(baseDir, 'node_modules', '@public-ui', 'react');
21+
fs.mkdirSync(publicUiReact, { recursive: true });
22+
fs.writeFileSync(path.join(publicUiReact, 'index.d.ts'), 'export class KolTable {}; export class KolTableStateful {};');
23+
}
24+
25+
describe('end-to-end migration', () => {
26+
beforeEach(() => {
27+
(RenameTagNameTask as any).instances?.clear();
28+
(RenamePropertyNameTask as any).instances?.clear();
29+
(RemovePropertyNameTask as any).instances?.clear();
30+
});
31+
32+
it('runs multiple tasks and compiles the project', () => {
33+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kolibri-e2e-'));
34+
const srcDir = path.join(tmpDir, 'src');
35+
fs.mkdirSync(srcDir);
36+
fs.writeFileSync(
37+
path.join(srcDir, 'index.tsx'),
38+
'import { KolTable } from \'@public-ui/react\';\nexport const App = () => <KolTable _old="foo" _remove="bar" />;\n',
39+
);
40+
fs.writeFileSync(path.join(tmpDir, 'index.html'), '<kol-table _old="foo" _remove="bar"></kol-table>');
41+
createStubModules(tmpDir);
42+
const tsconfigPath = path.join(tmpDir, 'tsconfig.json');
43+
fs.writeFileSync(tsconfigPath, '{}');
44+
45+
const runner = new TaskRunner(tmpDir, '3.0.0', '1.0.0', { migrate: { tasks: {} } });
46+
setRemoveMode('delete');
47+
const tasks = [
48+
RenameTagNameTask.getInstance('kol-table', 'kol-table-stateful', '^1'),
49+
RenamePropertyNameTask.getInstance('kol-table-stateful', '_old', '_new', '^1'),
50+
RemovePropertyNameTask.getInstance('kol-table-stateful', '_remove', '^1'),
51+
];
52+
runner.registerTasks(tasks);
53+
runner.run();
54+
55+
const tsxContent = fs.readFileSync(path.join(srcDir, 'index.tsx'), 'utf8');
56+
const htmlContent = fs.readFileSync(path.join(tmpDir, 'index.html'), 'utf8');
57+
console.log('debug-tsx', tsxContent);
58+
console.log('debug-html', htmlContent);
59+
assert.ok(tsxContent.includes('KolTableStateful'));
60+
assert.ok(tsxContent.includes('_new="foo"'));
61+
assert.ok(!tsxContent.includes('_remove'));
62+
assert.ok(htmlContent.includes('<kol-table-stateful'));
63+
assert.ok(htmlContent.includes('_new="foo"'));
64+
assert.ok(!htmlContent.includes('_remove'));
65+
66+
setRemoveMode('prefix');
67+
});
68+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import assert from 'node:assert';
2+
import { ExecTask } from '../src/migrate/runner/tasks/common/ExecTask';
3+
4+
describe('ExecTask', () => {
5+
it('executes given command', () => {
6+
const cp = require('child_process');
7+
const original = cp.execSync;
8+
let called = '';
9+
(cp as any).execSync = (cmd: string) => {
10+
called = cmd;
11+
return Buffer.from('');
12+
};
13+
14+
const task = ExecTask.getInstance('echo "works"', '^1');
15+
task.run();
16+
17+
(cp as any).execSync = original;
18+
assert.strictEqual(called, 'echo "works"');
19+
});
20+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import assert from 'node:assert';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { GenericRenameSlotNameTask } from '../src/migrate/runner/tasks/common/GenericRenameSlotNameTask';
6+
7+
class TestRenameSlotTask extends GenericRenameSlotNameTask {
8+
constructor(tmpTag: string, oldName: string, newName: string) {
9+
super('test', 'desc', tmpTag, oldName, newName, 'slot', '^1');
10+
}
11+
}
12+
13+
describe('GenericRenameSlotNameTask', () => {
14+
it('updates slot names in components', () => {
15+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kolibri-cli-'));
16+
const tsxPath = path.join(tmpDir, 'component.tsx');
17+
fs.writeFileSync(tsxPath, '<KolCard slot="header"></KolCard>');
18+
19+
const task = new TestRenameSlotTask('kol-card', 'header', 'main');
20+
task.run(tmpDir);
21+
22+
const content = fs.readFileSync(tsxPath, 'utf8');
23+
assert.ok(content.includes('slot="main"'));
24+
});
25+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import assert from 'node:assert';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { GenericRenameTagNameTask } from '../src/migrate/runner/tasks/common/GenericRenameTagNameTask';
6+
7+
class TestRenameTagTask extends GenericRenameTagNameTask {
8+
constructor(oldTag: string, newTag: string) {
9+
super('test', 'desc', oldTag, newTag, '^1');
10+
}
11+
}
12+
13+
describe('GenericRenameTagNameTask', () => {
14+
it('renames tags in component and custom element files', () => {
15+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kolibri-cli-'));
16+
const tsxPath = path.join(tmpDir, 'component.tsx');
17+
fs.writeFileSync(tsxPath, '<KolAlert />');
18+
const htmlPath = path.join(tmpDir, 'sample.html');
19+
fs.writeFileSync(htmlPath, '<kol-alert></kol-alert>');
20+
21+
const task = new TestRenameTagTask('kol-alert', 'kol-notice');
22+
task.run(tmpDir);
23+
24+
const tsxContent = fs.readFileSync(tsxPath, 'utf8');
25+
const htmlContent = fs.readFileSync(htmlPath, 'utf8');
26+
assert.ok(tsxContent.includes('KolNotice'));
27+
assert.ok(htmlContent.includes('kol-notice'));
28+
});
29+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import assert from 'node:assert';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { GitIgnoreAddRuleTask } from '../src/migrate/runner/tasks/common/GitIgnoreAddRuleTask';
6+
7+
describe('GitIgnoreAddRuleTask', () => {
8+
it('adds rule to .gitignore', () => {
9+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kolibri-cli-'));
10+
const cwd = process.cwd();
11+
process.chdir(tmpDir);
12+
13+
const task = GitIgnoreAddRuleTask.getInstance('dist', '^1');
14+
task.run();
15+
16+
process.chdir(cwd);
17+
const content = fs.readFileSync(path.join(tmpDir, '.gitignore'), 'utf8');
18+
assert.ok(content.includes('dist'));
19+
});
20+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import assert from 'node:assert';
2+
import { HandleDependencyTask } from '../src/migrate/runner/tasks/common/HandleDependencyTask';
3+
4+
5+
describe('HandleDependencyTask', () => {
6+
it('calls package manager with dependencies', async () => {
7+
const childProc = require('child_process');
8+
const original = childProc.execSync;
9+
let commands: string[] = [];
10+
(childProc as any).execSync = (cmd: string) => { commands.push(cmd); return Buffer.from(''); };
11+
12+
const task = HandleDependencyTask.getInstance('add', { lodash: '1.0.0' }, { mocha: '2.0.0' }, '^1');
13+
task.run();
14+
15+
(childProc as any).execSync = original;
16+
assert.ok(commands[0].includes('add')); // first call
17+
assert.ok(commands[1].includes('-D'));
18+
});
19+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import assert from 'node:assert';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { JsonTask } from '../src/migrate/runner/tasks/common/JsonTask';
6+
7+
describe('JsonTask', () => {
8+
it('merges config into package.json', () => {
9+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kolibri-cli-'));
10+
const cwd = process.cwd();
11+
process.chdir(tmpDir);
12+
fs.writeFileSync('package.json', '{"name":"test"}');
13+
14+
const task = JsonTask.getInstance('scripts', { scripts: { test: 'mocha' } }, '^1');
15+
task.run();
16+
17+
process.chdir(cwd);
18+
const pkg = JSON.parse(fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf8'));
19+
assert.equal(pkg.scripts.test, 'mocha');
20+
});
21+
});

0 commit comments

Comments
 (0)