11import fs from 'node:fs/promises' ;
22import path from 'node:path' ;
3+ import process from 'node:process' ;
34import { createRequire } from 'node:module' ;
45import test from 'ava' ;
56import resolveFrom from 'resolve-from' ;
6- import util from '../util.js' ;
7+ import util , { findProjectRoot } from '../util.js' ;
78
89const require = createRequire ( import . meta. url ) ;
910const packageJson = require ( '../package.json' ) ;
@@ -30,27 +31,211 @@ test.serial('loadAvaHelper retries lookup when helper becomes available', async
3031 } ) ;
3132
3233 const testFilename = path . join ( fixtureRootDirectory , 'example.test.js' ) ;
33- await fs . writeFile ( path . join ( fixtureRootDirectory , 'package.json' ) , '{"name":"fixture"}' ) ;
34+ await fs . writeFile ( path . join ( fixtureRootDirectory , 'package.json' ) , '{"name":"fixture","ava":{} }' ) ;
3435 await fs . writeFile ( testFilename , '' ) ;
3536
3637 const overrides = { files : [ 'test.js' ] } ;
3738 const helperPath = path . join ( fixtureRootDirectory , 'eslint-plugin-helper.js' ) ;
3839 await fs . writeFile ( helperPath , 'module.exports = {load(rootDirectory, options) { return {rootDirectory, options}; }};' ) ;
3940
4041 const originalResolveFromSilent = resolveFrom . silent ;
41- let numberOfResolveCalls = 0 ;
42- resolveFrom . silent = ( ) => {
43- numberOfResolveCalls += 1 ;
44- return numberOfResolveCalls === 1 ? undefined : helperPath ;
45- } ;
42+ let isHelperAvailable = false ;
43+ resolveFrom . silent = ( ) => isHelperAvailable ? helperPath : undefined ;
4644
4745 t . teardown ( ( ) => {
4846 resolveFrom . silent = originalResolveFromSilent ;
4947 } ) ;
5048
5149 t . is ( util . loadAvaHelper ( testFilename , overrides ) , undefined ) ;
50+ isHelperAvailable = true ;
5251
5352 const helper = util . loadAvaHelper ( testFilename , overrides ) ;
54- t . is ( numberOfResolveCalls , 2 ) ;
5553 t . deepEqual ( helper , { rootDirectory : fixtureRootDirectory , options : overrides } ) ;
5654} ) ;
55+
56+ test . serial ( 'loadAvaHelper resolves helper from sub-package when config is at workspace root' , async t => {
57+ const workspaceRootDirectory = await fs . mkdtemp ( path . join ( import . meta. dirname , 'tmp-load-ava-helper-workspace-' ) ) ;
58+ t . teardown ( async ( ) => {
59+ await fs . rm ( workspaceRootDirectory , { recursive : true , force : true } ) ;
60+ } ) ;
61+
62+ await fs . writeFile ( path . join ( workspaceRootDirectory , 'package.json' ) , '{"name":"workspace","ava":{}}' ) ;
63+ const packageDirectory = path . join ( workspaceRootDirectory , 'packages' , 'pkg-a' ) ;
64+ await fs . mkdir ( packageDirectory , { recursive : true } ) ;
65+ await fs . writeFile ( path . join ( packageDirectory , 'package.json' ) , '{"name":"pkg-a"}' ) ;
66+ const testFilename = path . join ( packageDirectory , 'test' , 'example.test.js' ) ;
67+ await fs . mkdir ( path . join ( packageDirectory , 'test' ) , { recursive : true } ) ;
68+ await fs . writeFile ( testFilename , '' ) ;
69+
70+ const helperPath = path . join ( workspaceRootDirectory , 'eslint-plugin-helper.js' ) ;
71+ await fs . writeFile ( helperPath , 'module.exports = {load(rootDirectory, options) { return {rootDirectory, options}; }};' ) ;
72+
73+ const originalResolveFromSilent = resolveFrom . silent ;
74+ resolveFrom . silent = fromDirectory => {
75+ if ( fromDirectory === workspaceRootDirectory ) {
76+ return undefined ;
77+ }
78+
79+ return fromDirectory . startsWith ( packageDirectory ) ? helperPath : undefined ;
80+ } ;
81+
82+ t . teardown ( ( ) => {
83+ resolveFrom . silent = originalResolveFromSilent ;
84+ } ) ;
85+
86+ const overrides = { files : [ 'test.js' ] } ;
87+ const helper = util . loadAvaHelper ( testFilename , overrides ) ;
88+ t . deepEqual ( helper , { rootDirectory : workspaceRootDirectory , options : overrides } ) ;
89+ } ) ;
90+
91+ test . serial ( 'loadAvaHelper resolves hoisted helper outside sub-package root' , async t => {
92+ const workspaceRootDirectory = await fs . mkdtemp ( path . join ( import . meta. dirname , 'tmp-load-ava-helper-hoisted-' ) ) ;
93+ t . teardown ( async ( ) => {
94+ await fs . rm ( workspaceRootDirectory , { recursive : true , force : true } ) ;
95+ } ) ;
96+
97+ // Sub-package has its own AVA config, but AVA is hoisted to the monorepo root.
98+ await fs . writeFile ( path . join ( workspaceRootDirectory , 'package.json' ) , '{"name":"workspace"}' ) ;
99+ const packageDirectory = path . join ( workspaceRootDirectory , 'packages' , 'pkg-a' ) ;
100+ await fs . mkdir ( packageDirectory , { recursive : true } ) ;
101+ await fs . writeFile ( path . join ( packageDirectory , 'package.json' ) , '{"name":"pkg-a","ava":{}}' ) ;
102+ const testFilename = path . join ( packageDirectory , 'test' , 'example.test.js' ) ;
103+ await fs . mkdir ( path . join ( packageDirectory , 'test' ) , { recursive : true } ) ;
104+ await fs . writeFile ( testFilename , '' ) ;
105+
106+ // The helper lives at the workspace root (hoisted node_modules), outside the sub-package.
107+ const helperPath = path . join ( workspaceRootDirectory , 'node_modules' , 'ava' , 'eslint-plugin-helper.js' ) ;
108+ await fs . mkdir ( path . dirname ( helperPath ) , { recursive : true } ) ;
109+ await fs . writeFile ( helperPath , 'module.exports = {load(rootDirectory, options) { return {rootDirectory, options}; }};' ) ;
110+
111+ const originalResolveFromSilent = resolveFrom . silent ;
112+ resolveFrom . silent = ( ) => helperPath ;
113+
114+ t . teardown ( ( ) => {
115+ resolveFrom . silent = originalResolveFromSilent ;
116+ } ) ;
117+
118+ const overrides = { files : [ 'test.js' ] } ;
119+ const helper = util . loadAvaHelper ( testFilename , overrides ) ;
120+ t . deepEqual ( helper , { rootDirectory : packageDirectory , options : overrides } ) ;
121+ } ) ;
122+
123+ const createFixtureDirectory = async t => {
124+ const directory = await fs . mkdtemp ( path . join ( import . meta. dirname , 'tmp-find-root-' ) ) ;
125+ t . teardown ( async ( ) => {
126+ await fs . rm ( directory , { recursive : true , force : true } ) ;
127+ } ) ;
128+
129+ return directory ;
130+ } ;
131+
132+ test ( 'findProjectRoot: finds directory with ava config in package.json' , async t => {
133+ const root = await createFixtureDirectory ( t ) ;
134+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"fixture","ava":{}}' ) ;
135+ const testFile = path . join ( root , 'test' , 'foo.test.js' ) ;
136+ await fs . mkdir ( path . join ( root , 'test' ) , { recursive : true } ) ;
137+ await fs . writeFile ( testFile , '' ) ;
138+
139+ t . is ( findProjectRoot ( testFile ) , root ) ;
140+ } ) ;
141+
142+ test ( 'findProjectRoot: finds directory with ava.config.js' , async t => {
143+ const root = await createFixtureDirectory ( t ) ;
144+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"fixture"}' ) ;
145+ await fs . writeFile ( path . join ( root , 'ava.config.js' ) , '' ) ;
146+ const testFile = path . join ( root , 'test' , 'foo.test.js' ) ;
147+ await fs . mkdir ( path . join ( root , 'test' ) , { recursive : true } ) ;
148+ await fs . writeFile ( testFile , '' ) ;
149+
150+ t . is ( findProjectRoot ( testFile ) , root ) ;
151+ } ) ;
152+
153+ test ( 'findProjectRoot: finds directory with ava.config.cjs' , async t => {
154+ const root = await createFixtureDirectory ( t ) ;
155+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"fixture"}' ) ;
156+ await fs . writeFile ( path . join ( root , 'ava.config.cjs' ) , '' ) ;
157+ const testFile = path . join ( root , 'test' , 'foo.test.js' ) ;
158+ await fs . mkdir ( path . join ( root , 'test' ) , { recursive : true } ) ;
159+ await fs . writeFile ( testFile , '' ) ;
160+
161+ t . is ( findProjectRoot ( testFile ) , root ) ;
162+ } ) ;
163+
164+ test ( 'findProjectRoot: finds directory with ava.config.mjs' , async t => {
165+ const root = await createFixtureDirectory ( t ) ;
166+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"fixture"}' ) ;
167+ await fs . writeFile ( path . join ( root , 'ava.config.mjs' ) , '' ) ;
168+ const testFile = path . join ( root , 'test' , 'foo.test.js' ) ;
169+ await fs . mkdir ( path . join ( root , 'test' ) , { recursive : true } ) ;
170+ await fs . writeFile ( testFile , '' ) ;
171+
172+ t . is ( findProjectRoot ( testFile ) , root ) ;
173+ } ) ;
174+
175+ test ( 'findProjectRoot: monorepo - finds root with ava config over sub-package' , async t => {
176+ const root = await createFixtureDirectory ( t ) ;
177+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"monorepo","ava":{}}' ) ;
178+ const packageDir = path . join ( root , 'packages' , 'pkg-a' ) ;
179+ await fs . mkdir ( packageDir , { recursive : true } ) ;
180+ await fs . writeFile ( path . join ( packageDir , 'package.json' ) , '{"name":"pkg-a"}' ) ;
181+ const testFile = path . join ( packageDir , 'test' , 'foo.test.js' ) ;
182+ await fs . mkdir ( path . join ( packageDir , 'test' ) , { recursive : true } ) ;
183+ await fs . writeFile ( testFile , '' ) ;
184+
185+ t . is ( findProjectRoot ( testFile ) , root ) ;
186+ } ) ;
187+
188+ test ( 'findProjectRoot: config file closer to file wins over package.json ava key higher up' , async t => {
189+ const root = await createFixtureDirectory ( t ) ;
190+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"monorepo","ava":{}}' ) ;
191+ const subDir = path . join ( root , 'packages' , 'pkg-a' ) ;
192+ await fs . mkdir ( subDir , { recursive : true } ) ;
193+ await fs . writeFile ( path . join ( subDir , 'package.json' ) , '{"name":"pkg-a"}' ) ;
194+ await fs . writeFile ( path . join ( subDir , 'ava.config.js' ) , '' ) ;
195+ const testFile = path . join ( subDir , 'test' , 'foo.test.js' ) ;
196+ await fs . mkdir ( path . join ( subDir , 'test' ) , { recursive : true } ) ;
197+ await fs . writeFile ( testFile , '' ) ;
198+
199+ t . is ( findProjectRoot ( testFile ) , subDir ) ;
200+ } ) ;
201+
202+ test ( 'findProjectRoot: skips package.json without ava key' , async t => {
203+ const root = await createFixtureDirectory ( t ) ;
204+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"fixture"}' ) ;
205+ await fs . mkdir ( path . join ( root , '.git' ) ) ;
206+ const testFile = path . join ( root , 'test' , 'foo.test.js' ) ;
207+ await fs . mkdir ( path . join ( root , 'test' ) , { recursive : true } ) ;
208+ await fs . writeFile ( testFile , '' ) ;
209+
210+ // The fixture package.json has no "ava" key, but traversal should stop
211+ // at the repository boundary and return the nearest package root.
212+ const result = findProjectRoot ( testFile ) ;
213+ t . is ( result , root ) ;
214+ } ) ;
215+
216+ test ( 'findProjectRoot: does not escape repository boundary' , async t => {
217+ const outsideDirectory = await createFixtureDirectory ( t ) ;
218+ await fs . writeFile ( path . join ( outsideDirectory , 'package.json' ) , '{"name":"outside","ava":{}}' ) ;
219+ await fs . writeFile ( path . join ( outsideDirectory , 'ava.config.js' ) , '' ) ;
220+
221+ const root = path . join ( outsideDirectory , 'project' ) ;
222+ await fs . mkdir ( root , { recursive : true } ) ;
223+ await fs . mkdir ( path . join ( root , '.git' ) ) ;
224+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"fixture"}' ) ;
225+ const testFile = path . join ( root , 'test' , 'foo.test.js' ) ;
226+ await fs . mkdir ( path . join ( root , 'test' ) , { recursive : true } ) ;
227+ await fs . writeFile ( testFile , '' ) ;
228+
229+ t . is ( findProjectRoot ( testFile ) , root ) ;
230+ } ) ;
231+
232+ test ( 'findProjectRoot: handles relative filename' , async t => {
233+ const root = await createFixtureDirectory ( t ) ;
234+ await fs . writeFile ( path . join ( root , 'package.json' ) , '{"name":"fixture","ava":{}}' ) ;
235+ const testFile = path . join ( root , 'test' , 'foo.test.js' ) ;
236+ await fs . mkdir ( path . join ( root , 'test' ) , { recursive : true } ) ;
237+ await fs . writeFile ( testFile , '' ) ;
238+
239+ const relativeTestFile = path . relative ( process . cwd ( ) , testFile ) ;
240+ t . is ( findProjectRoot ( relativeTestFile ) , root ) ;
241+ } ) ;
0 commit comments