Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2d75e54
chore(core): prototype vitest setup for nx package unit tests
FrozenPandaz Aug 20, 2026
25d2968
chore(core): codemod jest.* to vi.* in nx package specs
FrozenPandaz Aug 21, 2026
d846d13
chore(core): convert jest.isolateModules to vi.resetModules + dynamic…
FrozenPandaz Aug 21, 2026
ac2347a
chore(core): fix native partial mocks, frozen-namespace spies, and sl…
FrozenPandaz Aug 21, 2026
b1b8ef0
chore(core): bridge CJS-channel mocks and constructor mocks for vitest
FrozenPandaz Aug 21, 2026
9baa136
chore(core): repair sync mock contracts and bridge lazy-required modules
FrozenPandaz Aug 21, 2026
99a4c81
chore(core): fix remaining channel mismatches and mock-registry seman…
FrozenPandaz Aug 21, 2026
e829e10
chore(core): fix hook-cleanup mock returns, regen snapshots for vitest
FrozenPandaz Aug 21, 2026
055955a
chore(core): neutralize swc-node stack patch breaking vitest snapshot…
FrozenPandaz Aug 21, 2026
8631229
chore(core): convert done-callback tests to promises for vitest
FrozenPandaz Aug 21, 2026
cc771ee
chore(core): widen fail-fast timing margin for parallel suite runs
FrozenPandaz Aug 21, 2026
6281e73
chore(core): run nx package unit tests with vitest via @nx/vitest plugin
FrozenPandaz Aug 21, 2026
c827f15
chore(core): migrate specs added on master to vitest
FrozenPandaz Aug 21, 2026
9283042
chore(core): make shared testing-util mocks dual-runner for jest cons…
FrozenPandaz Aug 21, 2026
5af2b9c
chore(core): remove stray it.only and regen stale snapshot in store-r…
FrozenPandaz Aug 21, 2026
144b77c
chore(core): pin vitest worker count for container-limited ci agents
FrozenPandaz Aug 22, 2026
ff30631
chore(core): bound vitest workers on ci only
FrozenPandaz Aug 22, 2026
c9b0bee
chore(core): ignore vitest pool teardown errors instead of bounding w…
FrozenPandaz Aug 22, 2026
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
3 changes: 2 additions & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,8 @@
},
"include": [
"packages/angular-rspack-compiler/**",
"packages/angular-rspack/**"
"packages/angular-rspack/**",
"packages/nx/**"
]
},
{
Expand Down
23 changes: 0 additions & 23 deletions packages/nx/jest-resolver.js

This file was deleted.

10 changes: 0 additions & 10 deletions packages/nx/jest.config.cts

This file was deleted.

3 changes: 0 additions & 3 deletions packages/nx/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,6 @@
}
},
"test-native": {},
"test": {
"inputs": ["...", "{workspaceRoot}/scripts/patched-jest-resolver.js"]
},
"format-native": {
"command": "cargo fmt",
"cache": true,
Expand Down
6 changes: 3 additions & 3 deletions packages/nx/release/changelog-renderer/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from '../../src/command-line/rele
import { GithubRemoteReleaseClient } from '../../src/command-line/release/utils/remote-release-clients/github';
import DefaultChangelogRenderer from './index';

jest.mock('../../src/project-graph/file-map-utils', () => ({
createFileMapUsingProjectGraph: jest.fn().mockImplementation(() => {
vi.mock('../../src/project-graph/file-map-utils', () => ({
createFileMapUsingProjectGraph: vi.fn().mockImplementation(() => {
return Promise.resolve({
allWorkspaceFiles: [],
fileMap: {
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('ChangelogRenderer', () => {
});

it('should not collect empty author emails (which would otherwise be attributed to the "find" user via ungh)', async () => {
const applyUsernameSpy = jest
const applyUsernameSpy = vi
.spyOn(remoteReleaseClient, 'applyUsernameToAuthors')
.mockResolvedValue(undefined);
const renderer = new DefaultChangelogRenderer({
Expand Down
4 changes: 2 additions & 2 deletions packages/nx/src/adapter/ngcli-adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
wrapAngularDevkitSchematic,
} from './ngcli-adapter';

jest.mock('../project-graph/project-graph', () => ({
...jest.requireActual('../project-graph/project-graph'),
vi.mock('../project-graph/project-graph', async () => ({
...(await vi.importActual('../project-graph/project-graph')),
createProjectGraphAsync: () => ({
nodes: {},
externalNodes: {},
Expand Down
4 changes: 2 additions & 2 deletions packages/nx/src/ai/configure-ai-agents-disclaimer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { tmpdir } from 'os';
import { shouldPrintConfigureAiAgentsDisclaimer } from './configure-ai-agents-disclaimer';
import { agentsMdPath, getAgentRulesWrapped } from './constants';

jest.mock('./detect-ai-agent', () => ({
detectAiAgent: jest.fn(() => null),
vi.mock('./detect-ai-agent', () => ({
detectAiAgent: vi.fn(() => null),
}));

import { detectAiAgent } from './detect-ai-agent';
Expand Down
20 changes: 10 additions & 10 deletions packages/nx/src/ai/set-up-ai-agents/set-up-ai-agents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import * as installedNxVersionUtils from '../../utils/installed-nx-version';
import * as cloneModule from '../clone-ai-config-repo';
import * as fs from 'fs';

jest.mock('fs', () => {
const actual = jest.requireActual('fs');
vi.mock('fs', async () => {
const actual = await vi.importActual('fs');
return {
...actual,
existsSync: jest
existsSync: vi
.fn()
.mockImplementation((...args: any[]) => actual.existsSync(...args)),
readFileSync: jest
readFileSync: vi
.fn()
.mockImplementation((...args: any[]) => actual.readFileSync(...args)),
};
Expand All @@ -35,7 +35,7 @@ describe('setup-ai-agents generator', () => {

// Mock getInstalledNxVersion to return Nx 22+ by default
// This ensures existing tests pass by defaulting to the new format
getInstalledNxVersionSpy = jest
getInstalledNxVersionSpy = vi
.spyOn(installedNxVersionUtils, 'getInstalledNxVersion')
.mockReturnValue('22.0.0');
});
Expand Down Expand Up @@ -717,7 +717,7 @@ describe('setup-ai-agents generator', () => {

it('should not delete .gemini/skills when .agents/skills does not exist', async () => {
// Mock getAiConfigRepoPath to fail so .agents/skills is not created
const spy = jest
const spy = vi
.spyOn(cloneModule, 'getAiConfigRepoPath')
.mockImplementation(() => {
throw new Error('no network');
Expand Down Expand Up @@ -1160,12 +1160,12 @@ config_file = ".codex/agents/ci-monitor-subagent.toml"
`;

beforeEach(() => {
getAiConfigRepoPathSpy = jest
getAiConfigRepoPathSpy = vi
.spyOn(cloneModule, 'getAiConfigRepoPath')
.mockReturnValue('/fake/repo');

const originalExistsSync = fs.existsSync;
existsSyncSpy = jest
existsSyncSpy = vi
.spyOn(fs, 'existsSync')
.mockImplementation((path: any) => {
if (
Expand All @@ -1190,7 +1190,7 @@ config_file = ".codex/agents/ci-monitor-subagent.toml"
});

const originalReadFileSync = fs.readFileSync;
readFileSyncSpy = jest
readFileSyncSpy = vi
.spyOn(fs, 'readFileSync')
.mockImplementation((path: any, ...args: any[]) => {
if (
Expand Down Expand Up @@ -1411,7 +1411,7 @@ sandbox_mode = "read-only"
// Mock readFileSync to fail only for package.json so it falls back to default version
// but allow other file reads (needed for generateFiles)
const originalReadFileSync = fs.readFileSync;
const readFileSyncSpy = jest
const readFileSyncSpy = vi
.spyOn(fs, 'readFileSync')
.mockImplementation((path: any, ...args: any[]) => {
if (
Expand Down
9 changes: 5 additions & 4 deletions packages/nx/src/command-line/ai/ai-output.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { writeAiOutput, logProgress, writeErrorLog } from './ai-output';

// Mock isAiAgent
jest.mock('../../native', () => ({
isAiAgent: jest.fn(),
vi.mock('../../native', async (importOriginal) => ({
...(await importOriginal<any>()),
isAiAgent: vi.fn(),
}));

import { isAiAgent } from '../../native';
Expand All @@ -12,13 +13,13 @@ describe('shared ai-output', () => {
let stdoutSpy: jest.SpyInstance;

beforeEach(() => {
stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation();
stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation();
mockIsAiAgent.mockReturnValue(false);
});

afterEach(() => {
stdoutSpy.mockRestore();
jest.restoreAllMocks();
vi.restoreAllMocks();
});

describe('writeAiOutput', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('completion/completion-providers', () => {

// The real readCachedProjectGraph reads from a module-load-frozen
// path, so we redirect it to the per-test workspace fixture.
readGraphSpy = jest
readGraphSpy = vi
.spyOn(projectGraphModule, 'readCachedProjectGraph')
.mockImplementation(() => {
const path = join(
Expand Down
12 changes: 6 additions & 6 deletions packages/nx/src/command-line/completion/metadata.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('completion/metadata', () => {

describe('findFlagCompletion', () => {
it('returns the registered handler', () => {
const handler = jest.fn(() => ['ok']);
const handler = vi.fn(() => ['ok']);
registerCompletion('meta-test-flag', { flags: { focus: handler } });
const meta = findCompletionMetadata(['meta-test-flag', ''])!.metadata;

Expand All @@ -77,7 +77,7 @@ describe('completion/metadata', () => {

describe('resolveCompletion', () => {
it('dispatches a positional `complete` fn with current + args', () => {
const complete = jest.fn(() => ['proj-a', 'proj-b']);
const complete = vi.fn(() => ['proj-a', 'proj-b']);
registerCompletion('meta-test-resolve-pos', {
positionals: [{ complete }],
});
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('completion/metadata', () => {
});

it('dispatches a flag handler when previousToken is a flag', () => {
const handler = jest.fn(() => ['root', 'lib']);
const handler = vi.fn(() => ['root', 'lib']);
registerCompletion('meta-test-resolve-flag', {
flags: { focus: handler },
});
Expand All @@ -130,8 +130,8 @@ describe('completion/metadata', () => {
});

it('flag dispatch takes precedence over positionals', () => {
const flag = jest.fn(() => ['from-flag']);
const positional = jest.fn(() => ['from-positional']);
const flag = vi.fn(() => ['from-flag']);
const positional = vi.fn(() => ['from-positional']);
registerCompletion('meta-test-resolve-precedence', {
positionals: [{ complete: positional }],
flags: { focus: flag },
Expand All @@ -153,7 +153,7 @@ describe('completion/metadata', () => {
// default (filename completion). Falling through to positional
// dispatch would offer wrong candidates (e.g. project names for
// `nx g app --directory <TAB>`).
const positional = jest.fn(() => ['x']);
const positional = vi.fn(() => ['x']);
registerCompletion('meta-test-resolve-flag-fallthrough', {
positionals: [{ complete: positional }],
flags: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('completion/registrations', () => {
originalRoot = currentWorkspaceRoot;
setWorkspaceRoot(workspaceRoot);

readGraphSpy = jest
readGraphSpy = vi
.spyOn(projectGraphModule, 'readCachedProjectGraph')
.mockImplementation(() => {
const path = join(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('completion/value-completions', () => {

beforeEach(() => {
captured = [];
logSpy = jest.spyOn(console, 'log').mockImplementation((line: any) => {
logSpy = vi.spyOn(console, 'log').mockImplementation((line: any) => {
captured.push(String(line) + '\n');
});
});
Expand Down Expand Up @@ -77,7 +77,7 @@ describe('completion/value-completions', () => {
});

it('passes the full tokens array (including the partial) to the completion fn', () => {
const complete = jest.fn(() => ['x']);
const complete = vi.fn(() => ['x']);
registerCompletion('value-args', { positionals: [{ complete }] });

tryValueCompletion(argv('value-args', 'partial'));
Expand Down
75 changes: 39 additions & 36 deletions packages/nx/src/command-line/format/format.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,47 @@ import { format } from './format';
// whether CI passes - fail-open, configured-but-not-installed, and the
// write/check dispatch - have no fast test. These mock the seams around it.

jest.mock('../../utils/formatters', () => ({ detectFormatter: jest.fn() }));
jest.mock('../../utils/formatters/oxfmt', () => ({
getOxfmtBinPath: jest.fn(),
writeWithOxfmt: jest.fn(),
checkWithOxfmt: jest.fn().mockResolvedValue([]),
vi.mock('../../utils/formatters', () => ({ detectFormatter: vi.fn() }));
vi.mock('../../utils/formatters/oxfmt', () => ({
getOxfmtBinPath: vi.fn(),
writeWithOxfmt: vi.fn(),
checkWithOxfmt: vi.fn().mockResolvedValue([]),
}));
jest.mock('../../utils/formatters/prettier', () => ({
getPrettierPath: jest.fn(),
writeWithPrettier: jest.fn(),
checkWithPrettier: jest.fn().mockResolvedValue([]),
filterToPrettierSupportedFiles: jest.fn(async (files: string[]) => files),
quoteForShell: jest.fn((pattern: string) => pattern),
vi.mock('../../utils/formatters/prettier', () => ({
getPrettierPath: vi.fn(),
writeWithPrettier: vi.fn(),
checkWithPrettier: vi.fn().mockResolvedValue([]),
filterToPrettierSupportedFiles: vi.fn(async (files: string[]) => files),
quoteForShell: vi.fn((pattern: string) => pattern),
}));
jest.mock('../../config/configuration', () => ({ readNxJson: () => ({}) }));
jest.mock('../../utils/command-line-utils', () => ({
splitArgsIntoNxArgsAndOverrides: jest.fn(),
parseFiles: jest.fn(() => ({ files: [] })),
getProjectRoots: jest.fn(() => []),
vi.mock('../../config/configuration', () => ({ readNxJson: () => ({}) }));
vi.mock('../../utils/command-line-utils', () => ({
splitArgsIntoNxArgsAndOverrides: vi.fn(),
parseFiles: vi.fn(() => ({ files: [] })),
getProjectRoots: vi.fn(() => []),
}));
jest.mock('../../plugins/js/utils/typescript', () => ({
getRootTsConfigFileName: jest.fn(() => 'tsconfig.base.json'),
getRootTsConfigPath: jest.fn(() => '/ws/tsconfig.base.json'),
vi.mock('../../plugins/js/utils/typescript', () => ({
getRootTsConfigFileName: vi.fn(() => 'tsconfig.base.json'),
getRootTsConfigPath: vi.fn(() => '/ws/tsconfig.base.json'),
}));
jest.mock('../../utils/ignore', () => ({
vi.mock('../../utils/ignore', () => ({
getIgnoreObject: () => ({ filter: (files: string[]) => files }),
}));
jest.mock('../../utils/fileutils', () => ({
...jest.requireActual('../../utils/fileutils'),
vi.mock('../../utils/fileutils', async () => ({
...(await vi.importActual('../../utils/fileutils')),
fileExists: () => true,
}));

const { detectFormatter } = require('../../utils/formatters');
const { getOxfmtBinPath, writeWithOxfmt, checkWithOxfmt } =
require('../../utils/formatters/oxfmt') as Record<string, jest.Mock>;
const { getPrettierPath, writeWithPrettier, checkWithPrettier } =
require('../../utils/formatters/prettier') as Record<string, jest.Mock>;
const { splitArgsIntoNxArgsAndOverrides, parseFiles } =
require('../../utils/command-line-utils') as Record<string, jest.Mock>;
const { detectFormatter } = await import('../../utils/formatters');
const { getOxfmtBinPath, writeWithOxfmt, checkWithOxfmt } = (await import(
'../../utils/formatters/oxfmt'
)) as Record<string, jest.Mock>;
const { getPrettierPath, writeWithPrettier, checkWithPrettier } = (await import(
'../../utils/formatters/prettier'
)) as Record<string, jest.Mock>;
const { splitArgsIntoNxArgsAndOverrides, parseFiles } = (await import(
'../../utils/command-line-utils'
)) as Record<string, jest.Mock>;

describe('nx format', () => {
let warn: jest.SpyInstance;
Expand All @@ -54,7 +57,7 @@ describe('nx format', () => {
splitArgsIntoNxArgsAndOverrides.mockReturnValue({ nxArgs });
}

beforeEach(() => {
beforeEach(async () => {
// `mockReset`, not `clearAllMocks`: the not-installed cases install a
// throwing implementation, and clearing only wipes the call log.
[
Expand All @@ -71,20 +74,20 @@ describe('nx format', () => {
getPrettierPath.mockReturnValue('/bin/prettier');
parseFiles.mockReturnValue({ files: [] });

const { output } = require('../../utils/output');
warn = jest.spyOn(output, 'warn').mockImplementation(() => {});
error = jest.spyOn(output, 'error').mockImplementation(() => {});
const { output } = await import('../../utils/output');
warn = vi.spyOn(output, 'warn').mockImplementation(() => {});
error = vi.spyOn(output, 'error').mockImplementation(() => {});
// Throw rather than return, so the code under test stops where it would.
exit = jest.spyOn(process, 'exit').mockImplementation(() => {
exit = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Exited();
});
jest.spyOn(console, 'log').mockImplementation(() => {});
vi.spyOn(console, 'log').mockImplementation(() => {});
checkWithOxfmt.mockResolvedValue([]);
checkWithPrettier.mockResolvedValue([]);
withArgs({ all: true });
});

afterEach(() => jest.restoreAllMocks());
afterEach(() => vi.restoreAllMocks());

it('warns and does nothing when no formatter is configured', async () => {
// Fail-open: a Biome/dprint workspace must not be reformatted, and
Expand Down
Loading
Loading