Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/static-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:
run: npm run lint:js -- --format compact --quiet --prune-suppressions

- name: Test agent skill setup
run: npm run test:unit:vitest -- --project=node tools/agents/test/setup-skills.test.js
run: npm run test:unit:vitest -- --project='unit (node)' tools/agents/test/setup-skills.test.js

- name: Lint Styles
if: ${{ matrix.annotations }}
Expand Down
9 changes: 9 additions & 0 deletions test/unit/scripts/discover-test-files.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const TEST_IGNORES = [
];

export const VITEST_PROJECT_NAMES = [ 'node', 'jsdom', 'browser' ];
export const VITEST_SHARED_PROJECT_NAME = 'unit';

const JSDOM_TEST_PATH_PATTERN = /\.jsdom\.test\.[jt]sx?$/;
const BROWSER_TEST_PATH_PATTERN = /\.browser\.test\.[jt]sx?$/;
Expand Down Expand Up @@ -72,6 +73,14 @@ export function getTestEnvironmentName( testPath ) {
return 'node';
}

export function getVitestProjectName( listedProjectName ) {
const nestedProjectName = listedProjectName.match(
new RegExp( `^${ VITEST_SHARED_PROJECT_NAME } \\(([^)]+)\\)$` )
)?.[ 1 ];

return nestedProjectName ?? listedProjectName.replace( / \(.+\)$/, '' );
}

