From 0a73fdcdd1d99b938324a3fd007ee72adfd83bc0 Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Wed, 14 Sep 2022 17:57:24 +0100 Subject: [PATCH 1/8] Add --package option; refactor test logic; add logging --- packages/modular-scripts/src/program.ts | 15 +- packages/modular-scripts/src/test/index.ts | 139 +++++++++++++++--- .../modular-scripts/src/utils/gitActions.ts | 15 +- 3 files changed, 141 insertions(+), 28 deletions(-) diff --git a/packages/modular-scripts/src/program.ts b/packages/modular-scripts/src/program.ts index a6db5f5ee..3abc76c6d 100755 --- a/packages/modular-scripts/src/program.ts +++ b/packages/modular-scripts/src/program.ts @@ -142,6 +142,7 @@ program '--compareBranch ', "Specifies the branch to use with the --changed flag. If not specified, Modular will use the repo's default branch", ) + .option('--package ', 'Specifies one or more packages to test') .option('--coverage', testOptions.coverage.description) .option('--forceExit', testOptions.forceExit.description) .option('--env ', testOptions.env.description, 'jsdom') @@ -168,12 +169,16 @@ program .allowUnknownOption() .description('Run tests over the codebase') .action(async (regexes: string[], options: CLITestOptions) => { - if (options.ancestors && !options.changed) { + if (options.ancestors && !options.changed && !options.package) { process.stderr.write( - "Option --ancestors doesn't make sense without option --changed", + "Option --ancestors doesn't make sense without option --changed or option --package", ); process.exit(1); } + if (options.package && options.changed) { + process.stderr.write('Option --package conflicts with option --changed'); + process.exit(1); + } if (options.compareBranch && !options.changed) { process.stderr.write( "Option --compareBranch doesn't make sense without option --changed", @@ -186,6 +191,12 @@ program ); process.exit(1); } + if (options.package && regexes.length) { + process.stderr.write( + 'Option --package conflicts with supplied test regex', + ); + process.exit(1); + } const { default: test } = await import('./test'); diff --git a/packages/modular-scripts/src/test/index.ts b/packages/modular-scripts/src/test/index.ts index fb621843d..2d61c9d09 100644 --- a/packages/modular-scripts/src/test/index.ts +++ b/packages/modular-scripts/src/test/index.ts @@ -9,6 +9,10 @@ import { getAllWorkspaces } from '../utils/getAllWorkspaces'; import { getChangedWorkspaces } from '../utils/getChangedWorkspaces'; import { resolveAsBin } from '../utils/resolveAsBin'; import * as logger from '../utils/logger'; +import type { + WorkspaceContent, + ModularWorkspacePackage, +} from '@modular-scripts/modular-types'; export interface TestOptions { ancestors: boolean; @@ -28,6 +32,7 @@ export interface TestOptions { runInBand: boolean; onlyChanged: boolean; outputFile: string; + package: string[] | undefined; silent: boolean; testResultsProcessor: string | undefined; updateSnapshot: boolean; @@ -68,6 +73,7 @@ async function test( const { ancestors, changed, + package: packages, compareBranch, debug, env, @@ -115,21 +121,17 @@ async function test( const additionalOptions: string[] = []; const cleanRegexes: string[] = []; - let regexes = userRegexes; - - if (changed) { - const testRegexes = await computeChangedTestsRegexes( - compareBranch, - ancestors, - ); - if (!testRegexes.length) { - // No regexes means "run all tests", but we want to avoid that in --changed mode - process.stdout.write('No changed workspaces found'); - process.exit(0); - } else { - regexes = testRegexes; - } - } + // There are two ways of discovering the test regexes we need: either they're specified by the user as CLI arguments + // or they have to be calculated from selective options (--changed and --package) and optionally agumented with --ancestors. + const regexes = + changed || packages?.length + ? await computeSelectiveRegexes({ + changed, + compareBranch, + packages, + ancestors, + }) + : userRegexes; if (regexes?.length) { regexes.forEach((reg) => { @@ -189,22 +191,111 @@ async function test( } } -async function computeChangedTestsRegexes( - targetBranch: string | undefined, - ancestors: boolean, -) { - // Get all the workspaces +// This function takes all the selective options, validates them and returns a subset of workspaces to test +async function computeSelectiveRegexes({ + changed, + compareBranch, + packages, + ancestors, +}: { + changed: boolean; + compareBranch?: string; + packages?: string[]; + ancestors: boolean; +}) { + logger.debug( + packages?.length + ? `Calculating test regexes from specified packages: ${JSON.stringify( + packages, + )}` + : `Calculating test regexes from changed workspaces, compared with ${ + compareBranch ?? 'default' + } branch`, + ); + + if ((!changed && !packages?.length) || (changed && packages?.length)) { + throw new Error( + `Conflicting options: --changed (${changed.toString()}) and --package ("${JSON.stringify( + packages, + )}")`, + ); + } + + // It's terser if we mutate WorkspaceContent; we will use it only to pass it to computeTestsRegexes + let resultWorkspaceContent: WorkspaceContent = packages?.length + ? await getSinglePackagesContent(packages) + : await getChangedWorkspacesContent(compareBranch); + + if (ancestors) { + logger.debug( + `Calculating ancestors of packages: ${JSON.stringify( + Object.keys(resultWorkspaceContent[1]), + )}`, + ); + resultWorkspaceContent = await getAncestorWorkspacesContent( + resultWorkspaceContent, + ); + } + + logger.debug( + `Selected test packages are: ${JSON.stringify( + Object.keys(resultWorkspaceContent[1]), + )}`, + ); + + return computeTestsRegexes(resultWorkspaceContent); +} + +// This function returns a WorkspaceContent containing all the changed workspaces, compared to targetBranch +async function getChangedWorkspacesContent(targetBranch: string | undefined) { const allWorkspaces = await getAllWorkspaces(getModularRoot()); // Get the changed workspaces compared to our target branch const changedWorkspaces = await getChangedWorkspaces( allWorkspaces, targetBranch, ); - // Get the ancestors from the changed workspaces - const selectedWorkspaces = ancestors - ? computeAncestorWorkspaces(changedWorkspaces, allWorkspaces) - : changedWorkspaces; + return changedWorkspaces; +} + +// This function returns a WorkspaceContent from an array of workspace names +async function getSinglePackagesContent(singlePackages: string[]) { + // Get all the workspaces + const allWorkspaces = await getAllWorkspaces(getModularRoot()); + const uniqueSinglePackages = Array.from(new Set(singlePackages)); + + const result: WorkspaceContent = [ + new Map(), + {}, + ]; + + const [sourcePackageContent, sourcePackageMap] = allWorkspaces; + const [targetPackageContent, targetPackageMap] = result; + + // Filter, copy onto result and validate existence. This is easier to express with a mutable for loop + for (const pkgName of uniqueSinglePackages) { + const packageContent = sourcePackageContent.get(pkgName); + if (!sourcePackageMap[pkgName] || !packageContent) { + throw new Error( + `Package ${pkgName} was specified, but Modular couldn't find it in the workspaces.`, + ); + } + targetPackageContent.set(pkgName, packageContent); + targetPackageMap[pkgName] = sourcePackageMap[pkgName]; + } + + return result; +} + +// This function takes a WorkspaceContent and returns a WorkspaceContent agumented with all the ancestors of the original one +async function getAncestorWorkspacesContent( + selectedWorkspaces: WorkspaceContent, +) { + const allWorkspaces = await getAllWorkspaces(getModularRoot()); + return computeAncestorWorkspaces(selectedWorkspaces, allWorkspaces); +} +// This function returns a list for test regexes from a WorkspaceContent +function computeTestsRegexes(selectedWorkspaces: WorkspaceContent) { const testRegexes = Object.values(selectedWorkspaces[1]).map( ({ location }) => location, ); diff --git a/packages/modular-scripts/src/utils/gitActions.ts b/packages/modular-scripts/src/utils/gitActions.ts index 52aa3c468..9905c2d4d 100644 --- a/packages/modular-scripts/src/utils/gitActions.ts +++ b/packages/modular-scripts/src/utils/gitActions.ts @@ -1,6 +1,7 @@ import stripAnsi from 'strip-ansi'; import execa from 'execa'; import getModularRoot from './getModularRoot'; +import * as logger from './logger'; export function cleanGit(cwd: string): boolean { const trackedChanged = stripAnsi( @@ -36,10 +37,20 @@ function getGitDefaultBranch(): string { 'symbolic-ref', 'refs/remotes/origin/HEAD', ]); - return `origin/${stripAnsi(result.stdout).split('/').pop() as string}`; + const defaultBranch = `origin/${ + stripAnsi(result.stdout).split('/').pop() as string + }`; + logger.debug( + `Git default branch calculated from remote origin: ${defaultBranch}`, + ); + return defaultBranch; } catch (err) { // no remote origin, look into git config for init.defaultBranch setting - return getGitLocalDefaultBranch(); + const defaultBranch = getGitLocalDefaultBranch(); + logger.debug( + `Git default branch calculated from local git config: ${defaultBranch}`, + ); + return defaultBranch; } } From 2f64ea36a1aa6979458712be805d009bee7edc59 Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Wed, 14 Sep 2022 18:01:49 +0100 Subject: [PATCH 2/8] Create friendly-dancers-fix.md --- .changeset/friendly-dancers-fix.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/friendly-dancers-fix.md diff --git a/.changeset/friendly-dancers-fix.md b/.changeset/friendly-dancers-fix.md new file mode 100644 index 000000000..68e7a7058 --- /dev/null +++ b/.changeset/friendly-dancers-fix.md @@ -0,0 +1,5 @@ +--- +"modular-scripts": minor +--- + +Selective test on workspace name with the --package option From 1cb6c0f6a150c8fb36abeb6e079e8bbc68c97aeb Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Thu, 15 Sep 2022 16:35:41 +0100 Subject: [PATCH 3/8] Quit when selective tests find no workspaces --- packages/modular-scripts/src/test/index.ts | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/modular-scripts/src/test/index.ts b/packages/modular-scripts/src/test/index.ts index 2d61c9d09..a6ce9c69b 100644 --- a/packages/modular-scripts/src/test/index.ts +++ b/packages/modular-scripts/src/test/index.ts @@ -122,16 +122,26 @@ async function test( const cleanRegexes: string[] = []; // There are two ways of discovering the test regexes we need: either they're specified by the user as CLI arguments - // or they have to be calculated from selective options (--changed and --package) and optionally agumented with --ancestors. - const regexes = - changed || packages?.length - ? await computeSelectiveRegexes({ - changed, - compareBranch, - packages, - ancestors, - }) - : userRegexes; + // or they have to be calculated from selective options (--changed and --package) and optionally agumented with --ancestors + const isSelective = changed || packages?.length; + const regexes = isSelective + ? await computeSelectiveRegexes({ + changed, + compareBranch, + packages, + ancestors, + }) + : userRegexes; + + // If test is selective (user set --changed or --package) and we computed no regexes, then bail out + if (!regexes?.length && isSelective) { + process.stdout.write( + changed + ? 'No changed workspaces found' + : 'No workspaces found in selection', + ); + process.exit(0); + } if (regexes?.length) { regexes.forEach((reg) => { From f490d7352b635f33e3d1c9da1da22a1b5ba7592a Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Thu, 15 Sep 2022 18:22:39 +0100 Subject: [PATCH 4/8] Add tests for package option --- .../src/__tests__/test.test.ts | 75 +++++++++++++++++++ packages/modular-scripts/src/test/index.ts | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/modular-scripts/src/__tests__/test.test.ts b/packages/modular-scripts/src/__tests__/test.test.ts index 778e29186..d0533e504 100644 --- a/packages/modular-scripts/src/__tests__/test.test.ts +++ b/packages/modular-scripts/src/__tests__/test.test.ts @@ -194,6 +194,81 @@ describe('Modular test command', () => { ); }); }); + + describe('test command can successfully do selective tests based on selected packages', () => { + const fixturesFolder = path.join( + __dirname, + Array.from({ length: 4 }).reduce( + (acc) => `${acc}..${path.sep}`, + '', + ), + '__fixtures__', + 'ghost-testing', + ); + + const currentModularFolder = getModularRoot(); + let randomOutputFolder: string; + + beforeEach(() => { + // Create random dir + randomOutputFolder = tmp.dirSync({ unsafeCleanup: true }).name; + fs.copySync(fixturesFolder, randomOutputFolder); + execa.sync('yarn', { + cwd: randomOutputFolder, + }); + }); + + // Run in a single test, serially for performance reasons (the setup time is quite long) + it('finds --package after specifying a valid workspaces / finds ancestors using --ancestors', () => { + const resultPackages = runRemoteModularTest( + currentModularFolder, + randomOutputFolder, + ['test', '--package', 'b', '--package', 'c'], + ); + expect(resultPackages.stderr).toContain( + 'packages/c/src/__tests__/utils/c-nested.test.ts', + ); + expect(resultPackages.stderr).toContain( + 'packages/c/src/__tests__/c.test.ts', + ); + expect(resultPackages.stderr).toContain( + 'packages/b/src/__tests__/utils/b-nested.test.ts', + ); + expect(resultPackages.stderr).toContain( + 'packages/b/src/__tests__/b.test.ts', + ); + + const resultPackagesWithAncestors = runRemoteModularTest( + currentModularFolder, + randomOutputFolder, + ['test', '--ancestors', '--package', 'b', '--package', 'c'], + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/c/src/__tests__/utils/c-nested.test.ts', + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/c/src/__tests__/c.test.ts', + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/b/src/__tests__/utils/b-nested.test.ts', + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/b/src/__tests__/b.test.ts', + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/a/src/__tests__/utils/a-nested.test.ts', + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/a/src/__tests__/a.test.ts', + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/e/src/__tests__/utils/e-nested.test.ts', + ); + expect(resultPackagesWithAncestors.stderr).toContain( + 'packages/e/src/__tests__/e.test.ts', + ); + }); + }); }); function runRemoteModularTest( diff --git a/packages/modular-scripts/src/test/index.ts b/packages/modular-scripts/src/test/index.ts index a6ce9c69b..17711a3e5 100644 --- a/packages/modular-scripts/src/test/index.ts +++ b/packages/modular-scripts/src/test/index.ts @@ -286,7 +286,7 @@ async function getSinglePackagesContent(singlePackages: string[]) { const packageContent = sourcePackageContent.get(pkgName); if (!sourcePackageMap[pkgName] || !packageContent) { throw new Error( - `Package ${pkgName} was specified, but Modular couldn't find it in the workspaces.`, + `Package ${pkgName} was specified, but Modular couldn't find it`, ); } targetPackageContent.set(pkgName, packageContent); From 086a088eebdabd1b669ecde1f7cef0ee3a9c3b46 Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Fri, 16 Sep 2022 12:53:06 +0100 Subject: [PATCH 5/8] Add unhappy paths test --- .../src/__tests__/test.test.ts | 128 ++++++++++++++++++ packages/modular-scripts/src/program.ts | 12 +- 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/packages/modular-scripts/src/__tests__/test.test.ts b/packages/modular-scripts/src/__tests__/test.test.ts index d0533e504..c21ed373c 100644 --- a/packages/modular-scripts/src/__tests__/test.test.ts +++ b/packages/modular-scripts/src/__tests__/test.test.ts @@ -269,6 +269,134 @@ describe('Modular test command', () => { ); }); }); + + describe('test command has error states', () => { + // Run in a single test, serially for performance reasons (the setup time is quite long) + it('errors when specifying --package with --changed', async () => { + let errorNumber; + try { + await execa( + 'yarnpkg', + ['modular', 'test', '--changed', '--package', 'modular-scripts'], + { + all: true, + cleanup: true, + }, + ); + } catch (error) { + errorNumber = (error as ExecaError).exitCode; + } + expect(errorNumber).toEqual(1); + }); + + it('errors when specifying --package with a non-existing workspace', async () => { + let capturedError; + try { + await execa( + 'yarnpkg', + ['modular', 'test', '--package', 'non-existing'], + { + all: true, + cleanup: true, + }, + ); + } catch (error) { + capturedError = error as ExecaError; + } + expect(capturedError?.exitCode).toEqual(1); + expect(capturedError?.stderr).toContain( + `Package non-existing was specified, but Modular couldn't find it`, + ); + }); + + it('errors when specifying a regex with --packages', async () => { + let capturedError; + try { + await execa( + 'yarnpkg', + [ + 'modular', + 'test', + 'memoize.test.ts', + '--package', + 'modular-scripts', + ], + { + all: true, + cleanup: true, + }, + ); + } catch (error) { + capturedError = error as ExecaError; + } + expect(capturedError?.exitCode).toEqual(1); + expect(capturedError?.stderr).toContain( + `Option --package conflicts with supplied test regex`, + ); + }); + + it('errors when specifying a regex with --package', async () => { + let capturedError; + try { + await execa( + 'yarnpkg', + [ + 'modular', + 'test', + 'memoize.test.ts', + '--package', + 'modular-scripts', + ], + { + all: true, + cleanup: true, + }, + ); + } catch (error) { + capturedError = error as ExecaError; + } + expect(capturedError?.exitCode).toEqual(1); + expect(capturedError?.stderr).toContain( + `Option --package conflicts with supplied test regex`, + ); + }); + + it('errors when specifying a regex with --changed', async () => { + let capturedError; + try { + await execa( + 'yarnpkg', + ['modular', 'test', 'memoize.test.ts', '--changed'], + { + all: true, + cleanup: true, + }, + ); + } catch (error) { + capturedError = error as ExecaError; + } + expect(capturedError?.exitCode).toEqual(1); + expect(capturedError?.stderr).toContain( + `Option --changed conflicts with supplied test regex`, + ); + }); + + it('errors when specifying --compareBranch without --changed', async () => { + let capturedError; + try { + await execa('yarnpkg', ['modular', 'test', '--compareBranch', 'main'], { + all: true, + cleanup: true, + }); + } catch (error) { + capturedError = error as ExecaError; + } + expect(capturedError?.exitCode).toEqual(1); + expect(capturedError?.stderr).toContain( + `Option --compareBranch doesn't make sense without option --changed`, + ); + }); + }); }); function runRemoteModularTest( diff --git a/packages/modular-scripts/src/program.ts b/packages/modular-scripts/src/program.ts index 3abc76c6d..c4f2bab0b 100755 --- a/packages/modular-scripts/src/program.ts +++ b/packages/modular-scripts/src/program.ts @@ -171,29 +171,31 @@ program .action(async (regexes: string[], options: CLITestOptions) => { if (options.ancestors && !options.changed && !options.package) { process.stderr.write( - "Option --ancestors doesn't make sense without option --changed or option --package", + "Option --ancestors doesn't make sense without option --changed or option --package\n", ); process.exit(1); } if (options.package && options.changed) { - process.stderr.write('Option --package conflicts with option --changed'); + process.stderr.write( + 'Option --package conflicts with option --changed\n', + ); process.exit(1); } if (options.compareBranch && !options.changed) { process.stderr.write( - "Option --compareBranch doesn't make sense without option --changed", + "Option --compareBranch doesn't make sense without option --changed\n", ); process.exit(1); } if (options.changed && regexes.length) { process.stderr.write( - 'Option --changed conflicts with supplied test regex', + 'Option --changed conflicts with supplied test regex\n', ); process.exit(1); } if (options.package && regexes.length) { process.stderr.write( - 'Option --package conflicts with supplied test regex', + 'Option --package conflicts with supplied test regex\n', ); process.exit(1); } From 98f82ed6d21113a685d0a1c4c53fa6a27c7c4e87 Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Fri, 16 Sep 2022 13:46:03 +0100 Subject: [PATCH 6/8] Add docs, fix headings --- docs/commands/test.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/commands/test.md b/docs/commands/test.md index adb409f2e..1256da024 100644 --- a/docs/commands/test.md +++ b/docs/commands/test.md @@ -64,7 +64,7 @@ Specify the comparison branch used to determine which files have changed when using the `changed` option. If this option is used without `changed`, the command will fail. -#### collectCoverageFrom +### collectCoverageFrom [_Documentation_](https://jestjs.io/docs/configuration#collectcoveragefrom-array) @@ -88,7 +88,7 @@ Example: } ``` -#### coveragePathIgnorePatterns +### coveragePathIgnorePatterns [_Documentation_](https://jestjs.io/docs/configuration#coveragepathignorepatterns-arraystring) @@ -98,7 +98,7 @@ An array of regexp pattern strings that are matched against all file paths before executing the test. If the file path matches any of the patterns, coverage information will be skipped. -#### coverageThreshold +### coverageThreshold [_Documentation_](https://jestjs.io/docs/configuration#coveragethreshold-object) @@ -111,7 +111,7 @@ positive number are taken to be the minimum percentage required. Thresholds specified as a negative number represent the maximum number of uncovered entities allowed. -#### moduleNameMapper +### moduleNameMapper [_Documentation_](https://jestjs.io/docs/configuration#modulenamemapper-objectstring-string--arraystring) @@ -131,7 +131,7 @@ The moduleNameMapper is merged with the `modular` defaults to provide common use cases for static assets, like static assets including images, CSS and CSS-modules. -#### modulePathIgnorePatterns +### modulePathIgnorePatterns [_Documentation_](https://jestjs.io/docs/configuration#modulepathignorepatterns-arraystring) @@ -142,7 +142,16 @@ before those paths are to be considered 'visible' to the module loader. If a given module's path matches any of the patterns, it will not be `require()`-able in the test environment. -#### testPathIgnorePatterns +### package + +Default: `undefined` + +Run all the test for the workspace with the specified package name. Can be +repeated to select more than one workspace. Can be combined with the +`--ancestors` option to test the specified workspace(s) plus all the workspaces +that, directly or indirectly, depend on them. Conflicts with `--changed`. + +### testPathIgnorePatterns [_Documentation_](https://jestjs.io/docs/configuration#testpathignorepatterns-arraystring) @@ -152,7 +161,7 @@ An array of regexp pattern strings that are matched against all test paths before executing the test. If the test path matches any of the patterns, it will be skipped. -#### testRunner +### testRunner [_Documentation_](https://jestjs.io/docs/configuration#testrunner-string) @@ -166,7 +175,7 @@ This can be used to revert to the previous `jest` default testRunner (`jest-jasmine2`) in cases where `circus` is not yet compatible with an older codebase. -#### transformIgnorePatterns +### transformIgnorePatterns [_Documentation_](https://jestjs.io/docs/configuration#transformignorepatterns-arraystring) From 2d649a4f3c6063b6c6c74faffc5ac1f4c866c8f0 Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Fri, 16 Sep 2022 13:51:35 +0100 Subject: [PATCH 7/8] Add newlines to stderr messages --- packages/modular-scripts/src/test/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modular-scripts/src/test/index.ts b/packages/modular-scripts/src/test/index.ts index 17711a3e5..28e8063a8 100644 --- a/packages/modular-scripts/src/test/index.ts +++ b/packages/modular-scripts/src/test/index.ts @@ -137,8 +137,8 @@ async function test( if (!regexes?.length && isSelective) { process.stdout.write( changed - ? 'No changed workspaces found' - : 'No workspaces found in selection', + ? 'No changed workspaces found\n' + : 'No workspaces found in selection\n', ); process.exit(0); } From 0111c22dd15682737e1b742b9cd84409d3d905e3 Mon Sep 17 00:00:00 2001 From: Cristiano Belloni Date: Fri, 16 Sep 2022 17:11:54 +0100 Subject: [PATCH 8/8] Update docs/commands/test.md Co-authored-by: Sam Brown --- docs/commands/test.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/commands/test.md b/docs/commands/test.md index 1256da024..449ad6f94 100644 --- a/docs/commands/test.md +++ b/docs/commands/test.md @@ -146,7 +146,7 @@ in the test environment. Default: `undefined` -Run all the test for the workspace with the specified package name. Can be +Run all the tests for the workspace with the specified package name. Can be repeated to select more than one workspace. Can be combined with the `--ancestors` option to test the specified workspace(s) plus all the workspaces that, directly or indirectly, depend on them. Conflicts with `--changed`.