11import { describe , expect , it , afterEach , beforeAll , afterAll , jest } from '@jest/globals' ;
2+ import {
3+ addGitObserver ,
4+ clearGitObservers ,
5+ getPackageInfo as getWsPackageInfo ,
6+ type Catalogs ,
7+ catalogsToYaml ,
8+ getCatalogs ,
9+ } from 'workspace-tools' ;
210import { defaultBranchName , defaultRemoteBranchName } from '../__fixtures__/gitDefaults' ;
311import { RepositoryFactory } from '../__fixtures__/repositoryFactory' ;
412import { getPackageInfos } from '../monorepo/getPackageInfos' ;
@@ -9,7 +17,6 @@ import { generateChangeFiles } from '../__fixtures__/changeFiles';
917import type { Repository } from '../__fixtures__/repository' ;
1018import { getParsedOptions } from '../options/getOptions' ;
1119import { getScopedPackages } from '../monorepo/getScopedPackages' ;
12- import { addGitObserver , clearGitObservers , getPackageInfo as getWsPackageInfo } from 'workspace-tools' ;
1320
1421//
1522// NOTE: These tests should mostly cover logic specific to getChangePackages itself.
@@ -19,12 +26,14 @@ describe('getChangedPackages', () => {
1926 const gitObserver = jest . fn ( ) ;
2027 const logs = initMockLogs ( ) ;
2128
22- /** Factory reused for most tests */
29+ let singleFactory : RepositoryFactory ;
2330 let monorepoFactory : RepositoryFactory ;
2431 const extraFactories : RepositoryFactory [ ] = [ ] ;
25- /** Monorepo reused for multiple tests where it's safe */
26- let _reusedRepo : Repository ;
27- /** Repo for current test */
32+ /** Repos reused for some tests where it's safe (don't use directly) */
33+ let _reusedRepos : {
34+ single : Repository ;
35+ monorepo : Repository ;
36+ } ; /** Repo for current test */
2837 let repo : Repository | undefined ;
2938
3039 /**
@@ -33,8 +42,8 @@ describe('getChangedPackages', () => {
3342 * - create uncommitted files or directories (commited files are fine because they'll go away
3443 * on branch change)
3544 */
36- function getReusedRepoWithBranch ( ) {
37- repo = _reusedRepo ;
45+ function getReusedRepoWithBranch ( type : 'single' | 'monorepo' ) {
46+ repo = type === 'single' ? _reusedRepos . single : _reusedRepos . monorepo ;
3847 repo . checkoutTestBranch ( ) ;
3948 return repo ;
4049 }
@@ -54,7 +63,9 @@ describe('getChangedPackages', () => {
5463 }
5564
5665 /** Get options/context, clear `gitObserver` mock, and call `getChangedPackages` */
57- function getChangedPackagesWrapper ( params : { repoOptions ?: Partial < RepoOptions > ; extraArgv ?: string [ ] } = { } ) {
66+ function getChangedPackagesWrapper (
67+ params : { repoOptions ?: Partial < RepoOptions > ; extraArgv ?: string [ ] ; cwd ?: string } = { }
68+ ) {
5869 const parsedOptions = getOptions ( params ) ;
5970 const packageInfos = getPackageInfos ( parsedOptions ) ;
6071 const scopedPackages = getScopedPackages ( parsedOptions . options , packageInfos ) ;
@@ -63,8 +74,12 @@ describe('getChangedPackages', () => {
6374 }
6475
6576 beforeAll ( ( ) => {
77+ singleFactory = new RepositoryFactory ( 'single' ) ;
6678 monorepoFactory = new RepositoryFactory ( 'monorepo' ) ;
67- _reusedRepo = monorepoFactory . cloneRepository ( ) ;
79+ _reusedRepos = {
80+ single : singleFactory . cloneRepository ( ) ,
81+ monorepo : monorepoFactory . cloneRepository ( ) ,
82+ } ;
6883 addGitObserver ( gitObserver ) ;
6984 } ) ;
7085
@@ -81,7 +96,7 @@ describe('getChangedPackages', () => {
8196 } ) ;
8297
8398 it ( 'returns the given package name(s) as-is' , ( ) => {
84- repo = getReusedRepoWithBranch ( ) ;
99+ repo = getReusedRepoWithBranch ( 'single' ) ;
85100 expect ( getChangedPackagesWrapper ( { extraArgv : [ '--package' , 'foo' ] } ) ) . toEqual ( [ 'foo' ] ) ;
86101 expect ( gitObserver ) . not . toHaveBeenCalled ( ) ;
87102
@@ -93,24 +108,108 @@ describe('getChangedPackages', () => {
93108 expect ( gitObserver ) . not . toHaveBeenCalled ( ) ;
94109 } ) ;
95110
96- // Basic test as a sanity check, but simple file-based cases are mainly covered by getAllChangedPackages
111+ // do a full test for each major repo structure
112+ it ( 'detects changed files in single-package repo' , ( ) => {
113+ repo = getReusedRepoWithBranch ( 'single' ) ;
114+ repo . commitChange ( 'myFilename' ) ;
115+ expect ( getChangedPackagesWrapper ( ) ) . toEqual ( [ 'foo' ] ) ;
116+ expect ( gitObserver ) . toHaveBeenCalled ( ) ;
117+ } ) ;
118+
97119 it ( 'detects changed files in monorepo' , ( ) => {
98- repo = getReusedRepoWithBranch ( ) ;
120+ repo = getReusedRepoWithBranch ( 'monorepo' ) ;
99121
100122 // empty if no changes yet
101123 expect ( getChangedPackagesWrapper ( ) ) . toEqual ( [ ] ) ;
102124
103- repo . commitChange ( 'packages/foo/test.js' ) ;
104- expect ( getChangedPackagesWrapper ( ) ) . toEqual ( [ 'foo' ] ) ;
125+ repo . writeFile ( 'packages/foo/test.js' ) ;
126+ repo . writeFile ( 'packages/bar/test.js' ) ;
127+ repo . commitAll ( ) ;
105128
106- expect ( logs . getMockLines ( 'all' , { sanitize : true } ) ) . toMatchInlineSnapshot ( `
129+ logs . clear ( ) ;
130+ const result = getChangedPackagesWrapper ( { extraArgv : [ '--verbose' ] } ) ;
131+ expect ( result . sort ( ) ) . toEqual ( [ 'bar' , 'foo' ] ) ;
132+ expect ( gitObserver ) . toHaveBeenCalled ( ) ;
133+
134+ expect ( logs . getMockLines ( 'all' ) ) . toMatchInlineSnapshot ( `
135+ "[log] Checking for changes against "origin/master"
136+ [log] Found 2 changed files in current branch (before filtering)
137+ [log] - packages/bar/test.js
138+ [log] - packages/foo/test.js
139+ [log] Found 2 files in 2 packages that should be published"
140+ ` ) ;
141+ } ) ;
142+
143+ it ( 'detects changed files in multi-project monorepo' , ( ) => {
144+ // this is the only multi-project test
145+ const multiFactory = new RepositoryFactory ( 'multi-project' ) ;
146+ extraFactories . push ( multiFactory ) ;
147+ repo = multiFactory . cloneRepository ( ) ;
148+ expect ( Object . keys ( multiFactory . fixtures ) ) . toEqual ( [ 'project-a' , 'project-b' ] ) ;
149+
150+ const projectARoot = repo . pathTo ( 'project-a' ) ;
151+ const projectBRoot = repo . pathTo ( 'project-b' ) ;
152+
153+ repo . stageChange ( 'project-a/packages/foo/test.js' ) ;
154+
155+ const changedPackagesA = getChangedPackagesWrapper ( { cwd : projectARoot } ) ;
156+ const changedPackagesB = getChangedPackagesWrapper ( { cwd : projectBRoot } ) ;
157+
158+ expect ( changedPackagesA ) . toEqual ( [ '@project-a/foo' ] ) ;
159+ expect ( changedPackagesB ) . toEqual ( [ ] ) ;
160+ } ) ;
161+
162+ // Do one real combined test with ignores to ensure all the path handling works
163+ // (the logic is mostly covered by getAllChangedPackages tests)
164+ it ( 'ignores CHANGELOG, change files, and ignorePatterns in single-package repo' , ( ) => {
165+ repo = getReusedRepoWithBranch ( 'single' ) ;
166+
167+ repo . writeFile ( 'change/change-abc123.json' , { } ) ;
168+ repo . writeFile ( 'CHANGELOG.md' ) ;
169+ repo . writeFile ( 'src/foo.test.js' ) ;
170+ repo . writeFile ( 'src/foo.test.js' ) ;
171+ repo . writeFile ( 'tests/stuff.js' ) ;
172+ repo . writeFile ( 'yarn.lock' ) ;
173+ repo . commitAll ( ) ;
174+
175+ const result = getChangedPackagesWrapper ( {
176+ repoOptions : { ignorePatterns : [ '*.test.js' , 'tests/**' , 'yarn.lock' ] } ,
177+ extraArgv : [ '--verbose' ] ,
178+ } ) ;
179+ expect ( result ) . toEqual ( [ ] ) ;
180+
181+ const logLines = logs . getMockLines ( 'all' ) ;
182+ expect ( logLines ) . toMatch ( 'ignored by pattern' ) ;
183+ expect ( logLines ) . toMatchInlineSnapshot ( `
107184 "[log] Checking for changes against "origin/master"
108- [log] Checking for changes against "origin/master""
185+ [log] Found 4 changed files in current branch (before filtering)
186+ [log] - ~~CHANGELOG.md~~ (ignored by pattern "CHANGELOG.{md,json}")
187+ [log] - ~~change/change-abc123.json~~ (ignored by pattern "change/*.json")
188+ [log] - ~~src/foo.test.js~~ (ignored by pattern "*.test.js")
189+ [log] - ~~tests/stuff.js~~ (ignored by pattern "tests/**")
190+ [log] All files were ignored"
109191 ` ) ;
110192 } ) ;
111193
194+ it ( 'includes staged files' , ( ) => {
195+ // need a separate repo since it stages changes
196+ repo = singleFactory . cloneRepository ( ) ;
197+ repo . checkout ( '-b' , 'test-staged' ) ;
198+ // write this file and modify it
199+ repo . commitChange ( 'src/foo.js' ) ;
200+ repo . writeFile ( 'src/bar.js' ) ;
201+ // add a new file that's only staged
202+ repo . writeFile ( 'src/foo.js' ) ;
203+ repo . git ( [ 'add' , '-A' ] ) ;
204+
205+ const result = getChangedPackagesWrapper ( { extraArgv : [ '--verbose' ] } ) ;
206+ expect ( result ) . toEqual ( [ 'foo' ] ) ;
207+ // src/foo.js is both committed and staged, but should be listed only once
208+ expect ( logs . getMockLines ( 'all' ) ) . toContain ( 'Found 2 changed files in current branch (before filtering)' ) ;
209+ } ) ;
210+
112211 it ( 'excludes packages that already have change files' , ( ) => {
113- repo = getReusedRepoWithBranch ( ) ;
212+ repo = getReusedRepoWithBranch ( 'monorepo' ) ;
114213
115214 // setup: change foo, create a change file, commit
116215 repo . checkout ( '-b' , 'test' ) ;
@@ -152,7 +251,7 @@ describe('getChangedPackages', () => {
152251 } ) ;
153252
154253 it ( 'returns all packages with all: true, removing those with change files' , ( ) => {
155- repo = getReusedRepoWithBranch ( ) ;
254+ repo = getReusedRepoWithBranch ( 'monorepo' ) ;
156255 generateChangeFiles ( [ 'foo' , 'a' ] , { ...getOptions ( ) . options , commit : true } ) ;
157256
158257 const result = getChangedPackagesWrapper ( { extraArgv : [ '--all' ] } ) ;
@@ -161,7 +260,7 @@ describe('getChangedPackages', () => {
161260 } ) ;
162261
163262 it ( 'throws if the remote is invalid' , ( ) => {
164- repo = getReusedRepoWithBranch ( ) ;
263+ repo = getReusedRepoWithBranch ( 'monorepo' ) ;
165264 const customRemote = 'foo' ;
166265 repo . git ( [ 'remote' , 'add' , customRemote , 'file:///__nonexistent' ] ) ;
167266 repo . commitChange ( 'fake.js' ) ;
@@ -216,4 +315,43 @@ describe('getChangedPackages', () => {
216315 [log] Found 1 file in 1 package that should be published"
217316 ` ) ;
218317 } ) ;
318+
319+ it ( 'includes catalog changes in the list of changed packages' , ( ) => {
320+ // This needs a separate factory since it pushes changes
321+ const repositoryFactory = new RepositoryFactory ( 'monorepo' ) ;
322+ extraFactories . push ( repositoryFactory ) ;
323+ repo = repositoryFactory . cloneRepository ( ) ;
324+
325+ // Add catalog dependencies
326+ const initialCatalogs : Catalogs = { default : { react : '18.0.0' , other : '1.0.0' } } ;
327+ repo . writeFile ( '.yarnrc.yml' , catalogsToYaml ( initialCatalogs ) ) ;
328+ repo . updateJsonFile ( 'packages/foo/package.json' , { dependencies : { react : 'catalog:' , other : 'catalog:' } } ) ;
329+ repo . updateJsonFile ( 'packages/bar/package.json' , { dependencies : { react : 'catalog:' } } ) ;
330+ repo . commitAll ( 'add catalogs' ) ;
331+ repo . push ( ) ;
332+ // sanity check
333+ expect ( getCatalogs ( repo . rootPath ) ) . toEqual ( initialCatalogs ) ;
334+
335+ // Update a catalog version, and make unrelated changes to verify merging
336+ const newCatalogs : Catalogs = { default : { react : '19.0.0' , other : '1.0.0' } } ;
337+ repo . writeFile ( '.yarnrc.yml' , catalogsToYaml ( newCatalogs ) ) ;
338+ repo . writeFile ( 'packages/foo/test.js' , '' ) ;
339+ repo . writeFile ( 'packages/grouped/a/foo.ts' , '' ) ;
340+ repo . commitAll ( ) ;
341+
342+ const result = getChangedPackagesWrapper ( { extraArgv : [ '--verbose' ] } ) ;
343+ expect ( result . sort ( ) ) . toEqual ( [ 'a' , 'bar' , 'foo' ] ) ;
344+ expect ( logs . getMockLines ( 'all' , { sanitize : true } ) ) . toMatchInlineSnapshot ( `
345+ "[log] Checking for changes against "origin/master"
346+ [log] Found 3 changed files in current branch (before filtering)
347+ [log] - ~~.yarnrc.yml~~ (not in a package)
348+ [log] - packages/foo/test.js
349+ [log] - packages/grouped/a/foo.ts
350+ [log] Found 2 files in 2 packages that should be published
351+ [log] Checking for changes to catalog: dependencies...
352+ [log] catalog: dependencies referenced by the following packages have changed:
353+ [log] - bar: react
354+ [log] - foo: react"
355+ ` ) ;
356+ } ) ;
219357} ) ;
0 commit comments