@@ -2355,9 +2355,42 @@ t.test('detect conflicts in transitive peerOptional deps', async t => {
23552355 createRegistry ( t , true )
23562356 const tree = await buildIdeal ( path )
23572357 t . matchSnapshot ( printTree ( tree ) )
2358- const name = '@isaacs/test-conflicted-optional-peer-dep-peer'
2359- const peers = tree . inventory . query ( 'name' , name )
2358+ const peerName = '@isaacs/test-conflicted-optional-peer-dep-peer'
2359+ const requiredHostName = '@isaacs/test-conflicted-optional-peer-dep-has-peer'
2360+ const optionalHostName = '@isaacs/test-conflicted-optional-peer-dep-has-peer-optional'
2361+ const optionalMetaName = '@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional'
2362+
2363+ const peers = tree . inventory . query ( 'name' , peerName )
23602364 t . equal ( peers . size , 2 , 'installed the peer dep twice to avoid conflict' )
2365+
2366+ const rootPeer = tree . children . get ( peerName )
2367+ const requiredHost = tree . children . get ( requiredHostName )
2368+ const optionalMeta = tree . children . get ( optionalMetaName )
2369+ const optionalHost = optionalMeta ?. children . get ( optionalHostName )
2370+ const nestedPeer = optionalMeta ?. children . get ( peerName )
2371+
2372+ t . equal ( rootPeer ?. version , '1.0.0' , 'required peer retains the root slot' )
2373+ t . equal (
2374+ requiredHost ?. edgesOut . get ( peerName ) ?. to ,
2375+ rootPeer ,
2376+ 'required peer edge resolves to the root provider'
2377+ )
2378+
2379+ t . notOk (
2380+ tree . children . get ( optionalHostName ) ,
2381+ 'optional peer dependent is not hoisted to the root'
2382+ )
2383+ t . ok ( optionalHost , 'optional peer dependent is nested under its branch' )
2384+ t . equal (
2385+ nestedPeer ?. version ,
2386+ '2.0.0' ,
2387+ 'compatible optional peer is nested with its dependent'
2388+ )
2389+ t . equal (
2390+ optionalHost ?. edgesOut . get ( peerName ) ?. to ,
2391+ nestedPeer ,
2392+ 'optional peer edge resolves to its nested provider'
2393+ )
23612394 } )
23622395
23632396 await t . test ( 'omit peerOptionals when not needed for conflicts' , async t => {
@@ -4885,6 +4918,119 @@ t.test('circular peer back-off does not crash when node is detached mid-resoluti
48854918 'backs off to plugin@1.0.0 to satisfy the optional peer instead of crashing' )
48864919} )
48874920
4921+ t . test ( 'does not fetch packuments for peerOptional deps that will not be installed' , async t => {
4922+ const registry = createRegistry ( t , false )
4923+
4924+ const hostPack = registry . packument ( {
4925+ name : 'host' ,
4926+ version : '1.0.0' ,
4927+ peerDependencies : { plugin : '^1.0.0' } ,
4928+ peerDependenciesMeta : { plugin : { optional : true } } ,
4929+ } )
4930+ const hostManifest = registry . manifest ( { name : 'host' , packuments : [ hostPack ] } )
4931+ await registry . package ( { manifest : hostManifest } )
4932+
4933+ const path = t . testdir ( {
4934+ 'package.json' : JSON . stringify ( {
4935+ dependencies : { host : '^1.0.0' } ,
4936+ } ) ,
4937+ } )
4938+
4939+ const arb = newArb ( path )
4940+ const tree = await arb . buildIdealTree ( )
4941+
4942+ t . equal ( tree . children . get ( 'host' ) . version , '1.0.0' , 'installed host' )
4943+ t . equal ( tree . children . get ( 'plugin' ) , undefined , 'did not install the optional peer' )
4944+ const edge = tree . children . get ( 'host' ) . edgesOut . get ( 'plugin' )
4945+ t . equal ( edge . type , 'peerOptional' )
4946+ t . equal ( edge . to , null , 'peerOptional edge left unresolved' )
4947+ t . ok ( edge . valid , 'missing peerOptional edge is valid' )
4948+ } )
4949+
4950+ t . test ( 'resolves peerOptional deps installed by another dependent' , async t => {
4951+ const registry = createRegistry ( t , false )
4952+
4953+ const hostPack = registry . packument ( {
4954+ name : 'host' ,
4955+ version : '1.0.0' ,
4956+ peerDependencies : { plugin : '^1.0.0' } ,
4957+ peerDependenciesMeta : { plugin : { optional : true } } ,
4958+ } )
4959+ const hostManifest = registry . manifest ( { name : 'host' , packuments : [ hostPack ] } )
4960+ await registry . package ( { manifest : hostManifest } )
4961+
4962+ const otherPack = registry . packument ( {
4963+ name : 'other' ,
4964+ version : '1.0.0' ,
4965+ dependencies : { plugin : '^1.0.0' } ,
4966+ } )
4967+ const otherManifest = registry . manifest ( { name : 'other' , packuments : [ otherPack ] } )
4968+ await registry . package ( { manifest : otherManifest } )
4969+
4970+ const pluginManifest = registry . manifest ( { name : 'plugin' } )
4971+ await registry . package ( { manifest : pluginManifest } )
4972+
4973+ const path = t . testdir ( {
4974+ 'package.json' : JSON . stringify ( {
4975+ dependencies : { host : '^1.0.0' , other : '^1.0.0' } ,
4976+ } ) ,
4977+ } )
4978+
4979+ const arb = newArb ( path )
4980+ const tree = await arb . buildIdealTree ( )
4981+
4982+ const plugin = tree . children . get ( 'plugin' )
4983+ t . ok ( plugin , 'installed the plugin for the dependent that requires it' )
4984+ const edge = tree . children . get ( 'host' ) . edgesOut . get ( 'plugin' )
4985+ t . equal ( edge . type , 'peerOptional' )
4986+ t . equal ( edge . to , plugin , 'peerOptional edge resolved to the installed plugin' )
4987+ t . ok ( edge . valid , 'peerOptional edge is valid' )
4988+ } )
4989+
4990+ t . test ( 'does not fetch packuments for peerOptional deps satisfied by the actual tree' , async t => {
4991+ const registry = createRegistry ( t , false )
4992+
4993+ const hostPack = registry . packument ( {
4994+ name : 'host' ,
4995+ version : '1.0.0' ,
4996+ peerDependencies : { plugin : '^1.0.0' } ,
4997+ peerDependenciesMeta : { plugin : { optional : true } } ,
4998+ } )
4999+ const hostManifest = registry . manifest ( { name : 'host' , packuments : [ hostPack ] } )
5000+ await registry . package ( { manifest : hostManifest } )
5001+
5002+ const path = t . testdir ( {
5003+ node_modules : {
5004+ other : {
5005+ 'package.json' : JSON . stringify ( {
5006+ name : 'other' ,
5007+ version : '1.0.0' ,
5008+ dependencies : { plugin : '^1.0.0' } ,
5009+ } ) ,
5010+ } ,
5011+ plugin : {
5012+ 'package.json' : JSON . stringify ( {
5013+ name : 'plugin' ,
5014+ version : '1.0.0' ,
5015+ } ) ,
5016+ } ,
5017+ } ,
5018+ 'package.json' : JSON . stringify ( {
5019+ dependencies : { other : '^1.0.0' } ,
5020+ } ) ,
5021+ } )
5022+
5023+ const arb = newArb ( path )
5024+ const tree = await arb . buildIdealTree ( { add : [ 'host@^1.0.0' ] } )
5025+
5026+ const plugin = tree . children . get ( 'plugin' )
5027+ t . equal ( plugin . version , '1.0.0' , 'kept the already installed plugin' )
5028+ const edge = tree . children . get ( 'host' ) . edgesOut . get ( 'plugin' )
5029+ t . equal ( edge . type , 'peerOptional' )
5030+ t . equal ( edge . to , plugin , 'peerOptional edge resolved to the existing plugin' )
5031+ t . ok ( edge . valid , 'peerOptional edge is valid' )
5032+ } )
5033+
48885034t . test ( 'peerOptional prefers existing tree node over registry fetch (#9249)' , async t => {
48895035 // Reproduction: ts-jest has peerOptional jest-util@"^29||^30".
48905036 // @types /jest@28 → expect@28 → jest-util@28 placed at root first.
@@ -4930,7 +5076,7 @@ t.test('peerOptional prefers existing tree node over registry fetch (#9249)', as
49305076 // Only publish 28, 29, and 30.
49315077 const jestUtilPacks = registry . packuments ( [ '28.0.0' , '29.0.0' , '30.0.0' ] , 'jest-util' )
49325078 const jestUtilManifest = registry . manifest ( { name : 'jest-util' , packuments : jestUtilPacks } )
4933- await registry . package ( { manifest : jestUtilManifest , times : 3 } )
5079+ await registry . package ( { manifest : jestUtilManifest , times : 2 } )
49345080
49355081 const path = t . testdir ( {
49365082 'package.json' : JSON . stringify ( {
0 commit comments