export function getVitestTestsByProject( discoveredTests, manifest ) {
const testsByProject = Object.fromEntries(
VITEST_PROJECT_NAMES.map( ( projectName ) => [ projectName, [] ] )
Expand Down
27 changes: 15 additions & 12 deletions test/unit/scripts/test-infrastructure-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ function getPropertyName( property ) {
function findConfigIsolationOptOuts( rootDir ) {
const violations = [];
const configFiles = globSync(
'**/{vite,vitest}.config.{js,jsx,cjs,mjs,ts,tsx,cts,mts}',
'**/{vite,vitest}{,.*}.config.{js,jsx,cjs,mjs,ts,tsx,cts,mts}',
{
cwd: rootDir,
ignore: SOURCE_IGNORES,
Expand Down Expand Up @@ -342,7 +342,10 @@ export function findVitestIsolationOptOuts( rootDir ) {
].sort();
}

export function validateVitestCleanupConfig( vitestConfig ) {
export function validateVitestCleanupConfig(
vitestConfig,
configFile = 'test/unit/vitest.config.mjs'
) {
const violations = [];
const requiredOptions = [
'isolate',
Expand All @@ -354,39 +357,39 @@ export function validateVitestCleanupConfig( vitestConfig ) {

for ( const option of requiredOptions ) {
if ( vitestConfig.test?.[ option ] !== true ) {
violations.push(
`test/unit/vitest.config.mjs: test.${ option } must be true`
);
violations.push( `${ configFile }: test.${ option } must be true` );
}
}
if ( vitestConfig.test?.globals !== false ) {
violations.push(
'test/unit/vitest.config.mjs: test.globals must remain false'
);
violations.push( `${ configFile }: test.globals must remain false` );
}
if ( vitestConfig.test?.clearMocks !== false ) {
violations.push(
'test/unit/vitest.config.mjs: test.clearMocks must remain false while test.mockReset is true'
`${ configFile }: test.clearMocks must remain false while test.mockReset is true`
);
}

for ( const project of vitestConfig.test?.projects ?? [] ) {
if ( typeof project === 'string' ) {
continue;
}

const projectName = project.test?.name ?? 'unnamed';
if ( project.extends !== true ) {
violations.push(
`test/unit/vitest.config.mjs: ${ projectName } must set extends: true to inherit the shared isolation defaults`
`${ configFile }: ${ projectName } must set extends: true to inherit the shared isolation defaults`
);
}
for ( const option of requiredOptions ) {
if ( project.test?.[ option ] === false ) {
violations.push(
`test/unit/vitest.config.mjs: ${ projectName } overrides test.${ option } with false`
`${ configFile }: ${ projectName } overrides test.${ option } with false`
);
}
}
if ( project.test?.globals === true ) {
violations.push(
`test/unit/vitest.config.mjs: ${ projectName } enables global Vitest APIs`
`${ configFile }: ${ projectName } enables global Vitest APIs`
);
}
}
Expand Down
11 changes: 11 additions & 0 deletions test/unit/scripts/test/discover-test-files.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
import {
findAddedLegacyJestTests,
getTestEnvironmentName,
getVitestProjectName,
getVitestTestsByProject,
} from '../discover-test-files.mjs';
import { sourceHasTestEnvironmentOverride } from '../test-environment-overrides.mjs';
Expand All @@ -24,6 +25,16 @@ describe( 'getTestEnvironmentName', () => {
} );
} );

describe( 'getVitestProjectName', () => {
it( 'normalizes nested Node and jsdom project labels', () => {
expect( getVitestProjectName( 'unit (node)' ) ).toBe( 'node' );
expect( getVitestProjectName( 'unit (jsdom)' ) ).toBe( 'jsdom' );
expect( getVitestProjectName( 'browser (chromium)' ) ).toBe(
'browser'
);
} );
} );

describe( 'getVitestTestsByProject', () => {
it( 'routes every new test to Vitest without migration metadata', () => {
expect(
Expand Down
20 changes: 12 additions & 8 deletions test/unit/scripts/test/test-infrastructure-policy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ describe( 'runner isolation policy', () => {
recursive: true,
} );
writeFileSync(
path.join( rootDir, 'vitest.config.mjs' ),
path.join( rootDir, 'vitest.node-jsdom.config.mjs' ),
`const isolate = true;
export default {
test: {
Expand All @@ -238,8 +238,8 @@ export default {
'.github/workflows/test.yml:1 disables Vitest module isolation: vitest --no-isolate',
'package.json:scripts.test disables Vitest module isolation: vitest --no-isolate',
'package.json:scripts.test:browser disables Vitest module isolation: vitest --browser.isolate=false',
'vitest.config.mjs:4 must set isolate to the literal value true',
'vitest.config.mjs:6 must set isolate to the literal value true',
'vitest.node-jsdom.config.mjs:4 must set isolate to the literal value true',
'vitest.node-jsdom.config.mjs:6 must set isolate to the literal value true',
] );
} );

Expand All @@ -258,22 +258,26 @@ export default {
);
} );

it( 'requires every Vitest project to inherit the shared defaults', () => {
it( 'allows referenced project containers while requiring inline projects to inherit shared cleanup defaults', () => {
expect(
validateVitestCleanupConfig( {
test: {
clearMocks: false,
globals: false,
isolate: true,
mockReset: true,
restoreMocks: true,
unstubEnvs: true,
unstubGlobals: true,
projects: [ { extends: false, test: { name: 'escape' } } ],
projects: [
'vitest.unit.config.mjs',
{ extends: false, test: { name: 'escape' } },
],
},
} )
).toContain(
'test/unit/vitest.config.mjs: escape must set extends: true to inherit the shared isolation defaults'
);
).toEqual( [
'test/unit/vitest.config.mjs: escape must set extends: true to inherit the shared isolation defaults',
] );
} );
} );

Expand Down
3 changes: 2 additions & 1 deletion test/unit/scripts/validate-test-routing.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
findAddedLegacyJestTests,
findOverlappingVitestProjectTests,
getTestEnvironmentName,
getVitestProjectName,
getVitestTests,
getVitestTestsByProject,
VITEST_PROJECT_NAMES,
Expand Down Expand Up @@ -95,7 +96,7 @@ function listVitestTestsByProject() {
const match = line.match( /^\[([^\]]+)\]\s+(.+)$/ );
assert.ok( match, `Unexpected Vitest list output: ${ line }` );
const [ , listedProjectName, testPath ] = match;
const projectName = listedProjectName.replace( / \(.+\)$/, '' );
const projectName = getVitestProjectName( listedProjectName );
assert.ok(
testsByProject[ projectName ],
`Unexpected Vitest project \`${ projectName }\`. Expected only: ${ VITEST_PROJECT_NAMES.join(
Expand Down
22 changes: 16 additions & 6 deletions test/unit/scripts/validate-vitest-conventions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ const vitestTestSet = new Set( vitestTests );
const jsdomTests = new Set( vitestTestsByProject.jsdom );
const browserTests = new Set( vitestTestsByProject.browser );
const vitestInfrastructure = [
'vitest.unit.config.mjs',
'test/unit/vitest.config.mjs',
'test/unit/vitest.node-jsdom.config.mjs',
...globSync( 'test/unit/config/**/*.vitest*.{js,jsx,mjs,ts,tsx}', {
cwd: ROOT_DIR,
nodir: true,
Expand Down Expand Up @@ -140,15 +142,23 @@ const rootPackageJson = JSON.parse(
const unitTestPackageJson = JSON.parse(
readFileSync( path.join( ROOT_DIR, 'test/unit/package.json' ), 'utf8' )
);
const vitestConfig = (
await import(
pathToFileURL( path.join( ROOT_DIR, 'test/unit/vitest.config.mjs' ) )
)
).default;
const vitestConfigFiles = [
'test/unit/vitest.config.mjs',
'test/unit/vitest.node-jsdom.config.mjs',
];
const vitestConfigs = await Promise.all(
vitestConfigFiles.map( async ( configFile ) => [
configFile,
( await import( pathToFileURL( path.join( ROOT_DIR, configFile ) ) ) )
.default,
] )
);
violations.push(
...findVitestIsolationOptOuts( ROOT_DIR ),
...validateRoutingScripts( rootPackageJson, unitTestPackageJson ),
...validateVitestCleanupConfig( vitestConfig ),
...vitestConfigs.flatMap( ( [ configFile, config ] ) =>
validateVitestCleanupConfig( config, configFile )
),
...validateVitestShuffleScripts( rootPackageJson, unitTestPackageJson )
);

Expand Down
Loading
Loading