11import { describe , it , expect , beforeEach , afterEach , vi } from 'vitest' ;
2+ import { parseFlag } from '../src/cli.js' ;
23
34// Use vi.hoisted for proper test isolation
45const mocks = vi . hoisted ( ( ) => ( {
@@ -22,6 +23,9 @@ const mocks = vi.hoisted(() => ({
2223 'opencode' ,
2324 'copilot' ,
2425 ] ) ,
26+ exists : vi . fn ( ( name : string ) =>
27+ [ 'kiro' , 'claude' , 'gemini' , 'opencode' , 'copilot' ] . includes ( name )
28+ ) ,
2529 getHelpText : vi . fn (
2630 ( ) =>
2731 ` kiro Generate .amazonq/cli-agents/vibe.json (Kiro/Amazon Q)
@@ -31,6 +35,20 @@ const mocks = vi.hoisted(() => ({
3135 copilot Generate .vscode/mcp.json, .github/agents/Vibe.agent.md`
3236 ) ,
3337 } ,
38+ SkillGeneratorRegistry : {
39+ getGeneratorNames : vi . fn ( ( ) => [
40+ 'claude' ,
41+ 'gemini' ,
42+ 'opencode' ,
43+ 'copilot' ,
44+ 'kiro' ,
45+ ] ) ,
46+ exists : vi . fn ( ( name : string ) =>
47+ [ 'claude' , 'gemini' , 'opencode' , 'copilot' , 'kiro' ] . includes ( name )
48+ ) ,
49+ getHelpText : vi . fn ( ( ) => '' ) ,
50+ } ,
51+ generateSkill : vi . fn ( ) ,
3452} ) ) ;
3553
3654vi . mock ( '@codemcp/workflows-core' , ( ) => ( {
@@ -64,6 +82,11 @@ vi.mock('../src/config-generator.js', () => ({
6482 GeneratorRegistry : mocks . GeneratorRegistry ,
6583} ) ) ;
6684
85+ vi . mock ( '../src/skill-generator.js' , ( ) => ( {
86+ generateSkill : mocks . generateSkill ,
87+ SkillGeneratorRegistry : mocks . SkillGeneratorRegistry ,
88+ } ) ) ;
89+
6790describe ( 'CLI' , ( ) => {
6891 let originalArgv : string [ ] ;
6992 let originalExit : typeof process . exit ;
@@ -142,8 +165,9 @@ describe('CLI', () => {
142165 kill : vi . fn ( ) ,
143166 } as unknown ) ;
144167
145- // Mock generateConfig to prevent actual file generation
168+ // Mock generateConfig and generateSkill to prevent actual file generation
146169 mocks . generateConfig . mockResolvedValue ( undefined ) ;
170+ mocks . generateSkill . mockResolvedValue ( undefined ) ;
147171
148172 // Import from source
149173 const module = await import ( '../src/cli.js' ) ;
@@ -385,6 +409,48 @@ describe('CLI', () => {
385409 } ) ;
386410 } ) ;
387411
412+ describe ( 'Setup Command --mode flag' , ( ) => {
413+ it ( 'should call generateConfig when --mode config is passed (space notation)' , async ( ) => {
414+ process . argv = [ 'node' , 'cli.js' , 'setup' , 'claude' , '--mode' , 'config' ] ;
415+
416+ await runCli ( ) ;
417+
418+ expect ( mocks . generateConfig ) . toHaveBeenCalledWith ( 'claude' , '/mock/cwd' ) ;
419+ } ) ;
420+
421+ it ( 'should call generateConfig when --mode=config is passed (equals notation)' , async ( ) => {
422+ process . argv = [ 'node' , 'cli.js' , 'setup' , 'claude' , '--mode=config' ] ;
423+
424+ await runCli ( ) ;
425+
426+ expect ( mocks . generateConfig ) . toHaveBeenCalledWith ( 'claude' , '/mock/cwd' ) ;
427+ } ) ;
428+
429+ it ( 'should show error for invalid --mode value' , async ( ) => {
430+ process . argv = [ 'node' , 'cli.js' , 'setup' , 'claude' , '--mode=invalid' ] ;
431+
432+ processExitSpy . mockImplementation ( ( ) => undefined as never ) ;
433+
434+ await runCli ( ) ;
435+
436+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
437+ expect . stringContaining ( '--mode must be "skill" or "config"' )
438+ ) ;
439+ } ) ;
440+
441+ it ( 'should show error for invalid --mode= value (equals notation)' , async ( ) => {
442+ process . argv = [ 'node' , 'cli.js' , 'setup' , 'claude' , '--mode=' ] ;
443+
444+ processExitSpy . mockImplementation ( ( ) => undefined as never ) ;
445+
446+ await runCli ( ) ;
447+
448+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
449+ expect . stringContaining ( '--mode must be "skill" or "config"' )
450+ ) ;
451+ } ) ;
452+ } ) ;
453+
388454 describe ( 'Unknown Arguments' , ( ) => {
389455 it ( 'should show error for unknown command' , ( ) => {
390456 process . argv = [ 'node' , 'cli.js' , '--unknown-flag' ] ;
@@ -401,6 +467,38 @@ describe('CLI', () => {
401467 } ) ;
402468 } ) ;
403469
470+ describe ( 'parseFlag helper' , ( ) => {
471+ it ( 'returns value for --flag value notation' , ( ) => {
472+ expect ( parseFlag ( [ '--mode' , 'config' ] , '--mode' ) ) . toBe ( 'config' ) ;
473+ } ) ;
474+
475+ it ( 'returns value for --flag=value notation' , ( ) => {
476+ expect ( parseFlag ( [ '--mode=config' ] , '--mode' ) ) . toBe ( 'config' ) ;
477+ } ) ;
478+
479+ it ( 'returns undefined when flag is absent' , ( ) => {
480+ expect ( parseFlag ( [ 'setup' , 'claude' ] , '--mode' ) ) . toBeUndefined ( ) ;
481+ } ) ;
482+
483+ it ( 'returns undefined when --flag appears last with no following value' , ( ) => {
484+ expect ( parseFlag ( [ '--mode' ] , '--mode' ) ) . toBeUndefined ( ) ;
485+ } ) ;
486+
487+ it ( 'handles empty string value from --flag= notation' , ( ) => {
488+ expect ( parseFlag ( [ '--flag=' ] , '--flag' ) ) . toBe ( '' ) ;
489+ } ) ;
490+
491+ it ( 'does not confuse prefix-matching flags (--mode vs --mode-extra)' , ( ) => {
492+ expect ( parseFlag ( [ '--mode-extra=config' ] , '--mode' ) ) . toBeUndefined ( ) ;
493+ } ) ;
494+
495+ it ( 'returns first match when flag appears multiple times' , ( ) => {
496+ expect ( parseFlag ( [ '--mode=skill' , '--mode=config' ] , '--mode' ) ) . toBe (
497+ 'skill'
498+ ) ;
499+ } ) ;
500+ } ) ;
501+
404502 describe ( 'Default Behavior' , ( ) => {
405503 it ( 'should start visualization tool by default' , ( ) => {
406504 process . argv = [ 'node' , 'cli.js' ] ;
0 commit comments