@@ -3,9 +3,12 @@ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
33import { discoverPlugins } from '../wizard/discover' ;
44
55vi . mock ( 'node:fs/promises' , ( ) => ( { readFile : vi . fn ( ) } ) ) ;
6+ vi . mock ( 'local-pkg' , ( ) => ( { getPackageInfo : vi . fn ( ) } ) ) ;
67
78import { readFile } from 'node:fs/promises' ;
9+ import { getPackageInfo } from 'local-pkg' ;
810const mockReadFile = readFile as ReturnType < typeof vi . fn > ;
11+ const mockGetPackageInfo = getPackageInfo as ReturnType < typeof vi . fn > ;
912
1013const ROOT = '/project' ;
1114
@@ -16,8 +19,19 @@ function makeRootPkg(
1619 return JSON . stringify ( { dependencies : deps , devDependencies : devDeps } ) ;
1720}
1821
19- function makeDepPkg ( wizardEntry ?: string ) : string {
20- return JSON . stringify ( wizardEntry ? { stencil : { wizard : wizardEntry } } : { } ) ;
22+ /** Registers a resolvable package at `${ROOT}/node_modules/${packageName}`, optionally declaring a wizard entry. */
23+ function mockResolvedPackage ( packageName : string , wizardEntry ?: string ) {
24+ const rootPath = `${ ROOT } /node_modules/${ packageName } ` ;
25+ mockGetPackageInfo . mockImplementation ( ( name : string ) => {
26+ if ( name !== packageName ) return Promise . resolve ( undefined ) ;
27+ return Promise . resolve ( {
28+ name,
29+ version : '1.0.0' ,
30+ rootPath,
31+ packageJsonPath : `${ rootPath } /package.json` ,
32+ packageJson : wizardEntry ? { stencil : { wizard : wizardEntry } } : { } ,
33+ } ) ;
34+ } ) ;
2135}
2236
2337function makeLoader ( modules : Record < string , Record < string , unknown > > = { } ) {
@@ -34,6 +48,7 @@ describe('discoverPlugins', () => {
3448
3549 beforeEach ( ( ) => {
3650 warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
51+ mockGetPackageInfo . mockReset ( ) . mockResolvedValue ( undefined ) ;
3752 } ) ;
3853
3954 afterEach ( ( ) => {
@@ -47,36 +62,54 @@ describe('discoverPlugins', () => {
4762 } ) ;
4863
4964 it ( 'returns empty array when no deps declare stencil.wizard' , async ( ) => {
50- mockReadFile
51- . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) )
52- . mockResolvedValueOnce ( makeDepPkg ( /* no wizard */ ) ) ;
65+ mockReadFile . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) ) ;
66+ mockResolvedPackage ( '@stencil/vitest' /* no wizard */ ) ;
67+
68+ const result = await discoverPlugins ( ROOT , makeLoader ( ) ) ;
69+ expect ( result ) . toEqual ( [ ] ) ;
70+ } ) ;
71+
72+ it ( 'returns empty array when a dep cannot be resolved at all' , async ( ) => {
73+ mockReadFile . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) ) ;
74+ // mockGetPackageInfo already defaults to resolving undefined
5375
5476 const result = await discoverPlugins ( ROOT , makeLoader ( ) ) ;
5577 expect ( result ) . toEqual ( [ ] ) ;
5678 } ) ;
5779
58- it ( 'discovers a plugin from dependencies' , async ( ) => {
80+ it ( 'discovers a plugin from dependencies, resolved via node module resolution ' , async ( ) => {
5981 const plugin = { generate : { fileTemplates : [ ] } } ;
60- mockReadFile
61- . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) )
62- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) ;
82+ mockReadFile . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) ) ;
83+ mockResolvedPackage ( '@stencil/vitest' , './dist/wizard.js' ) ;
6384
6485 const wizardPath = `${ ROOT } /node_modules/@stencil/vitest/dist/wizard.js` ;
6586 const loader = makeLoader ( { [ wizardPath ] : { wizard : plugin } } ) ;
6687
6788 const result = await discoverPlugins ( ROOT , loader ) ;
6889 expect ( result ) . toEqual ( [ { packageName : '@stencil/vitest' , plugin } ] ) ;
90+ // rootDir is passed through as a resolution path, so hoisted deps (workspace root
91+ // node_modules) resolve correctly, not just a literal rootDir/node_modules join
92+ expect ( mockGetPackageInfo ) . toHaveBeenCalledWith ( '@stencil/vitest' , { paths : [ ROOT ] } ) ;
6993 } ) ;
7094
7195 it ( 'discovers plugins from both dependencies and devDependencies' , async ( ) => {
7296 const pluginA = { generate : { fileTemplates : [ ] } } ;
7397 const pluginB = { init : { id : 'sass' , displayName : 'Sass' , description : '' } } ;
74- mockReadFile
75- . mockResolvedValueOnce (
76- makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } , { '@stencil/sass' : '^3.0.0' } ) ,
77- )
78- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) // @stencil /vitest
79- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) ; // @stencil /sass
98+ mockReadFile . mockResolvedValueOnce (
99+ makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } , { '@stencil/sass' : '^3.0.0' } ) ,
100+ ) ;
101+ mockGetPackageInfo . mockImplementation ( ( name : string ) => {
102+ const wizardEntry = './dist/wizard.js' ;
103+ if ( name !== '@stencil/vitest' && name !== '@stencil/sass' ) return Promise . resolve ( undefined ) ;
104+ const rootPath = `${ ROOT } /node_modules/${ name } ` ;
105+ return Promise . resolve ( {
106+ name,
107+ version : '1.0.0' ,
108+ rootPath,
109+ packageJsonPath : `${ rootPath } /package.json` ,
110+ packageJson : { stencil : { wizard : wizardEntry } } ,
111+ } ) ;
112+ } ) ;
80113
81114 const loader = makeLoader ( {
82115 [ `${ ROOT } /node_modules/@stencil/vitest/dist/wizard.js` ] : { wizard : pluginA } ,
@@ -90,9 +123,8 @@ describe('discoverPlugins', () => {
90123 } ) ;
91124
92125 it ( 'skips a plugin whose module fails to load and warns' , async ( ) => {
93- mockReadFile
94- . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) )
95- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) ;
126+ mockReadFile . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) ) ;
127+ mockResolvedPackage ( '@stencil/vitest' , './dist/wizard.js' ) ;
96128
97129 const loader = makeLoader ( /* no matching module */ ) ;
98130
@@ -103,9 +135,8 @@ describe('discoverPlugins', () => {
103135 } ) ;
104136
105137 it ( 'skips a plugin that does not export wizard and warns' , async ( ) => {
106- mockReadFile
107- . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) )
108- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) ;
138+ mockReadFile . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/vitest' : '^1.0.0' } ) ) ;
139+ mockResolvedPackage ( '@stencil/vitest' , './dist/wizard.js' ) ;
109140
110141 const wizardPath = `${ ROOT } /node_modules/@stencil/vitest/dist/wizard.js` ;
111142 const loader = makeLoader ( { [ wizardPath ] : { notWizard : { } } } ) ;
@@ -119,12 +150,21 @@ describe('discoverPlugins', () => {
119150
120151 it ( 'does not fail when one plugin errors and others succeed' , async ( ) => {
121152 const plugin = { generate : { fileTemplates : [ ] } } ;
122- mockReadFile
123- . mockResolvedValueOnce (
124- makeRootPkg ( { '@stencil/vitest' : '^1.0.0' , '@stencil/broken' : '^1.0.0' } ) ,
125- )
126- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) // @stencil /vitest
127- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) ; // @stencil /broken
153+ mockReadFile . mockResolvedValueOnce (
154+ makeRootPkg ( { '@stencil/vitest' : '^1.0.0' , '@stencil/broken' : '^1.0.0' } ) ,
155+ ) ;
156+ mockGetPackageInfo . mockImplementation ( ( name : string ) => {
157+ if ( name !== '@stencil/vitest' && name !== '@stencil/broken' )
158+ return Promise . resolve ( undefined ) ;
159+ const rootPath = `${ ROOT } /node_modules/${ name } ` ;
160+ return Promise . resolve ( {
161+ name,
162+ version : '1.0.0' ,
163+ rootPath,
164+ packageJsonPath : `${ rootPath } /package.json` ,
165+ packageJson : { stencil : { wizard : './dist/wizard.js' } } ,
166+ } ) ;
167+ } ) ;
128168
129169 const loader = makeLoader ( {
130170 [ `${ ROOT } /node_modules/@stencil/vitest/dist/wizard.js` ] : { wizard : plugin } ,
@@ -166,9 +206,9 @@ describe('discoverPlugins', () => {
166206 const installedPlugin = { generate : { fileTemplates : [ ] } } ;
167207 mockReadFile
168208 . mockResolvedValueOnce ( makeRootPkg ( { '@stencil/sass' : '^3.0.0' } ) )
169- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) // @stencil /sass
170209 . mockRejectedValueOnce ( new Error ( 'ENOENT' ) ) // dist/package.json not found
171210 . mockResolvedValueOnce ( JSON . stringify ( { name : 'my-plugin' } ) ) ; // parent package.json
211+ mockResolvedPackage ( '@stencil/sass' , './dist/wizard.js' ) ;
172212
173213 const loader = makeLoader ( {
174214 [ `${ ROOT } /node_modules/@stencil/sass/dist/wizard.js` ] : { wizard : installedPlugin } ,
@@ -186,9 +226,9 @@ describe('discoverPlugins', () => {
186226 const installedPlugin = { generate : { fileTemplates : [ ] } } ;
187227 mockReadFile
188228 . mockResolvedValueOnce ( makeRootPkg ( { 'my-plugin' : '^1.0.0' } ) )
189- . mockResolvedValueOnce ( makeDepPkg ( './dist/wizard.js' ) ) // installed my-plugin
190229 . mockRejectedValueOnce ( new Error ( 'ENOENT' ) ) // dev: dist/package.json not found
191230 . mockResolvedValueOnce ( JSON . stringify ( { name : 'my-plugin' } ) ) ; // dev: parent package.json
231+ mockResolvedPackage ( 'my-plugin' , './dist/wizard.js' ) ;
192232
193233 const loader = makeLoader ( {
194234 [ `${ ROOT } /node_modules/my-plugin/dist/wizard.js` ] : { wizard : installedPlugin } ,
0 commit comments