|
1 | 1 | import test from 'node:test'; |
2 | 2 | import assert from 'node:assert/strict'; |
3 | 3 | import { parseArgs, usage } from '../args.ts'; |
| 4 | +import { AppError } from '../errors.ts'; |
| 5 | +import { getCliCommandNames, getSchemaCapabilityKeys } from '../command-schema.ts'; |
| 6 | +import { listCapabilityCommands } from '../../core/capabilities.ts'; |
4 | 7 |
|
5 | 8 | test('parseArgs recognizes --relaunch', () => { |
6 | 9 | const parsed = parseArgs(['open', 'settings', '--relaunch']); |
@@ -61,6 +64,107 @@ test('parseArgs rejects invalid swipe pattern', () => { |
61 | 64 |
|
62 | 65 | test('usage includes --relaunch flag', () => { |
63 | 66 | assert.match(usage(), /--relaunch/); |
| 67 | + assert.match(usage(), /pinch <scale> \[x\] \[y\]/); |
| 68 | + assert.match(usage(), /--metadata/); |
| 69 | +}); |
| 70 | + |
| 71 | +test('every capability command has a parser schema entry', () => { |
| 72 | + const schemaCommands = new Set(getCliCommandNames()); |
| 73 | + for (const command of listCapabilityCommands()) { |
| 74 | + assert.equal(schemaCommands.has(command), true, `Missing schema for command: ${command}`); |
| 75 | + } |
| 76 | +}); |
| 77 | + |
| 78 | +test('schema capability mappings match capability source-of-truth', () => { |
| 79 | + assert.deepEqual(getSchemaCapabilityKeys(), listCapabilityCommands()); |
| 80 | +}); |
| 81 | + |
| 82 | +test('compat mode warns and strips unsupported pilot-command flags', () => { |
| 83 | + const parsed = parseArgs(['press', '10', '20', '--depth', '2'], { strictFlags: false }); |
| 84 | + assert.equal(parsed.command, 'press'); |
| 85 | + assert.equal(parsed.flags.snapshotDepth, undefined); |
| 86 | + assert.equal(parsed.warnings.length, 1); |
| 87 | + assert.match(parsed.warnings[0], /not supported for command press/); |
| 88 | +}); |
| 89 | + |
| 90 | +test('strict mode rejects unsupported pilot-command flags', () => { |
| 91 | + assert.throws( |
| 92 | + () => parseArgs(['press', '10', '20', '--depth', '2'], { strictFlags: true }), |
| 93 | + (error) => |
| 94 | + error instanceof AppError && |
| 95 | + error.code === 'INVALID_ARGS' && |
| 96 | + error.message.includes('not supported for command press'), |
| 97 | + ); |
| 98 | +}); |
| 99 | + |
| 100 | +test('snapshot command accepts command-specific flags', () => { |
| 101 | + const parsed = parseArgs(['snapshot', '-i', '-c', '--depth', '3', '-s', 'Login'], { strictFlags: true }); |
| 102 | + assert.equal(parsed.command, 'snapshot'); |
| 103 | + assert.equal(parsed.flags.snapshotInteractiveOnly, true); |
| 104 | + assert.equal(parsed.flags.snapshotCompact, true); |
| 105 | + assert.equal(parsed.flags.snapshotDepth, 3); |
| 106 | + assert.equal(parsed.flags.snapshotScope, 'Login'); |
| 107 | +}); |
| 108 | + |
| 109 | +test('unknown short flags are rejected', () => { |
| 110 | + assert.throws( |
| 111 | + () => parseArgs(['press', '10', '20', '-x'], { strictFlags: true }), |
| 112 | + (error) => error instanceof AppError && error.code === 'INVALID_ARGS' && error.message === 'Unknown flag: -x', |
| 113 | + ); |
| 114 | +}); |
| 115 | + |
| 116 | +test('negative numeric positionals are accepted without -- separator', () => { |
| 117 | + const typed = parseArgs(['type', '-123'], { strictFlags: true }); |
| 118 | + assert.equal(typed.command, 'type'); |
| 119 | + assert.deepEqual(typed.positionals, ['-123']); |
| 120 | + |
| 121 | + const typedMulti = parseArgs(['type', '-123', '-456'], { strictFlags: true }); |
| 122 | + assert.equal(typedMulti.command, 'type'); |
| 123 | + assert.deepEqual(typedMulti.positionals, ['-123', '-456']); |
| 124 | + |
| 125 | + const pressed = parseArgs(['press', '-10', '20'], { strictFlags: true }); |
| 126 | + assert.equal(pressed.command, 'press'); |
| 127 | + assert.deepEqual(pressed.positionals, ['-10', '20']); |
| 128 | +}); |
| 129 | + |
| 130 | +test('command-specific flags without command fail in strict mode', () => { |
| 131 | + assert.throws( |
| 132 | + () => parseArgs(['--depth', '3'], { strictFlags: true }), |
| 133 | + (error) => |
| 134 | + error instanceof AppError && |
| 135 | + error.code === 'INVALID_ARGS' && |
| 136 | + error.message.includes('requires a command that supports it'), |
| 137 | + ); |
| 138 | +}); |
| 139 | + |
| 140 | +test('command-specific flags without command warn and strip in compat mode', () => { |
| 141 | + const parsed = parseArgs(['--depth', '3'], { strictFlags: false }); |
| 142 | + assert.equal(parsed.command, null); |
| 143 | + assert.equal(parsed.flags.snapshotDepth, undefined); |
| 144 | + assert.equal(parsed.warnings.length, 1); |
| 145 | + assert.match(parsed.warnings[0], /requires a command that supports/); |
| 146 | +}); |
| 147 | + |
| 148 | +test('all commands participate in strict command-flag validation', () => { |
| 149 | + assert.throws( |
| 150 | + () => parseArgs(['open', 'Settings', '--depth', '1'], { strictFlags: true }), |
| 151 | + (error) => |
| 152 | + error instanceof AppError && |
| 153 | + error.code === 'INVALID_ARGS' && |
| 154 | + error.message.includes('not supported for command open'), |
| 155 | + ); |
| 156 | +}); |
| 157 | + |
| 158 | +test('invalid enum/range errors are deterministic', () => { |
| 159 | + assert.throws( |
| 160 | + () => parseArgs(['snapshot', '--backend', 'foo'], { strictFlags: true }), |
| 161 | + (error) => |
| 162 | + error instanceof AppError && error.code === 'INVALID_ARGS' && error.message === 'Invalid backend: foo', |
| 163 | + ); |
| 164 | + assert.throws( |
| 165 | + () => parseArgs(['snapshot', '--depth', '-1'], { strictFlags: true }), |
| 166 | + (error) => error instanceof AppError && error.code === 'INVALID_ARGS' && error.message === 'Invalid depth: -1', |
| 167 | + ); |
64 | 168 | }); |
65 | 169 |
|
66 | 170 | test('usage includes swipe and press series options', () => { |
|
0 commit comments