Skip to content
Merged
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
9 changes: 7 additions & 2 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ jobs:
# Keep a separate Flakiness.io history for each Node.js major.
FK_ENV_NODEJS: ${{ matrix.node }}
run: |
# New workflow runs vary the seed; reruns keep the original seed.
echo "Reproduce with Node.js $FK_ENV_NODEJS: npm run test:unit:vitest:shuffled -- --sequence.seed=$GITHUB_RUN_NUMBER --project=node --project=jsdom --shard=$SHARD"
npm run test:unit:vitest:shuffled -- \
--sequence.seed="$GITHUB_RUN_NUMBER" \
Comment thread
ciampo marked this conversation as resolved.
--project=node \
--project=jsdom \
--shard="$SHARD" \
Expand Down Expand Up @@ -177,9 +180,11 @@ jobs:
echo "browser-trace-file must name an existing *.browser.test.* file under packages/ or test/."
exit 1
fi
WP_VITEST_BROWSER_TRACE=1 npm run test:unit:vitest:shuffled -- --project=browser "$BROWSER_TRACE_FILE"
echo "Reproduce with Node.js $FK_ENV_NODEJS: WP_VITEST_BROWSER_TRACE=1 npm run test:unit:vitest:shuffled -- --sequence.seed=$GITHUB_RUN_NUMBER --project=browser \"$BROWSER_TRACE_FILE\""
WP_VITEST_BROWSER_TRACE=1 npm run test:unit:vitest:shuffled -- --sequence.seed="$GITHUB_RUN_NUMBER" --project=browser "$BROWSER_TRACE_FILE"
else
npm run test:unit:vitest:shuffled -- --project=browser
echo "Reproduce with Node.js $FK_ENV_NODEJS: npm run test:unit:vitest:shuffled -- --sequence.seed=$GITHUB_RUN_NUMBER --project=browser"
npm run test:unit:vitest:shuffled -- --sequence.seed="$GITHUB_RUN_NUMBER" --project=browser
fi

- name: Upload Browser Mode failure artifacts
Expand Down
13 changes: 12 additions & 1 deletion docs/contributors/code/testing-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,18 @@ Run `npm run lint` independently for code style checks. [ESLint](https://eslint.

`npm run test:unit:routing` compares live Vitest discovery with the repository's test-file patterns. Each discovered test must belong to exactly one project, selected by its filename. The check rejects missing tests, duplicate ownership, per-file environment overrides, and obsolete Jest runner infrastructure. It does not depend on a fixed test count or migration metadata. The required `All` CI check runs it.

`npm run test:unit:conventions` checks explicit Vitest imports, workspace dependencies, the TypeScript test graph, environment conventions, and isolation defaults. Keep the existing Node 24/26 matrix, four Node/jsdom shards per runtime, one Chromium job on Node 24, timezone checks, and Storybook smoke coverage when changing test infrastructure. `npm run test:unit:vitest:shuffled` shuffles files with the fixed seed `80855`. It does not shuffle tests inside each file.
`npm run test:unit:conventions` checks explicit Vitest imports, workspace dependencies, the TypeScript test graph, environment conventions, and isolation defaults. Keep the existing Node 24/26 matrix, four Node/jsdom shards per runtime, one Chromium job on Node 24, timezone checks, and Storybook smoke coverage when changing test infrastructure.

`npm run test:unit:vitest:shuffled` shuffles files and tests inside each file. CI uses `GITHUB_RUN_NUMBER` as the seed across all Node/jsdom shards and the Chromium job. Each new workflow run uses a different seed; rerunning that workflow keeps the same seed. Each job logs its seed and a reproduction command. Use the same checkout and Node.js version when reproducing a failure.

Locally, Vitest chooses a seed from the current time unless you pass `--sequence.seed`. Supply the seed from a CI log to reproduce its ordering, retaining the project and shard arguments shown there. For example:

```sh
npm run test:unit:vitest:shuffled -- --sequence.seed=12345 --project=node --project=jsdom --shard=1/4
npm run test:unit:vitest:shuffled -- --sequence.seed=12345 --project=browser
```

A new seed can expose an existing test-order dependency on an unrelated pull request. When this happens, reproduce the failure with the logged seed and fix the shared state or missing setup and cleanup. Do not push an unrelated change just to get a different seed. If a fix cannot be made promptly, report the failure with its seed, commit, Node.js version, project, and shard, and agree on a temporary quarantine with the maintainers. Keep any quarantine limited to the affected test and link a tracking issue for the fix and removal of the quarantine. Rerun the original failing seed to verify the fix.

#### Public tooling consumers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe( 'useCopyOnClick', () => {
} );

