1- import { createNonphysicalIdStore } from '@devup-ui/plugin-utils'
1+ import { symlink } from 'node:fs/promises'
2+
3+ import {
4+ createNonphysicalIdStore ,
5+ resolveSourceDirs ,
6+ } from '@devup-ui/plugin-utils'
27import { expect , it } from 'bun:test'
38import { resolveConfig , type UserConfig } from 'vite'
49
510import {
611 prepareProductionManifest ,
712 type ProductionOptions ,
813} from '../production-manifest'
14+ import { productionRootSources } from '../production-root-sources'
915import { productionFixture } from './production-fixture'
1016
1117it . each ( [
@@ -20,7 +26,7 @@ it.each([
2026 'html' ,
2127 'html-mixed' ,
2228] as const ) (
23- 'reserves input closure while preserving legacy inventory when input=%s' ,
29+ 'reserves roots and input closure while preserving legacy inventory when input=%s' ,
2430 async ( form ) => {
2531 // Given exact inputs outside sourceDirs and an unrelated configured search file.
2632 const fixture = await productionFixture ( )
@@ -129,16 +135,17 @@ it.each([
129135 options ,
130136 ( inputs ) => observed . push ( ...inputs . fileDependencies ) ,
131137 )
132- // Then ordinary HTML retains 919 inventory, while scripts reserve only reached candidates .
138+ // Then ordinary HTML retains 919 inventory, while scripts also reserve configured roots .
133139 const legacy = form === 'html' || form === 'html-mixed'
134140 const expected =
135141 form === 'virtual'
136- ? [ ]
142+ ? [ unused ]
137143 : [
138144 entry ,
139145 view ,
140146 ...( form === 'mdx' ? [ mdx ] : [ ] ) ,
141- ...( legacy ? [ unused , tooling ] : [ ] ) ,
147+ unused ,
148+ ...( legacy ? [ tooling ] : [ ] ) ,
142149 ]
143150 const context = manifest . contexts . find (
144151 ( { context } ) => context === 'client' ,
@@ -149,7 +156,7 @@ it.each([
149156 'virtual:learned.js' ,
150157 ] )
151158 expect ( context ?. scan ) . not . toContain ( 'virtual:learned.js' )
152- if ( ! legacy ) expect ( context ?. scan ) . not . toContain ( unused )
159+ expect ( context ?. scan ) . toContain ( unused )
153160 if ( form !== 'virtual' )
154161 expect ( observed ) . toContain (
155162 entry . replaceAll ( '/' , process . platform === 'win32' ? '\\' : '/' ) ,
@@ -159,3 +166,94 @@ it.each([
159166 }
160167 } ,
161168)
169+
170+ it ( 'censuses explicit roots and includes without test, output or automatic package inventories' , async ( ) => {
171+ // Given default roots, explicit overrides, linked siblings and an included package.
172+ const fixture = await productionFixture ( )
173+ const source = await fixture . file ( 'app/src/view.js' , 'export {};' )
174+ const app = await fixture . file ( 'app/app/page.js' , 'export {};' )
175+ const configured = await fixture . file ( 'app/custom/view.js' , 'export {};' )
176+ const included = await fixture . file (
177+ 'app/node_modules/included/src/view.js' ,
178+ 'export {};' ,
179+ )
180+ await fixture . file (
181+ 'app/node_modules/included/package.json' ,
182+ '{"name":"included"}' ,
183+ )
184+ for ( const name of '__tests__ tests test __fixtures__ fixtures dist build out coverage .next tooling scripts generated' . split (
185+ ' ' ,
186+ ) )
187+ await fixture . file ( `app/src/${ name } /view.js` , 'export {};' )
188+ for ( const name of 'view.test.tsx view.spec.js types.d.ts types.d.mts types.d.cts' . split (
189+ ' ' ,
190+ ) )
191+ await fixture . file ( `app/src/${ name } ` , 'export {};' )
192+ await fixture . file ( 'app/node_modules/included/dist/unused.js' , 'export {};' )
193+ await fixture . file (
194+ 'app/node_modules/included/fixtures/unused.js' ,
195+ 'export {};' ,
196+ )
197+ await fixture . file (
198+ 'app/node_modules/@devup-ui/tooling/package.json' ,
199+ '{"main":"index.js"}' ,
200+ )
201+ await fixture . file (
202+ 'app/node_modules/@devup-ui/tooling/index.js' ,
203+ 'export {};' ,
204+ )
205+ const outside = await fixture . file ( 'outside/view.js' , 'export {};' )
206+ for ( const name of [ 'first' , 'second' ] )
207+ await symlink (
208+ `${ fixture . directory } /outside` ,
209+ `${ fixture . root } /src/${ name } ` ,
210+ 'junction' ,
211+ )
212+ await symlink (
213+ `${ fixture . root } /src` ,
214+ `${ fixture . directory } /outside/cycle` ,
215+ 'junction' ,
216+ )
217+ await symlink (
218+ `${ fixture . root } /src/fixtures` ,
219+ `${ fixture . root } /src/blocked` ,
220+ 'junction' ,
221+ )
222+ try {
223+ // When the same private census uses default roots or explicit sourceDirs.
224+ const options = {
225+ cwd : fixture . root ,
226+ exclude : [ `${ fixture . root } /src/generated` ] ,
227+ includeMdx : [ '.mdx' ] ,
228+ }
229+ const defaults = productionRootSources (
230+ { ...options , roots : resolveSourceDirs ( fixture . root ) } ,
231+ [ 'included' ] ,
232+ )
233+ const override = productionRootSources (
234+ { ...options , roots : resolveSourceDirs ( fixture . root , [ 'custom' ] ) } ,
235+ [ ] ,
236+ )
237+ // Then lexical aliases survive cycles; excluded census sources never join either set.
238+ const paths = defaults . map ( ( { path } ) => path . replaceAll ( '\\' , '/' ) ) . sort ( )
239+ expect ( paths ) . toEqual (
240+ [
241+ source ,
242+ app ,
243+ included ,
244+ `${ fixture . root } /src/first/view.js` ,
245+ `${ fixture . root } /src/second/view.js` ,
246+ ] . sort ( ) ,
247+ )
248+ expect (
249+ defaults . filter (
250+ ( { realPath } ) => realPath . replaceAll ( '\\' , '/' ) === outside ,
251+ ) ,
252+ ) . toHaveLength ( 2 )
253+ expect ( override . map ( ( { path } ) => path . replaceAll ( '\\' , '/' ) ) ) . toEqual ( [
254+ configured ,
255+ ] )
256+ } finally {
257+ await fixture . close ( )
258+ }
259+ } )
0 commit comments