@@ -537,13 +537,13 @@ test('mapNodeModules - packageDependencies hook no modification (snapshot)', asy
537537test ( 'mapNodeModules - unknownCanonicalName hook called for missing policy resources' , async t => {
538538 t . plan ( 6 ) ;
539539
540- /** @type {Array<{canonicalName: CanonicalName, path: string[], issue : string}> } */
540+ /** @type {Array<{canonicalName: CanonicalName, path: string[], message : string}> } */
541541 const hookCalls = [ ] ;
542542
543543 /** @type {HookConfiguration<MapNodeModulesHooks> } */
544544 const hooks = {
545- unknownCanonicalName : ( { canonicalName, path, issue } ) => {
546- hookCalls . push ( { canonicalName, path, issue } ) ;
545+ unknownCanonicalName : ( { canonicalName, path, message } ) => {
546+ hookCalls . push ( { canonicalName, path, message } ) ;
547547 } ,
548548 } ;
549549
@@ -589,10 +589,10 @@ test('mapNodeModules - unknownCanonicalName hook called for missing policy resou
589589 'should provide correct path for unknown resource' ,
590590 ) ;
591591 t . true (
592- unknownResourceCall ?. issue . includes (
592+ unknownResourceCall ?. message . includes (
593593 'Resource "unknown-package" was not found' ,
594594 ) ,
595- 'should provide descriptive issue message for unknown resource' ,
595+ 'should provide descriptive message for unknown resource' ,
596596 ) ;
597597
598598 // Check hook call for unknown nested package
@@ -625,3 +625,121 @@ test('mapNodeModules - unknownCanonicalName hook not called when all resources e
625625
626626 t . false ( hookCalled , 'should not call hook when all policy resources exist' ) ;
627627} ) ;
628+
629+ test ( 'mapNodeModules - unknownCanonicalName hook includes suggestions when available' , async t => {
630+ /** @type {Array<{canonicalName: CanonicalName, path: string[], message: string, suggestion?: CanonicalName}> } */
631+ const hookCalls = [ ] ;
632+
633+ /** @type {HookConfiguration<MapNodeModulesHooks> } */
634+ const hooks = {
635+ unknownCanonicalName : ( { canonicalName, path, message, suggestion } ) => {
636+ hookCalls . push ( { canonicalName, path, message, suggestion } ) ;
637+ } ,
638+ } ;
639+
640+ // Policy with typos that should trigger suggestions
641+ /** @type {SomePolicy } */
642+ const policyWithTypo = {
643+ entry : {
644+ packages : WILDCARD_POLICY_VALUE ,
645+ } ,
646+ resources : {
647+ 'dep-aa' : {
648+ // Not close enough to 'dep-a' to suggest, but contains 'dep-c'
649+ // which should suggest 'dep-a>dep-c'
650+ packages : {
651+ 'dep-c' : true ,
652+ } ,
653+ } ,
654+ } ,
655+ } ;
656+
657+ const readPowers = makeProjectFixtureReadPowers ( testFixture ) ;
658+ await mapNodeModules ( readPowers , testFixture . entrypoint , {
659+ hooks,
660+ policy : policyWithTypo ,
661+ } ) ;
662+
663+ t . is (
664+ hookCalls . length ,
665+ 2 ,
666+ 'should call hook twice for both unknown resources' ,
667+ ) ;
668+
669+ // Check the call for the unknown top-level resource (no close suggestion)
670+ const unknownResourceCall = hookCalls . find (
671+ call => call . canonicalName === 'dep-aa' ,
672+ ) ;
673+ t . truthy ( unknownResourceCall , 'should call hook for unknown resource' ) ;
674+ t . deepEqual (
675+ unknownResourceCall ?. path ,
676+ [ 'resources' , 'dep-aa' ] ,
677+ 'should provide correct path for unknown resource' ,
678+ ) ;
679+ t . true (
680+ unknownResourceCall ?. message . includes ( 'Resource "dep-aa" was not found' ) ,
681+ 'should provide descriptive message for unknown resource' ,
682+ ) ;
683+ t . is (
684+ unknownResourceCall ?. suggestion ,
685+ undefined ,
686+ 'should not suggest when no close match exists' ,
687+ ) ;
688+
689+ // Check the call for the nested package (should have suggestion)
690+ const nestedPackageCall = hookCalls . find (
691+ call => call . canonicalName === 'dep-c' ,
692+ ) ;
693+ t . truthy ( nestedPackageCall , 'should call hook for nested unknown package' ) ;
694+ t . deepEqual (
695+ nestedPackageCall ?. path ,
696+ [ 'resources' , 'dep-aa' , 'packages' , 'dep-c' ] ,
697+ 'should provide correct path for nested unknown package' ,
698+ ) ;
699+ t . true (
700+ nestedPackageCall ?. message . includes (
701+ 'Resource "dep-c" from resource "dep-aa" was not found' ,
702+ ) ,
703+ 'should provide descriptive message for nested unknown package' ,
704+ ) ;
705+ t . is (
706+ nestedPackageCall ?. suggestion ,
707+ 'dep-a>dep-c' ,
708+ 'should suggest the closest matching canonical name' ,
709+ ) ;
710+ } ) ;
711+
712+ test ( 'mapNodeModules - canonicalNames hook called with all canonical names' , async t => {
713+ t . plan ( 1 ) ;
714+
715+ /** @type {Set<CanonicalName> } */
716+ let receivedCanonicalNames = new Set ( ) ;
717+
718+ /** @type {HookConfiguration<MapNodeModulesHooks> } */
719+ const hooks = {
720+ canonicalNames : ( { canonicalNames } ) => {
721+ receivedCanonicalNames = new Set ( canonicalNames ) ;
722+ } ,
723+ } ;
724+
725+ const readPowers = makeProjectFixtureReadPowers ( testFixture ) ;
726+ await mapNodeModules ( readPowers , testFixture . entrypoint , { hooks } ) ;
727+
728+ // Expected canonical names based on the test fixture:
729+ // - $root$ (the entry package 'app' becomes '$root$')
730+ // - dep-a (direct dependency)
731+ // - dep-b (direct dependency)
732+ // - dep-a>dep-c (transitive dependency through dep-a)
733+ const expectedCanonicalNames = new Set ( [
734+ '$root$' ,
735+ 'dep-a' ,
736+ 'dep-b' ,
737+ 'dep-a>dep-c' ,
738+ ] ) ;
739+
740+ t . deepEqual (
741+ receivedCanonicalNames ,
742+ expectedCanonicalNames ,
743+ 'should receive exactly the expected canonical names from the project fixture' ,
744+ ) ;
745+ } ) ;
0 commit comments