it( 'should not update hasCopied after unmount', async () => {
const user = userEvent.setup();
const renderSpy = vi.fn();

const SpyComponent = ( { text }: { text: string } ) => {
Expand All @@ -132,7 +133,6 @@ describe( 'useCopyOnClick', () => {
delayedPromise as Promise< void >
);

const user = userEvent.setup();
const { unmount } = render( <SpyComponent text="test" /> );

expect( renderSpy ).toHaveBeenLastCalledWith( false );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe( 'useCopyToClipboard', () => {
} );

it( 'should call onSuccess after the node unmounts', async () => {
const user = userEvent.setup();
let resolvePromise: () => void;
const delayedPromise = new Promise< void >( ( resolve ) => {
resolvePromise = resolve;
Expand All @@ -71,7 +72,6 @@ describe( 'useCopyToClipboard', () => {
delayedPromise
);

const user = userEvent.setup();
const onSuccess = vi.fn();
const { unmount } = render(
<TestComponent text="test" onSuccess={ onSuccess } />
Expand All @@ -89,6 +89,7 @@ describe( 'useCopyToClipboard', () => {
} );

it( 'should not restore focus after the node unmounts', async () => {
const user = userEvent.setup();
let resolvePromise: () => void;
const delayedPromise = new Promise< void >( ( resolve ) => {
resolvePromise = resolve;
Expand All @@ -97,7 +98,6 @@ describe( 'useCopyToClipboard', () => {
delayedPromise
);

const user = userEvent.setup();
const { unmount } = render( <TestComponent text="test" /> );

const button = screen.getByRole( 'button' );
Expand Down
5 changes: 5 additions & 0 deletions packages/core-data/src/test/store.jsdom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ describe( 'clearEntityRecordEdits', () => {
const select = registry.select( coreDataStore );

dispatch.receiveEntityRecords( 'postType', 'post', post );
dispatch.finishResolution( 'getEntityRecord', [
'postType',
'post',
post.id,
] );
dispatch.editEntityRecord( 'postType', 'post', post.id, {
slug: 'updated-slug',
} );
Expand Down
7 changes: 7 additions & 0 deletions packages/upload-media/src/store/test/vips.jsdom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@ describe( 'vips utilities', () => {
} );

describe( 'vipsCancelOperations', () => {
beforeEach( async () => {
// Cancellation only delegates to the worker after an image operation loads it.
mockCompressImage.mockResolvedValue( new ArrayBuffer( 5 ) );
await vipsCompressImage( 'item-1', jpegFile );
} );

it( 'calls worker cancelOperations with item ID', async () => {
mockCancelOperations.mockResolvedValue( true );

Expand All @@ -378,6 +384,7 @@ describe( 'vips utilities', () => {

const result = await vipsCancelOperations( 'item-456' );

expect( mockCancelOperations ).toHaveBeenCalledWith( 'item-456' );
expect( result ).toBe( false );
} );
} );
Expand Down
2 changes: 1 addition & 1 deletion test/unit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"test:unit:conventions": "node scripts/validate-vitest-conventions.mjs",
"test:unit:date": "node scripts/run-date-tests.mts",
"test:unit:vitest": "vitest run --config vitest.config.mjs",
"test:unit:vitest:shuffled": "npm run test:unit:vitest -- --sequence.shuffle.files --sequence.seed=80855",
"test:unit:vitest:shuffled": "npm run test:unit:vitest -- --sequence.shuffle.files --sequence.shuffle.tests",
"test:unit:vitest:update": "vitest run --config vitest.config.mjs --update=true",
"test:unit:vitest:watch": "vitest --config vitest.config.mjs",
"test:unit:profile": "vitest run --config vitest.config.mjs --pool=forks --no-file-parallelism --execArgv=--cpu-prof",
Expand Down
2 changes: 1 addition & 1 deletion test/unit/scripts/test-infrastructure-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const WORKSPACE_ROUTING_COMMAND = 'node scripts/validate-test-routing.mjs';
const ROOT_SHUFFLED_COMMAND =
'npm run --workspace @wordpress/unit-tests test:unit:vitest:shuffled --';
const WORKSPACE_SHUFFLED_COMMAND =
'npm run test:unit:vitest -- --sequence.shuffle.files --sequence.seed=80855';
'npm run test:unit:vitest -- --sequence.shuffle.files --sequence.shuffle.tests';

function normalizeShellCommand( command ) {
return command
Expand Down
28 changes: 16 additions & 12 deletions test/unit/scripts/test/test-infrastructure-policy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ describe( 'test infrastructure policy', () => {
] );
} );

it( 'requires deterministic file-order shuffling', () => {
it( 'requires file and test-order shuffling without a fixed seed', () => {
const validRootPackageJson = {
scripts: {
'test:unit:vitest:shuffled':
Expand All @@ -163,7 +163,7 @@ describe( 'test infrastructure policy', () => {
const validUnitTestPackageJson = {
scripts: {
'test:unit:vitest:shuffled':
'npm run test:unit:vitest -- --sequence.shuffle.files --sequence.seed=80855',
'npm run test:unit:vitest -- --sequence.shuffle.files --sequence.shuffle.tests',
},
};

Expand All @@ -180,16 +180,20 @@ describe( 'test infrastructure policy', () => {
).toEqual( [
'package.json: scripts.test:unit:vitest:shuffled must be exactly `npm run --workspace @wordpress/unit-tests test:unit:vitest:shuffled --`',
] );
expect(
validateVitestShuffleScripts( validRootPackageJson, {
scripts: {
'test:unit:vitest:shuffled':
'npm run test:unit:vitest -- --sequence.shuffle --sequence.seed=80855',
},
} )
).toEqual( [
'test/unit/package.json: scripts.test:unit:vitest:shuffled must be exactly `npm run test:unit:vitest -- --sequence.shuffle.files --sequence.seed=80855`',
] );
for ( const command of [
'npm run test:unit:vitest -- --sequence.shuffle.files',
'npm run test:unit:vitest -- --sequence.shuffle.tests',
'npm run test:unit:vitest -- --sequence.shuffle',
'npm run test:unit:vitest -- --sequence.shuffle.files --sequence.shuffle.tests --sequence.seed=80855',
] ) {
expect(
validateVitestShuffleScripts( validRootPackageJson, {
scripts: { 'test:unit:vitest:shuffled': command },
} )
).toEqual( [
'test/unit/package.json: scripts.test:unit:vitest:shuffled must be exactly `npm run test:unit:vitest -- --sequence.shuffle.files --sequence.shuffle.tests`',
] );
}
expect(
validateVitestShuffleScripts(
validRootPackageJson,
Expand Down
Loading