test(install): replace source-grep prompt assertions with structured checks
Two test files were asserting installer prompt behavior by regex/.includes() against bin/install.js source. Per CONTRIBUTING.md "no-source-grep" testing standard, replace with structured assertions: - tests/kilo-install.test.cjs: import runtimeMap and buildRuntimePromptText from the install module; assert runtimeMap['11'] === 'kilo' and that the rendered prompt lists Kilo above OpenCode without marketing copy. - tests/multi-runtime-select.test.cjs: import runtimeMap, allRuntimes, parseRuntimeInput, buildRuntimePromptText. Assert exported runtimeMap matches the canonical option list, allRuntimes contains every runtime exactly once, prompt text lists Hermes (10), Qwen Code (13), Trae (14), All (16), and parser splits/dedupes by exercising parseRuntimeInput rather than regexing source code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -225,12 +225,29 @@ describe('Source code integration (Kilo)', () => {
|
||||
});
|
||||
|
||||
test('promptRuntime runtimeMap has Kilo as option 11', () => {
|
||||
assert.ok(src.includes("'11': 'kilo'"), 'runtimeMap has 11 -> kilo');
|
||||
// Structural assertion against exported runtimeMap rather than source-grep.
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
delete require.cache[require.resolve(path.join(__dirname, '..', 'bin', 'install.js'))];
|
||||
const { runtimeMap } = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
assert.strictEqual(runtimeMap['11'], 'kilo', 'runtimeMap has 11 -> kilo');
|
||||
});
|
||||
|
||||
test('prompt text shows Kilo above OpenCode without marketing copy', () => {
|
||||
assert.ok(src.includes('11${reset}) Kilo'), 'prompt lists Kilo as option 11');
|
||||
assert.ok(!src.includes('the #1 AI coding platform on OpenRouter'), 'prompt does not include marketing tagline');
|
||||
// Call the exported prompt builder; assert against rendered text, not raw source.
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
delete require.cache[require.resolve(path.join(__dirname, '..', 'bin', 'install.js'))];
|
||||
const { buildRuntimePromptText } = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const promptText = buildRuntimePromptText();
|
||||
// Strip ANSI color codes so assertions don't depend on terminal escapes.
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const plain = promptText.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
assert.ok(/\b11\)\s*Kilo\b/.test(plain), 'prompt lists Kilo as option 11');
|
||||
const kiloIdx = plain.indexOf('11) Kilo');
|
||||
const opencodeIdx = plain.indexOf('OpenCode');
|
||||
assert.ok(kiloIdx > -1 && opencodeIdx > -1 && kiloIdx < opencodeIdx,
|
||||
'Kilo appears above OpenCode in prompt');
|
||||
assert.ok(!plain.includes('the #1 AI coding platform on OpenRouter'),
|
||||
'prompt does not include marketing tagline');
|
||||
});
|
||||
|
||||
test('hooks are skipped for Kilo', () => {
|
||||
|
||||
@@ -3,60 +3,29 @@
|
||||
* Verifies that promptRuntime accepts comma-separated, space-separated,
|
||||
* and single-choice inputs, deduplicates, and falls back to claude.
|
||||
* See issue #1281.
|
||||
*
|
||||
* Per CONTRIBUTING.md "no-source-grep" testing standard, prompt + parser
|
||||
* behavior is asserted via the install module's exported pure functions
|
||||
* (`runtimeMap`, `allRuntimes`, `parseRuntimeInput`, `buildRuntimePromptText`)
|
||||
* instead of regexing bin/install.js source text.
|
||||
*/
|
||||
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
// Read install.js source to extract the runtimeMap and parsing logic
|
||||
const installSrc = fs.readFileSync(
|
||||
path.join(__dirname, '..', 'bin', 'install.js'),
|
||||
'utf8'
|
||||
);
|
||||
const {
|
||||
runtimeMap,
|
||||
allRuntimes,
|
||||
parseRuntimeInput,
|
||||
buildRuntimePromptText,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
// Extract runtimeMap from source for validation
|
||||
const runtimeMap = {
|
||||
'1': 'claude',
|
||||
'2': 'antigravity',
|
||||
'3': 'augment',
|
||||
'4': 'cline',
|
||||
'5': 'codebuddy',
|
||||
'6': 'codex',
|
||||
'7': 'copilot',
|
||||
'8': 'cursor',
|
||||
'9': 'gemini',
|
||||
'10': 'hermes',
|
||||
'11': 'kilo',
|
||||
'12': 'opencode',
|
||||
'13': 'qwen',
|
||||
'14': 'trae',
|
||||
'15': 'windsurf'
|
||||
};
|
||||
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'gemini', 'hermes', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf'];
|
||||
|
||||
/**
|
||||
* Simulate the parsing logic from promptRuntime without requiring readline.
|
||||
* This mirrors the exact logic in the rl.question callback.
|
||||
*/
|
||||
function parseRuntimeInput(input) {
|
||||
input = input.trim() || '1';
|
||||
|
||||
if (input === '16') {
|
||||
return allRuntimes;
|
||||
}
|
||||
|
||||
const choices = input.split(/[\s,]+/).filter(Boolean);
|
||||
const selected = [];
|
||||
for (const c of choices) {
|
||||
const runtime = runtimeMap[c];
|
||||
if (runtime && !selected.includes(runtime)) {
|
||||
selected.push(runtime);
|
||||
}
|
||||
}
|
||||
|
||||
return selected.length > 0 ? selected : ['claude'];
|
||||
// Strip ANSI color codes for human-readable assertions on prompt text.
|
||||
function stripAnsi(s) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
||||
describe('multi-runtime selection parsing', () => {
|
||||
@@ -142,68 +111,75 @@ describe('multi-runtime selection parsing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('install.js source contains multi-select support', () => {
|
||||
test('runtimeMap is defined with all 15 runtimes', () => {
|
||||
for (const [key, name] of Object.entries(runtimeMap)) {
|
||||
assert.ok(
|
||||
installSrc.includes(`'${key}': '${name}'`),
|
||||
`runtimeMap has ${key} -> ${name}`
|
||||
);
|
||||
describe('install.js exports multi-select runtime metadata', () => {
|
||||
const expectedRuntimeMap = {
|
||||
'1': 'claude',
|
||||
'2': 'antigravity',
|
||||
'3': 'augment',
|
||||
'4': 'cline',
|
||||
'5': 'codebuddy',
|
||||
'6': 'codex',
|
||||
'7': 'copilot',
|
||||
'8': 'cursor',
|
||||
'9': 'gemini',
|
||||
'10': 'hermes',
|
||||
'11': 'kilo',
|
||||
'12': 'opencode',
|
||||
'13': 'qwen',
|
||||
'14': 'trae',
|
||||
'15': 'windsurf',
|
||||
};
|
||||
const expectedRuntimes = [
|
||||
'claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex',
|
||||
'copilot', 'cursor', 'gemini', 'hermes', 'kilo', 'opencode', 'qwen',
|
||||
'trae', 'windsurf',
|
||||
];
|
||||
|
||||
test('runtimeMap exports every option key bound to the right runtime', () => {
|
||||
assert.deepStrictEqual(runtimeMap, expectedRuntimeMap,
|
||||
'exported runtimeMap matches the canonical option list');
|
||||
});
|
||||
|
||||
test('allRuntimes contains every runtime exactly once', () => {
|
||||
assert.strictEqual(allRuntimes.length, expectedRuntimes.length);
|
||||
for (const rt of expectedRuntimes) {
|
||||
assert.ok(allRuntimes.includes(rt), `allRuntimes contains ${rt}`);
|
||||
}
|
||||
assert.strictEqual(new Set(allRuntimes).size, allRuntimes.length,
|
||||
'allRuntimes has no duplicates');
|
||||
});
|
||||
|
||||
test('allRuntimes array contains all runtimes', () => {
|
||||
const match = installSrc.match(/const allRuntimes = \[([^\]]+)\]/);
|
||||
assert.ok(match, 'allRuntimes array found');
|
||||
for (const rt of allRuntimes) {
|
||||
assert.ok(match[1].includes(`'${rt}'`), `allRuntimes includes ${rt}`);
|
||||
}
|
||||
test('"All" shortcut (option 16) selects every runtime', () => {
|
||||
assert.deepStrictEqual(parseRuntimeInput('16'), allRuntimes);
|
||||
});
|
||||
|
||||
test('all shortcut uses option 16', () => {
|
||||
assert.ok(
|
||||
installSrc.includes("if (input === '16')"),
|
||||
'all shortcut uses option 16'
|
||||
);
|
||||
});
|
||||
|
||||
test('prompt lists Hermes Agent as option 10, Qwen Code as option 13, and All as option 16', () => {
|
||||
assert.ok(
|
||||
installSrc.includes('10${reset}) Hermes Agent'),
|
||||
'prompt lists Hermes Agent as option 10'
|
||||
);
|
||||
assert.ok(
|
||||
installSrc.includes('13${reset}) Qwen Code'),
|
||||
'prompt lists Qwen Code as option 13'
|
||||
);
|
||||
assert.ok(
|
||||
installSrc.includes('14${reset}) Trae'),
|
||||
'prompt lists Trae as option 14'
|
||||
);
|
||||
assert.ok(
|
||||
installSrc.includes('16${reset}) All'),
|
||||
'prompt lists All as option 16'
|
||||
);
|
||||
test('prompt lists Hermes Agent (10), Qwen Code (13), Trae (14), and All (16)', () => {
|
||||
const prompt = stripAnsi(buildRuntimePromptText());
|
||||
assert.ok(/\b10\)\s*Hermes Agent\b/.test(prompt),
|
||||
'prompt lists Hermes Agent as option 10');
|
||||
assert.ok(/\b13\)\s*Qwen Code\b/.test(prompt),
|
||||
'prompt lists Qwen Code as option 13');
|
||||
assert.ok(/\b14\)\s*Trae\b/.test(prompt),
|
||||
'prompt lists Trae as option 14');
|
||||
assert.ok(/\b16\)\s*All\b/.test(prompt),
|
||||
'prompt lists All as option 16');
|
||||
});
|
||||
|
||||
test('prompt text shows multi-select hint', () => {
|
||||
assert.ok(
|
||||
installSrc.includes('Select multiple'),
|
||||
'prompt includes multi-select instructions'
|
||||
);
|
||||
const prompt = stripAnsi(buildRuntimePromptText());
|
||||
assert.ok(/Select multiple/i.test(prompt),
|
||||
'prompt includes multi-select instructions');
|
||||
});
|
||||
|
||||
test('parsing uses split with comma and space regex', () => {
|
||||
assert.ok(
|
||||
installSrc.includes("split(/[\\s,]+/)"),
|
||||
'input is split on commas and whitespace'
|
||||
);
|
||||
});
|
||||
|
||||
test('deduplication check exists', () => {
|
||||
assert.ok(
|
||||
installSrc.includes('!selected.includes(runtime)'),
|
||||
'deduplication guard exists'
|
||||
test('parser splits on commas and whitespace and deduplicates', () => {
|
||||
// Behavioral assertion: same set of choices in different separators
|
||||
// produces the same selection, and duplicates collapse.
|
||||
assert.deepStrictEqual(
|
||||
parseRuntimeInput('1,7,9'),
|
||||
parseRuntimeInput('1 7 9'),
|
||||
'comma- and space-separated input yield identical selections'
|
||||
);
|
||||
assert.deepStrictEqual(parseRuntimeInput('1,1,7,7'), ['claude', 'copilot'],
|
||||
'duplicates collapsed in order');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user