Files
ccs/tests/unit/commands/arg-extractor.test.ts
T

224 lines
6.8 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import { extractOption, hasAnyFlag, scanCommandArgs } from '../../../src/commands/arg-extractor';
describe('arg-extractor', () => {
describe('extractOption', () => {
it('extracts --flag value and removes both tokens from remaining args', () => {
const result = extractOption(['--profile', 'gemini', '--yes'], ['--profile']);
expect(result).toEqual({
found: true,
value: 'gemini',
missingValue: false,
remainingArgs: ['--yes'],
});
});
it('extracts --flag=value and removes inline token from remaining args', () => {
const result = extractOption(['--yes', '--profile=codex', 'prompt'], ['--profile']);
expect(result).toEqual({
found: true,
value: 'codex',
missingValue: false,
remainingArgs: ['--yes', 'prompt'],
});
});
it('marks missing value when flag is last token', () => {
const result = extractOption(['prompt', '--profile'], ['--profile']);
expect(result).toEqual({
found: true,
missingValue: true,
remainingArgs: ['prompt'],
});
});
it('marks missing value for empty inline value', () => {
const result = extractOption(['--profile=', '--yes'], ['--profile']);
expect(result).toEqual({
found: true,
missingValue: true,
remainingArgs: ['--yes'],
});
});
it('marks missing value when next token is another flag and keeps that flag', () => {
const result = extractOption(['--profile', '--yes', 'prompt'], ['--profile']);
expect(result).toEqual({
found: true,
missingValue: true,
remainingArgs: ['--yes', 'prompt'],
});
});
it('accepts dash-prefixed value when allowDashValue is enabled', () => {
const result = extractOption(['--model', '-preview', '--yes'], ['--model'], {
allowDashValue: true,
knownFlags: ['--model', '--yes'],
});
expect(result).toEqual({
found: true,
value: '-preview',
missingValue: false,
remainingArgs: ['--yes'],
});
});
it('accepts long dash-prefixed values when allowLongDashValue is enabled', () => {
const result = extractOption(['--out', '--snapshot.json', '--yes'], ['--out'], {
allowDashValue: true,
allowLongDashValue: true,
knownFlags: ['--out', '--yes'],
});
expect(result).toEqual({
found: true,
value: '--snapshot.json',
missingValue: false,
remainingArgs: ['--yes'],
});
});
it('still treats known flags as missing when allowDashValue is enabled', () => {
const result = extractOption(['--model', '--yes', 'prompt'], ['--model'], {
allowDashValue: true,
knownFlags: ['--model', '--yes'],
});
expect(result).toEqual({
found: true,
missingValue: true,
remainingArgs: ['--yes', 'prompt'],
});
});
it('still treats known long flags as missing when allowLongDashValue is enabled', () => {
const result = extractOption(['--out', '--yes', 'prompt'], ['--out'], {
allowDashValue: true,
allowLongDashValue: true,
knownFlags: ['--out', '--yes'],
});
expect(result).toEqual({
found: true,
missingValue: true,
remainingArgs: ['--yes', 'prompt'],
});
});
it('supports repeated extraction loops with deterministic last-value wins behavior', () => {
let remaining = ['--model', 'gpt-4.1-mini', '--model', 'gpt-4.1'];
let selected: string | undefined;
while (true) {
const extracted = extractOption(remaining, ['--model']);
if (!extracted.found) {
break;
}
if (!extracted.missingValue && extracted.value) {
selected = extracted.value;
}
remaining = extracted.remainingArgs;
}
expect(selected).toBe('gpt-4.1');
expect(remaining).toEqual([]);
});
it('returns non-match state without altering args content', () => {
const args = ['--yes', 'prompt'];
const result = extractOption(args, ['--profile', '-p']);
expect(result).toEqual({
found: false,
missingValue: false,
remainingArgs: ['--yes', 'prompt'],
});
expect(args).toEqual(['--yes', 'prompt']);
});
});
describe('hasAnyFlag', () => {
it('returns true when any exact flag is present', () => {
expect(hasAnyFlag(['prompt', '--yes'], ['--yes', '-y'])).toBe(true);
expect(hasAnyFlag(['prompt', '-y'], ['--yes', '-y'])).toBe(true);
});
it('supports inline truthy values for boolean flags', () => {
expect(hasAnyFlag(['prompt', '--yes=true'], ['--yes', '-y'])).toBe(true);
expect(hasAnyFlag(['prompt', '--yes=1'], ['--yes', '-y'])).toBe(true);
expect(hasAnyFlag(['prompt', '--yes=on'], ['--yes', '-y'])).toBe(true);
});
it('returns false for non-truthy or unrelated inline tokens', () => {
expect(hasAnyFlag(['prompt', '--yes=false'], ['--yes', '-y'])).toBe(false);
expect(hasAnyFlag(['prompt', '--profile=gemini'], ['--yes', '-y'])).toBe(false);
});
});
describe('scanCommandArgs', () => {
it('collects positionals and unknown flags while ignoring known boolean flags', () => {
const result = scanCommandArgs(['profile-a', '--yes', '--bogus', 'extra'], {
knownFlags: ['--yes'],
});
expect(result).toEqual({
positionals: ['profile-a', 'extra'],
unknownFlags: ['--bogus'],
});
});
it('skips values for known value flags', () => {
const result = scanCommandArgs(['--model', 'claude-3-7-sonnet', 'profile-a', '--force'], {
knownFlags: ['--model', '--force'],
valueFlags: ['--model'],
});
expect(result).toEqual({
positionals: ['profile-a'],
unknownFlags: [],
});
});
it('preserves option-terminator positionals', () => {
const result = scanCommandArgs(['--yes', '--', '--literal-name'], {
knownFlags: ['--yes'],
});
expect(result).toEqual({
positionals: ['--literal-name'],
unknownFlags: [],
});
});
it('consumes single-dash values but not long-flag lookalikes when allowDashValue is enabled', () => {
const singleDashValue = scanCommandArgs(['--model', '-preview', '--yes'], {
knownFlags: ['--model', '--yes'],
valueFlags: ['--model'],
allowDashValue: true,
});
const longFlagLookalike = scanCommandArgs(['--model', '--target', 'droid'], {
knownFlags: ['--model', '--target'],
valueFlags: ['--model'],
allowDashValue: true,
});
expect(singleDashValue).toEqual({
positionals: [],
unknownFlags: [],
});
expect(longFlagLookalike).toEqual({
positionals: ['droid'],
unknownFlags: [],
});
});
});
});