@@ -6,7 +6,7 @@ import { describe, it } from 'node:test';
66
77import type { StringOption } from '../src/types.js' ;
88
9- import { bargs , handle , map } from '../src/bargs.js' ;
9+ import { bargs , handle , map , merge } from '../src/bargs.js' ;
1010import { opt , pos } from '../src/opt.js' ;
1111
1212describe ( 'bargs()' , ( ) => {
@@ -28,6 +28,12 @@ describe('bargs()', () => {
2828
2929 expect ( cli , 'to be defined' ) ;
3030 } ) ;
31+
32+ it ( 'accepts custom theme' , ( ) => {
33+ const cli = bargs ( 'test-cli' , { theme : 'mono' } ) ;
34+
35+ expect ( cli , 'to be defined' ) ;
36+ } ) ;
3137} ) ;
3238
3339describe ( '.globals()' , ( ) => {
@@ -722,3 +728,232 @@ describe('command aliases', () => {
722728 } ) ;
723729 } ) ;
724730} ) ;
731+
732+ describe ( 'merge() edge cases' , ( ) => {
733+ it ( 'throws when called with no parsers' , ( ) => {
734+ expect (
735+ // @ts -expect-error testing runtime behavior
736+ ( ) => merge ( ) ,
737+ 'to throw' ,
738+ / m e r g e \( \) r e q u i r e s a t l e a s t o n e p a r s e r / ,
739+ ) ;
740+ } ) ;
741+
742+ it ( 'chains transforms from both parsers' , async ( ) => {
743+ // First parser with a transform
744+ const p1 = map (
745+ opt . options ( { x : opt . number ( { default : 1 } ) } ) ,
746+ ( { values } ) => ( {
747+ positionals : [ ] as const ,
748+ values : { ...values , doubled : values . x * 2 } ,
749+ } ) ,
750+ ) ;
751+
752+ // Second parser with a transform
753+ const p2 = map (
754+ opt . options ( { y : opt . number ( { default : 2 } ) } ) ,
755+ ( { values } ) => ( {
756+ positionals : [ ] as const ,
757+ values : { ...values , tripled : values . y * 3 } ,
758+ } ) ,
759+ ) ;
760+
761+ // Merge preserves transforms (though behavior depends on implementation)
762+ const merged = merge ( p1 , p2 ) ;
763+
764+ expect ( merged . __brand , 'to be' , 'Parser' ) ;
765+ expect ( merged . __optionsSchema , 'to satisfy' , {
766+ x : { type : 'number' } ,
767+ y : { type : 'number' } ,
768+ } ) ;
769+ } ) ;
770+
771+ it ( 'merges four parsers' , ( ) => {
772+ const p1 = opt . options ( { a : opt . boolean ( ) } ) ;
773+ const p2 = opt . options ( { b : opt . string ( ) } ) ;
774+ const p3 = opt . options ( { c : opt . number ( ) } ) ;
775+ const p4 = pos . positionals ( pos . string ( { name : 'file' } ) ) ;
776+
777+ const merged = merge ( p1 , p2 , p3 , p4 ) ;
778+
779+ expect ( merged . __optionsSchema , 'to satisfy' , {
780+ a : { type : 'boolean' } ,
781+ b : { type : 'string' } ,
782+ c : { type : 'number' } ,
783+ } ) ;
784+ expect ( merged . __positionalsSchema , 'to have length' , 1 ) ;
785+ } ) ;
786+ } ) ;
787+
788+ describe ( 'error paths' , ( ) => {
789+ it ( 'throws HelpError when no command specified and no default' , async ( ) => {
790+ const cli = bargs ( 'test-cli' )
791+ . command (
792+ 'run' ,
793+ handle ( opt . options ( { } ) , ( ) => { } ) ,
794+ )
795+ . command (
796+ 'build' ,
797+ handle ( opt . options ( { } ) , ( ) => { } ) ,
798+ ) ;
799+ // No defaultCommand set
800+
801+ await expectAsync (
802+ cli . parseAsync ( [ ] ) ,
803+ 'to reject with error satisfying' ,
804+ / N o c o m m a n d s p e c i f i e d / ,
805+ ) ;
806+ } ) ;
807+
808+ it ( 'handles async global transform in nested commands' , async ( ) => {
809+ let result : unknown ;
810+
811+ const cli = bargs ( 'test-cli' )
812+ . globals (
813+ map ( opt . options ( { verbose : opt . boolean ( ) } ) , async ( { values } ) => {
814+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1 ) ) ;
815+ return { positionals : [ ] as const , values : { ...values , ts : 123 } } ;
816+ } ) ,
817+ )
818+ . command ( 'parent' , ( parent ) =>
819+ parent . command ( 'child' , opt . options ( { } ) , ( { values } ) => {
820+ result = values ;
821+ } ) ,
822+ ) ;
823+
824+ await cli . parseAsync ( [ '--verbose' , 'parent' , 'child' ] ) ;
825+
826+ expect ( result , 'to satisfy' , {
827+ ts : 123 ,
828+ verbose : true ,
829+ } ) ;
830+ } ) ;
831+
832+ it ( 'throws when sync parse() has async global transform in nested command' , ( ) => {
833+ const cli = bargs ( 'test-cli' )
834+ . globals (
835+ map ( opt . options ( { verbose : opt . boolean ( ) } ) , async ( { values } ) => {
836+ await Promise . resolve ( ) ;
837+ return { positionals : [ ] as const , values } ;
838+ } ) ,
839+ )
840+ . command ( 'parent' , ( parent ) =>
841+ parent . command ( 'child' , opt . options ( { } ) , ( ) => { } ) ,
842+ ) ;
843+
844+ expect (
845+ ( ) => cli . parse ( [ 'parent' , 'child' ] ) ,
846+ 'to throw' ,
847+ / A s y n c .* g l o b a l t r a n s f o r m .* U s e p a r s e A s y n c / ,
848+ ) ;
849+ } ) ;
850+ } ) ;
851+
852+ describe ( 'defaultCommand edge cases' , ( ) => {
853+ it ( 'defaultCommand with Command object' , async ( ) => {
854+ let executed = false ;
855+
856+ const cmd = handle ( opt . options ( { flag : opt . boolean ( ) } ) , ( ) => {
857+ executed = true ;
858+ } ) ;
859+
860+ const cli = bargs ( 'test-cli' ) . defaultCommand ( cmd ) ;
861+
862+ await cli . parseAsync ( [ '--flag' ] ) ;
863+
864+ expect ( executed , 'to be' , true ) ;
865+ } ) ;
866+
867+ it ( 'defaultCommand with Parser and handler' , async ( ) => {
868+ let result : unknown ;
869+
870+ const cli = bargs ( 'test-cli' ) . defaultCommand (
871+ opt . options ( { name : opt . string ( { default : 'world' } ) } ) ,
872+ ( { values } ) => {
873+ result = values ;
874+ } ,
875+ ) ;
876+
877+ await cli . parseAsync ( [ '--name' , 'Alice' ] ) ;
878+
879+ expect ( result , 'to satisfy' , { name : 'Alice' } ) ;
880+ } ) ;
881+ } ) ;
882+
883+ describe ( 'command transforms' , ( ) => {
884+ it ( 'applies both global and command transforms' , async ( ) => {
885+ let result : unknown ;
886+
887+ const cli = bargs ( 'test-cli' )
888+ . globals (
889+ map ( opt . options ( { x : opt . number ( { default : 1 } ) } ) , ( { values } ) => ( {
890+ positionals : [ ] as const ,
891+ values : { ...values , globalTransformed : true } ,
892+ } ) ) ,
893+ )
894+ . command (
895+ 'run' ,
896+ map ( opt . options ( { y : opt . number ( { default : 2 } ) } ) , ( { values } ) => ( {
897+ positionals : [ ] as const ,
898+ values : { ...values , commandTransformed : true } ,
899+ } ) ) ,
900+ ( { values } ) => {
901+ result = values ;
902+ } ,
903+ ) ;
904+
905+ await cli . parseAsync ( [ 'run' , '--x' , '10' , '--y' , '20' ] ) ;
906+
907+ expect ( result , 'to satisfy' , {
908+ commandTransformed : true ,
909+ globalTransformed : true ,
910+ x : 10 ,
911+ y : 20 ,
912+ } ) ;
913+ } ) ;
914+
915+ it ( 'applies async command transform' , async ( ) => {
916+ let result : unknown ;
917+
918+ const cli = bargs ( 'test-cli' ) . command (
919+ 'run' ,
920+ map (
921+ opt . options ( { delay : opt . number ( { default : 1 } ) } ) ,
922+ async ( { values } ) => {
923+ await new Promise ( ( resolve ) => setTimeout ( resolve , values . delay ) ) ;
924+ return {
925+ positionals : [ ] as const ,
926+ values : { ...values , asyncDone : true } ,
927+ } ;
928+ } ,
929+ ) ,
930+ ( { values } ) => {
931+ result = values ;
932+ } ,
933+ ) ;
934+
935+ await cli . parseAsync ( [ 'run' , '--delay' , '1' ] ) ;
936+
937+ expect ( result , 'to satisfy' , {
938+ asyncDone : true ,
939+ delay : 1 ,
940+ } ) ;
941+ } ) ;
942+
943+ it ( 'throws on sync parse() with async command transform' , ( ) => {
944+ const cli = bargs ( 'test-cli' ) . command (
945+ 'run' ,
946+ map ( opt . options ( { } ) , async ( r ) => {
947+ await Promise . resolve ( ) ;
948+ return r ;
949+ } ) ,
950+ ( ) => { } ,
951+ ) ;
952+
953+ expect (
954+ ( ) => cli . parse ( [ 'run' ] ) ,
955+ 'to throw' ,
956+ / A s y n c .* c o m m a n d t r a n s f o r m .* U s e p a r s e A s y n c / ,
957+ ) ;
958+ } ) ;
959+ } ) ;
0 commit comments