Files
ECC/tests/hooks/ecc-statusline-project-plugins.test.js
T

56 lines
4.2 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { readInstalledPlugins } = require('../../scripts/lib/statusline-render');
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-plugin-project-'));
const config = path.join(root, 'profile');const a = path.join(root, 'A');const b = path.join(root, 'B');
for (const dir of [config, a, b]) fs.mkdirSync(path.join(dir, '.claude'), { recursive: true });
fs.mkdirSync(path.join(config, 'plugins'));
const records = { 'a-only@fixture': [{ scope: 'local', projectPath: a, version: '9.9.9' }],
'shared@fixture': [{ scope: 'project', projectPath: a, version: '1.1.0' }, { scope: 'project', projectPath: b, version: '2.2.0' }, { scope: 'user', version: '0.0.1' }],
'global@fixture': [{ scope: 'user', version: '3.3.3' }] };
const write = value => fs.writeFileSync(path.join(config, 'plugins/installed_plugins.json'), JSON.stringify({ version: 2, plugins: value }));
const settings = (dir, name, value) => fs.writeFileSync(path.join(dir, '.claude', name), JSON.stringify({ enabledPlugins: value }));
function cli(project) {
const result = spawnSync(process.execPath, [path.resolve(__dirname, '../../scripts/hooks/ecc-statusline.js')], {
input: JSON.stringify({ model: { display_name: 'Fixture' }, workspace: { current_dir: project } }), encoding: 'utf8', timeout: 5000,
env: { ...process.env, CLAUDE_CONFIG_DIR: config, ECC_STATUSLINE_USAGE: '1', ECC_STATUSLINE_COMPACT: '0' },
});assert.equal(result.status, 0, result.stderr);return result.stdout;
}
const tests = [
['actual B statusline excludes A-only plugins and selects B shared version independent of record order', () => {
write(records);const first = cli(b);assert.doesNotMatch(first, /a-only|1\.1\.0/);assert.match(first, /shared.*2\.2\.0/);
write({ ...records, 'shared@fixture': [...records['shared@fixture']].reverse() });assert.equal(cli(b), first);
}],
['project A gets its applicable local and shared records', () => { write(records);const output = cli(a);assert.match(output, /a-only.*9\.9\.9/);assert.match(output, /shared.*1\.1\.0/); }],
['project settings disable a user-enabled plugin in the real statusline', () => {
write(records);fs.writeFileSync(path.join(config, 'settings.json'), JSON.stringify({ enabledPlugins: { 'global@fixture': true } }));
settings(b, 'settings.json', { 'global@fixture': false });assert.doesNotMatch(cli(b), /global/);
}],
['local settings override shared project settings without changing their bytes', () => {
settings(b, 'settings.local.json', { 'global@fixture': true });const file = path.join(b, '.claude/settings.json');const before = fs.readFileSync(file);assert.match(cli(b), /global/);assert.deepEqual(fs.readFileSync(file), before);
}],
['applicable local version overrides project and user records', () => {
write({ ...records, 'shared@fixture': [...records['shared@fixture'], { scope: 'local', projectPath: b, version: '4.4.4' }] });
assert.equal(readInstalledPlugins(config, b).find(x => x.name === 'shared').version, '4.4.4');
}],
['nested Git workspaces use the project inventory and local settings', () => {
require('node:child_process').execFileSync('git', ['init', '-q', b]);
const nested = path.join(b, 'src');fs.mkdirSync(nested);
write(records);settings(b, 'settings.local.json', { 'global@fixture': false });
const output = cli(nested);assert.match(output, /shared.*2\.2\.0/);assert.doesNotMatch(output, /global|a-only/);
}],
['a similarly named sibling path does not inherit B-scoped installations', () => {
const sibling = path.join(root, 'B-other');fs.mkdirSync(sibling);write(records);
const plugins = readInstalledPlugins(config, sibling);assert.equal(plugins.find(x => x.name === 'shared').version, '0.0.1');assert.ok(!plugins.some(x => x.name === 'a-only'));
}],
];
let passed = 0;let failed = 0;
try {
for (const [name, test] of tests) { try { test();console.log(`PASS ${name}`);passed++; } catch (error) { console.log(`FAIL ${name}: ${error.stack}`);failed++; } }
} finally { fs.rmSync(root, { recursive: true, force: true }); }
console.log(`Passed: ${passed}\nFailed: ${failed}`);process.exitCode = failed ? 1 : 0;