@nx/vitest 23.3.0-beta.7 → 23.3.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/executors/test/lib/nx-reporter.d.ts +1 -2
- package/dist/src/generators/configuration/configuration.js +10 -1
- package/dist/src/generators/init/init.js +3 -1
- package/dist/src/migrations/update-23-3-0/ai-instructions-for-vitest-5.md +92 -0
- package/dist/src/migrations/update-23-3-0/migrate-to-vitest-5.d.ts +14 -0
- package/dist/src/migrations/update-23-3-0/migrate-to-vitest-5.js +211 -0
- package/dist/src/plugins/plugin.js +1 -1
- package/dist/src/utils/ensure-dependencies.js +0 -4
- package/dist/src/utils/ignore-vitest-temp-files.js +10 -5
- package/dist/src/utils/version-utils.d.ts +1 -1
- package/dist/src/utils/version-utils.js +2 -3
- package/dist/src/utils/versions.d.ts +8 -4
- package/dist/src/utils/versions.js +57 -19
- package/migrations.json +89 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](<>)
|
|
13
13
|
[](http://commitizen.github.io/cz-cli/)
|
|
14
14
|
[](https://go.nx.dev/community)
|
|
15
|
-
[](https://nx.dev/nx-cloud)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -66,6 +66,14 @@ async function configurationGeneratorInternal(tree, schema, hasPlugin = false) {
|
|
|
66
66
|
const uiFramework = schema.uiFramework ?? (await (0, detect_ui_framework_1.detectUiFramework)(schema.project));
|
|
67
67
|
const isRootProject = root === '.';
|
|
68
68
|
tasks.push(await (0, js_1.initGenerator)(tree, { ...schema, skipFormat: true }));
|
|
69
|
+
// Written before init so version selection sees it: analog peers vitest 4,
|
|
70
|
+
// and init is what picks the vitest version.
|
|
71
|
+
if (uiFramework === 'angular' && !schema.skipPackageJson) {
|
|
72
|
+
tasks.push((0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
|
|
73
|
+
'@analogjs/vitest-angular': versions_1.analogVitestAngular,
|
|
74
|
+
'@analogjs/vite-plugin-angular': versions_1.analogVitestAngular,
|
|
75
|
+
}, undefined, true));
|
|
76
|
+
}
|
|
69
77
|
const initTask = await (0, init_1.default)(tree, {
|
|
70
78
|
skipFormat: true,
|
|
71
79
|
addPlugin: schema.addPlugin,
|
|
@@ -196,7 +204,8 @@ getTestBed().initTestEnvironment(
|
|
|
196
204
|
// workspace files in favor of inlining the projects into a root vitest.config
|
|
197
205
|
// via `test.projects` (https://vitest.dev/guide/migration.html#workspace-is-replaced-with-projects).
|
|
198
206
|
// Emit that shape for vitest 4+ and when the installed version can't be
|
|
199
|
-
// detected (new
|
|
207
|
+
// detected (a new install resolves to 4 or 5); vitest 3 keeps the workspace
|
|
208
|
+
// file.
|
|
200
209
|
if (!isRootProject) {
|
|
201
210
|
const projectGlobs = `'**/vite.config.{mjs,js,ts,mts}', '**/vitest.config.{mjs,js,ts,mts}'`;
|
|
202
211
|
const vitestMajorVersion = (0, versions_1.getInstalledVitestMajorVersion)(tree);
|
|
@@ -34,7 +34,9 @@ function updateDependencies(tree, schema) {
|
|
|
34
34
|
}
|
|
35
35
|
return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
|
|
36
36
|
'@nx/vitest': versions_1.nxVersion,
|
|
37
|
-
|
|
37
|
+
// The range about to be installed is passed for the case where the
|
|
38
|
+
// workspace has no vite of its own; it constrains the vitest major.
|
|
39
|
+
vitest: (0, versions_1.versions)(tree, { viteRange: viteVersionToUse }).vitestVersion,
|
|
38
40
|
vite: viteVersionToUse,
|
|
39
41
|
}, undefined, schema.keepExistingVersions ?? true);
|
|
40
42
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Vitest 5 migration
|
|
2
|
+
|
|
3
|
+
The deterministic pre-pass already handled the mechanical parts: removed deep
|
|
4
|
+
entry points were repointed where a plain import or export declaration reached
|
|
5
|
+
them, `.vitest` was added to `.gitignore`, and `vite` was declared in
|
|
6
|
+
`package.json` if the workspace had been relying on Vitest 4 pulling it in.
|
|
7
|
+
|
|
8
|
+
Anything it could not decide for itself is in the context it passed you. Work
|
|
9
|
+
through that list first, then run the tests.
|
|
10
|
+
|
|
11
|
+
Most of what is left is runtime behavior. Do not try to find it by reading every
|
|
12
|
+
test file. Run the test suites, then work through the failures with the list
|
|
13
|
+
below, which covers the changes that produce confusing failures.
|
|
14
|
+
|
|
15
|
+
## Run the tests first
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
nx run-many -t test
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## What the failures probably mean
|
|
22
|
+
|
|
23
|
+
**Mocks look empty.** `clearMocks` now defaults to `true`, so `mock.calls`,
|
|
24
|
+
`mock.instances`, `mock.contexts` and `mock.results` reset before every test. A
|
|
25
|
+
test that asserts on calls recorded by an earlier test now sees nothing. Fix the
|
|
26
|
+
test, or set `clearMocks: false` in the config to keep the old behavior.
|
|
27
|
+
|
|
28
|
+
**"Cannot call vi.mock inside ...".** `vi.mock`, `vi.unmock` and `vi.hoisted`
|
|
29
|
+
now throw when called outside the module top level. In v4 this only warned. Move
|
|
30
|
+
the call to the top level, or switch to `vi.doMock`.
|
|
31
|
+
|
|
32
|
+
**A test fails that used to pass silently.** An unawaited `resolves`, `rejects`
|
|
33
|
+
or `toMatchFileSnapshot` now fails the test instead of being ignored. Add the
|
|
34
|
+
missing `await`.
|
|
35
|
+
|
|
36
|
+
**`expect.poll` times out.** It now fails when the callback does not settle
|
|
37
|
+
within `timeout`, rather than hanging on the last value.
|
|
38
|
+
|
|
39
|
+
**A `toThrow('')` assertion changed meaning.** An empty string now matches any
|
|
40
|
+
message. Use `/^$/` to assert on an empty message.
|
|
41
|
+
|
|
42
|
+
**Snapshot names moved.** String values interpolated through `$` in
|
|
43
|
+
`test.for`/`test.each` titles are no longer wrapped in quotes, and truncation is
|
|
44
|
+
now controlled by `taskTitleValueFormatTruncate` (default 40).
|
|
45
|
+
|
|
46
|
+
**A CI filter matches nothing.** `-t` and `testNamePattern` now join name
|
|
47
|
+
segments with `' > '` instead of a space.
|
|
48
|
+
|
|
49
|
+
**Per-worker resources collide.** `VITEST_POOL_ID` and `VITEST_WORKER_ID` are
|
|
50
|
+
now 1-based. Any logic deriving a database name, port or directory from them
|
|
51
|
+
shifts by one.
|
|
52
|
+
|
|
53
|
+
**Vitest cannot find a config.** It no longer searches ancestor directories.
|
|
54
|
+
Pass `--config` explicitly, or give the directory its own config.
|
|
55
|
+
|
|
56
|
+
**Projects behave differently.** Inline projects now default to `extends: true`
|
|
57
|
+
and share the root Vite server (`sharedViteServer`). Set either to `false` if a
|
|
58
|
+
project needs its own resolution.
|
|
59
|
+
|
|
60
|
+
## Check these without running anything
|
|
61
|
+
|
|
62
|
+
- `sequential`, if the pre-pass flagged any. `test.sequential()` and
|
|
63
|
+
`describe.sequential()` are gone. When nothing makes the test concurrent, drop
|
|
64
|
+
the modifier: `test.sequential('a', ...)` becomes `test('a', ...)`. When it was
|
|
65
|
+
opting out of a concurrent suite or a concurrent setting in the config, it
|
|
66
|
+
becomes the `{ concurrent: false }` option. The same applies to a `sequential`
|
|
67
|
+
entry in a test's options object.
|
|
68
|
+
- `vitest/suite` and `vitest/runners` imports, if the pre-pass flagged any. Their
|
|
69
|
+
exports did not move as a group: `getCurrentSuite` and `createTaskCollector` are
|
|
70
|
+
static members of `TestRunner` (exported from `vitest`), `VitestTestRunner` is
|
|
71
|
+
exported from `vitest`, the `VitestRunner` type is in `vitest/runtime`, and
|
|
72
|
+
`NodeBenchmarkRunner`, `getBenchFn` and `getBenchOptions` went with the old
|
|
73
|
+
benchmark API. Rebind the use sites rather than repointing the import.
|
|
74
|
+
- `@vitest/runner` and `@vitest/ws-client` in `package.json`: neither is published
|
|
75
|
+
for v5. Their exports scattered. The hooks and `describe`/`it`/`test` come from
|
|
76
|
+
`vitest`, the collection helpers are now static members of `TestRunner`
|
|
77
|
+
(`TestRunner.getCurrentSuite()`, `TestRunner.getCurrentTest()`), and only the
|
|
78
|
+
`VitestRunner` type and the environment and snapshot helpers are in
|
|
79
|
+
`vitest/runtime`. `collectTests`, `startTests` and `updateTask` have no public
|
|
80
|
+
replacement.
|
|
81
|
+
- Benchmarks: the module-level `bench()` API is gone, along with
|
|
82
|
+
`benchmark.reporters`, `benchmark.outputFile`, `benchmark.compare` and the
|
|
83
|
+
`--compare` CLI flag. `bench` now comes off the test context.
|
|
84
|
+
- `coverage.thresholds` under a glob no longer inherits the top-level `perFile`.
|
|
85
|
+
Set it on each glob that needs it.
|
|
86
|
+
- Browser mode, if the workspace configures it by hand: locators are strict by
|
|
87
|
+
default, `toHaveTextContent` no longer accepts a RegExp (`toMatchTextContent`
|
|
88
|
+
does), browser commands receive a locator object rather than a selector string,
|
|
89
|
+
automocked modules return `undefined`, and `browser.api` moved to top-level
|
|
90
|
+
`api`.
|
|
91
|
+
|
|
92
|
+
The full list is at https://vitest.dev/guide/migration.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Tree } from '@nx/devkit';
|
|
2
|
+
/**
|
|
3
|
+
* Hybrid migration paired with `ai-instructions-for-vitest-5.md`. Applies the
|
|
4
|
+
* handful of Vitest 4 -> 5 changes that have one correct answer and forwards
|
|
5
|
+
* everything else to the paired prompt.
|
|
6
|
+
*
|
|
7
|
+
* The apply set is deliberately small: the rest of v5's breaking changes are
|
|
8
|
+
* runtime semantics (mock clearing, unawaited assertions, hoisting) that a
|
|
9
|
+
* test run surfaces far better than a codemod can guess at.
|
|
10
|
+
*/
|
|
11
|
+
export default function migrateToVitest5(tree: Tree): Promise<{
|
|
12
|
+
nextSteps?: string[];
|
|
13
|
+
agentContext?: string[];
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = migrateToVitest5;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const internal_1 = require("@nx/devkit/internal");
|
|
6
|
+
const ignore_vitest_temp_files_1 = require("../../utils/ignore-vitest-temp-files");
|
|
7
|
+
const semver_1 = require("semver");
|
|
8
|
+
const tsquery_1 = require("@phenomnomnominal/tsquery");
|
|
9
|
+
const TEST_CALLEES = new Set(['test', 'it', 'describe', 'suite', 'bench']);
|
|
10
|
+
/**
|
|
11
|
+
* Hybrid migration paired with `ai-instructions-for-vitest-5.md`. Applies the
|
|
12
|
+
* handful of Vitest 4 -> 5 changes that have one correct answer and forwards
|
|
13
|
+
* everything else to the paired prompt.
|
|
14
|
+
*
|
|
15
|
+
* The apply set is deliberately small: the rest of v5's breaking changes are
|
|
16
|
+
* runtime semantics (mock clearing, unawaited assertions, hoisting) that a
|
|
17
|
+
* test run surfaces far better than a codemod can guess at.
|
|
18
|
+
*/
|
|
19
|
+
async function migrateToVitest5(tree) {
|
|
20
|
+
const unhandled = [];
|
|
21
|
+
(0, ignore_vitest_temp_files_1.addVitestTempFilesToGitIgnore)(tree);
|
|
22
|
+
const addedVite = declareVitePeerDependency(tree);
|
|
23
|
+
(0, devkit_1.visitNotIgnoredFiles)(tree, '', (filePath) => {
|
|
24
|
+
if (!isJsOrTsFile(filePath))
|
|
25
|
+
return;
|
|
26
|
+
const contents = tree.read(filePath, 'utf-8');
|
|
27
|
+
if (!contents)
|
|
28
|
+
return;
|
|
29
|
+
const hasEntryPoints = contents.includes('vitest/');
|
|
30
|
+
const hasSequential = contents.includes('sequential');
|
|
31
|
+
if (!hasEntryPoints && !hasSequential)
|
|
32
|
+
return;
|
|
33
|
+
const sourceFile = (0, tsquery_1.ast)(contents);
|
|
34
|
+
if (hasEntryPoints) {
|
|
35
|
+
rewriteRemovedEntryPoints(tree, filePath, contents, sourceFile, unhandled);
|
|
36
|
+
}
|
|
37
|
+
if (hasSequential) {
|
|
38
|
+
detectSequentialUsage(filePath, sourceFile, unhandled);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
42
|
+
const result = {};
|
|
43
|
+
if (addedVite) {
|
|
44
|
+
result.nextSteps = [
|
|
45
|
+
`Added \`vite\` to the root \`package.json\` devDependencies, matching the version already resolved in the workspace. Vitest 4 depended on Vite directly; Vitest 5 only declares it as a required peer, so a workspace that relied on the transitive copy has none after the upgrade.`,
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
if (unhandled.length > 0)
|
|
49
|
+
result.agentContext = unhandled;
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
// Fallback only. The workspace's own resolved vite is preferred, since moving
|
|
53
|
+
// a 6.4 or 7.x workspace to 8 is a bundler major riding inside a test upgrade.
|
|
54
|
+
const FALLBACK_VITE_VERSION = '^8.0.0';
|
|
55
|
+
/**
|
|
56
|
+
* Vitest 4 listed `vite` in `dependencies`, so workspaces that only ran tests
|
|
57
|
+
* never had to declare it. Vitest 5 dropped it to a required peer.
|
|
58
|
+
*/
|
|
59
|
+
function declareVitePeerDependency(tree) {
|
|
60
|
+
if (!tree.exists('package.json'))
|
|
61
|
+
return false;
|
|
62
|
+
const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
|
|
63
|
+
const hasVitest = packageJson.dependencies?.['vitest'] ??
|
|
64
|
+
packageJson.devDependencies?.['vitest'];
|
|
65
|
+
if (!hasVitest)
|
|
66
|
+
return false;
|
|
67
|
+
if (packageJson.dependencies?.['vite'] ??
|
|
68
|
+
packageJson.devDependencies?.['vite'])
|
|
69
|
+
return false;
|
|
70
|
+
const installedVite = (0, internal_1.getInstalledPackageVersion)('vite');
|
|
71
|
+
(0, devkit_1.updateJson)(tree, 'package.json', (json) => {
|
|
72
|
+
json.devDependencies ??= {};
|
|
73
|
+
json.devDependencies['vite'] = installedVite
|
|
74
|
+
? `^${(0, semver_1.major)(installedVite)}.0.0`
|
|
75
|
+
: FALLBACK_VITE_VERSION;
|
|
76
|
+
return json;
|
|
77
|
+
});
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
const TS_JS_RE = /\.[cm]?[jt]sx?$/;
|
|
81
|
+
function isJsOrTsFile(filePath) {
|
|
82
|
+
return TS_JS_RE.test(filePath);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* v5 removed the deprecated deep entry points. A specifier is only rewritten
|
|
86
|
+
* when every one of its v4 exports landed in a single v5 module; the rest are
|
|
87
|
+
* described for the agent, because their exports scattered or disappeared.
|
|
88
|
+
*/
|
|
89
|
+
const REMOVED_ENTRY_POINTS = {
|
|
90
|
+
'vitest/reporters': {
|
|
91
|
+
replacement: 'vitest/node',
|
|
92
|
+
note: 'its reporter exports are now in `vitest/node`',
|
|
93
|
+
},
|
|
94
|
+
'vitest/coverage': {
|
|
95
|
+
replacement: 'vitest/node',
|
|
96
|
+
note: '`BaseCoverageProvider` is now in `vitest/node`',
|
|
97
|
+
},
|
|
98
|
+
'vitest/snapshot': {
|
|
99
|
+
replacement: 'vitest/runtime',
|
|
100
|
+
note: 'its snapshot exports are now in `vitest/runtime`',
|
|
101
|
+
},
|
|
102
|
+
'vitest/environments': {
|
|
103
|
+
replacement: 'vitest/runtime',
|
|
104
|
+
note: 'its environment exports are now in `vitest/runtime`',
|
|
105
|
+
},
|
|
106
|
+
'vitest/suite': {
|
|
107
|
+
note: 'its exports did not survive as module exports. `getCurrentSuite` and `createTaskCollector` are static members of `TestRunner` (exported from `vitest`), and the rest were removed. Rebind the use sites rather than repointing the import',
|
|
108
|
+
},
|
|
109
|
+
'vitest/runners': {
|
|
110
|
+
note: 'its exports split up. `VitestTestRunner` is exported from `vitest`, `VitestRunner` from `vitest/runtime`, and `NodeBenchmarkRunner` was removed with the old benchmark API',
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Exports the v5 benchmark rewrite removed outright. A file importing one of
|
|
115
|
+
* these from `vitest/reporters` cannot simply be repointed.
|
|
116
|
+
*/
|
|
117
|
+
const REMOVED_SYMBOLS = new Set([
|
|
118
|
+
'BenchmarkBuiltinReporters',
|
|
119
|
+
'BenchmarkReporter',
|
|
120
|
+
'BenchmarkReportsMap',
|
|
121
|
+
'VerboseBenchmarkReporter',
|
|
122
|
+
'NodeBenchmarkRunner',
|
|
123
|
+
]);
|
|
124
|
+
/**
|
|
125
|
+
* Only import and export declarations are rewritten. A removed specifier
|
|
126
|
+
* reached any other way still has to go, so name it for the agent.
|
|
127
|
+
*/
|
|
128
|
+
function reportUnrewritableSpecifiers(sourceFile, moduleSpecifiers, filePath, unhandled) {
|
|
129
|
+
const specifierStarts = new Set(moduleSpecifiers.map((node) => node.getStart(sourceFile)));
|
|
130
|
+
const stragglers = new Set((0, tsquery_1.query)(sourceFile, 'StringLiteral')
|
|
131
|
+
.filter((node) => node.text in REMOVED_ENTRY_POINTS &&
|
|
132
|
+
!specifierStarts.has(node.getStart(sourceFile)))
|
|
133
|
+
.map((node) => node.text));
|
|
134
|
+
for (const specifier of stragglers) {
|
|
135
|
+
unhandled.push(`${filePath} references \`${specifier}\` outside an import or export declaration. Vitest 5 removed it: ${REMOVED_ENTRY_POINTS[specifier].note}.`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function rewriteRemovedEntryPoints(tree, filePath, contents, sourceFile, unhandled) {
|
|
139
|
+
const specifiers = (0, tsquery_1.query)(sourceFile, 'ImportDeclaration > StringLiteral, ExportDeclaration > StringLiteral');
|
|
140
|
+
reportUnrewritableSpecifiers(sourceFile, specifiers, filePath, unhandled);
|
|
141
|
+
const opaqueStarts = new Set((0, tsquery_1.query)(sourceFile, OPAQUE_DECLARATIONS).map((node) => node.getStart(sourceFile)));
|
|
142
|
+
let updated = contents;
|
|
143
|
+
let changed = false;
|
|
144
|
+
// Right-to-left so earlier offsets stay valid as the text shifts.
|
|
145
|
+
for (const specifier of [...specifiers].reverse()) {
|
|
146
|
+
const entryPoint = REMOVED_ENTRY_POINTS[specifier.text];
|
|
147
|
+
if (!entryPoint)
|
|
148
|
+
continue;
|
|
149
|
+
const blocker = whyNotRewritable(specifier, entryPoint, opaqueStarts, sourceFile);
|
|
150
|
+
if (blocker) {
|
|
151
|
+
unhandled.push(`${filePath} imports from \`${specifier.text}\`, which Vitest 5 removed: ${blocker}.`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
updated =
|
|
155
|
+
updated.slice(0, specifier.getStart(sourceFile) + 1) +
|
|
156
|
+
entryPoint.replacement +
|
|
157
|
+
updated.slice(specifier.getEnd() - 1);
|
|
158
|
+
changed = true;
|
|
159
|
+
}
|
|
160
|
+
if (changed)
|
|
161
|
+
tree.write(filePath, updated);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Declarations that take a whole module instead of naming what they use, so
|
|
165
|
+
* there is no symbol list to check against the ones Vitest 5 removed.
|
|
166
|
+
*/
|
|
167
|
+
const OPAQUE_DECLARATIONS = [
|
|
168
|
+
'ImportDeclaration:has(NamespaceImport) > StringLiteral',
|
|
169
|
+
'ExportDeclaration:has(NamespaceExport) > StringLiteral',
|
|
170
|
+
'ImportDeclaration:has(ImportClause > Identifier) > StringLiteral',
|
|
171
|
+
'ExportDeclaration:not(:has(ExportSpecifier)) > StringLiteral',
|
|
172
|
+
].join(', ');
|
|
173
|
+
/** The reason this specifier cannot be repointed, if there is one. */
|
|
174
|
+
function whyNotRewritable(specifier, entryPoint, opaqueStarts, sourceFile) {
|
|
175
|
+
if (!entryPoint.replacement) {
|
|
176
|
+
return entryPoint.note;
|
|
177
|
+
}
|
|
178
|
+
if (opaqueStarts.has(specifier.getStart(sourceFile))) {
|
|
179
|
+
return `${entryPoint.note}, but this declaration takes the whole module rather than naming what it uses, so the symbols cannot be checked here`;
|
|
180
|
+
}
|
|
181
|
+
const removed = boundNamesOf(specifier).filter((name) => REMOVED_SYMBOLS.has(name));
|
|
182
|
+
return removed.length
|
|
183
|
+
? `\`${removed.join('`, `')}\` no longer exists anywhere, it went with the old benchmark API`
|
|
184
|
+
: undefined;
|
|
185
|
+
}
|
|
186
|
+
function boundNamesOf(specifier) {
|
|
187
|
+
return (0, tsquery_1.query)(specifier.parent, 'ImportSpecifier, ExportSpecifier').map((element) => (element.propertyName ?? element.name).text);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* `test.sequential()` / `describe.sequential()` and the `sequential` test
|
|
191
|
+
* option are gone in v5. The replacement depends on whether the test inherits
|
|
192
|
+
* concurrency from a suite or the config, so the agent decides rather than the
|
|
193
|
+
* codemod.
|
|
194
|
+
*/
|
|
195
|
+
function detectSequentialUsage(filePath, sourceFile, unhandled) {
|
|
196
|
+
// Both AST forms of a property key, and only in an options object handed to
|
|
197
|
+
// a test or suite call. A loose `{ sequential: true }` is someone else's API.
|
|
198
|
+
const optionKeys = (0, tsquery_1.query)(sourceFile, 'PropertyAssignment > :matches(Identifier[name=sequential], StringLiteral[value=sequential])').filter((key) => {
|
|
199
|
+
// PropertyAssignment < ObjectLiteralExpression < CallExpression.
|
|
200
|
+
const call = key.parent?.parent?.parent;
|
|
201
|
+
return (!!call &&
|
|
202
|
+
'arguments' in call &&
|
|
203
|
+
TEST_CALLEES.has(call.expression.getText(sourceFile).split('.')[0]));
|
|
204
|
+
});
|
|
205
|
+
const modifiers = (0, tsquery_1.query)(sourceFile, 'PropertyAccessExpression:has(Identifier[name=sequential])').filter((node) => node.name.text === 'sequential' &&
|
|
206
|
+
TEST_CALLEES.has(node.expression.getText(sourceFile)));
|
|
207
|
+
const usages = [...optionKeys, ...modifiers];
|
|
208
|
+
if (usages.length === 0)
|
|
209
|
+
return;
|
|
210
|
+
unhandled.push(`${filePath} uses \`sequential\`, which Vitest 5 removed. Replace \`test.sequential(...)\` / \`describe.sequential(...)\` with a plain \`test(...)\` / \`describe(...)\` when nothing makes them concurrent, and with the \`{ concurrent: false }\` option when they are opting out of a concurrent suite or a concurrent setting in the config.`);
|
|
211
|
+
}
|
|
@@ -442,7 +442,7 @@ function normalizeOutputPath(outputPath, projectRoot, workspaceRoot, path) {
|
|
|
442
442
|
}
|
|
443
443
|
else {
|
|
444
444
|
if ((0, node_path_1.isAbsolute)(outputPath)) {
|
|
445
|
-
return `{workspaceRoot}/${(0, node_path_1.relative)(workspaceRoot, outputPath)}`;
|
|
445
|
+
return `{workspaceRoot}/${(0, devkit_1.normalizePath)((0, node_path_1.relative)(workspaceRoot, outputPath))}`;
|
|
446
446
|
}
|
|
447
447
|
else {
|
|
448
448
|
if (outputPath.startsWith('..')) {
|
|
@@ -22,10 +22,6 @@ async function ensureDependencies(tree, schema) {
|
|
|
22
22
|
else if (schema.testEnvironment !== 'node' && schema.testEnvironment) {
|
|
23
23
|
devkit_1.logger.info(`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`);
|
|
24
24
|
}
|
|
25
|
-
if (schema.uiFramework === 'angular') {
|
|
26
|
-
devDependencies['@analogjs/vitest-angular'] = versions_1.analogVitestAngular;
|
|
27
|
-
devDependencies['@analogjs/vite-plugin-angular'] = versions_1.analogVitestAngular;
|
|
28
|
-
}
|
|
29
25
|
if (schema.uiFramework === 'react') {
|
|
30
26
|
if (schema.compiler === 'swc') {
|
|
31
27
|
// @vitejs/plugin-react-swc depends on @swc/core.
|
|
@@ -10,14 +10,19 @@ async function ignoreVitestTempFiles(tree, projectRoot) {
|
|
|
10
10
|
await ignoreVitestTempFilesInEslintConfig(tree, projectRoot);
|
|
11
11
|
}
|
|
12
12
|
function addVitestTempFilesToGitIgnore(tree) {
|
|
13
|
-
|
|
13
|
+
const contents = tree.exists('.gitignore')
|
|
14
14
|
? tree.read('.gitignore', 'utf-8')
|
|
15
15
|
: '';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
// Vitest 5 writes attachments, blob reports, failure screenshots and the
|
|
17
|
+
// json/junit reporter output to `.vitest`.
|
|
18
|
+
const additions = ['vitest.config.*.timestamp*', '.vitest'].filter((entry) => !new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm').test(contents));
|
|
19
|
+
if (!additions.length) {
|
|
20
|
+
return;
|
|
19
21
|
}
|
|
20
|
-
|
|
22
|
+
// Plain concatenation rather than a template: `stripIndents` would rewrite
|
|
23
|
+
// every existing line of the user's file.
|
|
24
|
+
const separator = !contents || contents.endsWith('\n') ? '' : '\n';
|
|
25
|
+
tree.write('.gitignore', `${contents}${separator}${additions.join('\n')}\n`);
|
|
21
26
|
}
|
|
22
27
|
async function ignoreVitestTempFilesInEslintConfig(tree, projectRoot) {
|
|
23
28
|
// Checked before `ensurePackage` so an Oxlint workspace does not install
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type Tree } from '@nx/devkit';
|
|
2
|
-
export declare function getInstalledViteVersion(tree: Tree): string;
|
|
2
|
+
export declare function getInstalledViteVersion(tree: Tree): string | undefined;
|
|
3
3
|
export declare function getInstalledViteMajorVersion(tree: Tree): 5 | 6 | 7 | 8 | undefined;
|
|
@@ -4,15 +4,14 @@ exports.getInstalledViteVersion = getInstalledViteVersion;
|
|
|
4
4
|
exports.getInstalledViteMajorVersion = getInstalledViteMajorVersion;
|
|
5
5
|
const devkit_1 = require("@nx/devkit");
|
|
6
6
|
const semver_1 = require("semver");
|
|
7
|
-
const versions_1 = require("./versions");
|
|
8
7
|
function getInstalledViteVersion(tree) {
|
|
9
8
|
const installedViteVersion = (0, devkit_1.getDependencyVersionFromPackageJson)(tree, 'vite');
|
|
10
9
|
if (!installedViteVersion ||
|
|
11
10
|
installedViteVersion === 'latest' ||
|
|
12
11
|
installedViteVersion === 'beta') {
|
|
13
|
-
return
|
|
12
|
+
return undefined;
|
|
14
13
|
}
|
|
15
|
-
return (0, semver_1.clean)(installedViteVersion) ?? (0, semver_1.coerce)(installedViteVersion)
|
|
14
|
+
return (0, semver_1.clean)(installedViteVersion) ?? (0, semver_1.coerce)(installedViteVersion)?.version;
|
|
16
15
|
}
|
|
17
16
|
function getInstalledViteMajorVersion(tree) {
|
|
18
17
|
const installedViteVersion = getInstalledViteVersion(tree);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type Tree } from '@nx/devkit';
|
|
2
2
|
export declare const nxVersion: any;
|
|
3
3
|
export declare const minSupportedVitestVersion = "3.0.0";
|
|
4
|
-
export declare const vitestVersion = "~
|
|
5
|
-
export declare const vitestCoverageV8Version = "~
|
|
6
|
-
export declare const vitestCoverageIstanbulVersion = "~
|
|
4
|
+
export declare const vitestVersion = "~5.0.1";
|
|
5
|
+
export declare const vitestCoverageV8Version = "~5.0.1";
|
|
6
|
+
export declare const vitestCoverageIstanbulVersion = "~5.0.1";
|
|
7
7
|
export declare const viteVersion = "^8.0.0";
|
|
8
8
|
export declare const viteV7Version = "^7.0.0";
|
|
9
9
|
export declare const viteV6Version = "^6.0.0";
|
|
@@ -18,12 +18,16 @@ export declare const happyDomVersion = "^20.10.4";
|
|
|
18
18
|
export declare const edgeRuntimeVmVersion = "~3.0.2";
|
|
19
19
|
export declare const jitiVersion = "2.4.2";
|
|
20
20
|
export declare const analogVitestAngular = "~2.6.0";
|
|
21
|
+
export type VersionSelectionOptions = {
|
|
22
|
+
/** Vite range about to be installed, when the workspace declares none yet. */
|
|
23
|
+
viteRange?: string;
|
|
24
|
+
};
|
|
21
25
|
type VitestVersions = {
|
|
22
26
|
vitestVersion: string;
|
|
23
27
|
vitestCoverageV8Version: string;
|
|
24
28
|
vitestCoverageIstanbulVersion: string;
|
|
25
29
|
};
|
|
26
|
-
export declare function versions(tree: Tree): VitestVersions;
|
|
30
|
+
export declare function versions(tree: Tree, options?: VersionSelectionOptions): VitestVersions;
|
|
27
31
|
export declare function getInstalledVitestVersion(tree?: Tree): string | null;
|
|
28
32
|
export declare function getInstalledVitestMajorVersion(tree?: Tree): number | null;
|
|
29
33
|
export {};
|
|
@@ -10,9 +10,9 @@ const path_1 = require("path");
|
|
|
10
10
|
const semver_1 = require("semver");
|
|
11
11
|
exports.nxVersion = require((0, path_1.join)('@nx/vitest', 'package.json')).version;
|
|
12
12
|
exports.minSupportedVitestVersion = '3.0.0';
|
|
13
|
-
exports.vitestVersion = '~
|
|
14
|
-
exports.vitestCoverageV8Version = '~
|
|
15
|
-
exports.vitestCoverageIstanbulVersion = '~
|
|
13
|
+
exports.vitestVersion = '~5.0.1';
|
|
14
|
+
exports.vitestCoverageV8Version = '~5.0.1';
|
|
15
|
+
exports.vitestCoverageIstanbulVersion = '~5.0.1';
|
|
16
16
|
exports.viteVersion = '^8.0.0';
|
|
17
17
|
exports.viteV7Version = '^7.0.0';
|
|
18
18
|
exports.viteV6Version = '^6.0.0';
|
|
@@ -27,25 +27,63 @@ exports.happyDomVersion = '^20.10.4';
|
|
|
27
27
|
exports.edgeRuntimeVmVersion = '~3.0.2';
|
|
28
28
|
exports.jitiVersion = '2.4.2';
|
|
29
29
|
exports.analogVitestAngular = '~2.6.0';
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
// Highest major first. Vitest 3 declares no vite peer at all, so it accepts
|
|
31
|
+
// anything and terminates the search.
|
|
32
|
+
const VITEST_COMPAT = [
|
|
33
|
+
{
|
|
34
|
+
major: 5,
|
|
35
|
+
minVite: '6.4.0',
|
|
36
|
+
versions: {
|
|
37
|
+
vitestVersion: exports.vitestVersion,
|
|
38
|
+
vitestCoverageV8Version: exports.vitestCoverageV8Version,
|
|
39
|
+
vitestCoverageIstanbulVersion: exports.vitestCoverageIstanbulVersion,
|
|
40
|
+
},
|
|
40
41
|
},
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
{
|
|
43
|
+
major: 4,
|
|
44
|
+
minVite: '6.0.0',
|
|
45
|
+
versions: {
|
|
46
|
+
vitestVersion: '^4.0.0',
|
|
47
|
+
vitestCoverageV8Version: '^4.0.0',
|
|
48
|
+
vitestCoverageIstanbulVersion: '^4.0.0',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
major: 3,
|
|
53
|
+
minVite: '0.0.0',
|
|
54
|
+
versions: {
|
|
55
|
+
vitestVersion: '^3.0.0',
|
|
56
|
+
vitestCoverageV8Version: '^3.0.5',
|
|
57
|
+
vitestCoverageIstanbulVersion: '^3.0.5',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
function versions(tree, options) {
|
|
62
|
+
const cap = highestMajorTheWorkspacePeersAllow(tree);
|
|
43
63
|
const installedVitestVersion = getInstalledVitestVersion(tree);
|
|
44
|
-
if (
|
|
45
|
-
|
|
64
|
+
if (installedVitestVersion) {
|
|
65
|
+
const installedMajor = (0, semver_1.major)(installedVitestVersion);
|
|
66
|
+
if (installedMajor > cap) {
|
|
67
|
+
throw new Error(`The installed vitest version "${installedVitestVersion}" is not compatible with this workspace, which has packages that peer vitest ${cap} and below. Pin vitest to ^${cap}.0.0 first.`);
|
|
68
|
+
}
|
|
69
|
+
return (VITEST_COMPAT.find((compat) => compat.major === installedMajor)
|
|
70
|
+
?.versions ?? VITEST_COMPAT[0].versions);
|
|
46
71
|
}
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
// The manifest wins over the caller's range: it carries the minor a
|
|
73
|
+
// major-derived range would drop, and it is what `keepExistingVersions` keeps.
|
|
74
|
+
const viteRange = (0, devkit_1.getDependencyVersionFromPackageJson)(tree, 'vite') ?? options?.viteRange;
|
|
75
|
+
const viteFloor = viteRange ? (0, semver_1.coerce)(viteRange)?.version : undefined;
|
|
76
|
+
const supported = VITEST_COMPAT.find((compat) => compat.major <= cap && (!viteFloor || (0, semver_1.gte)(viteFloor, compat.minVite)));
|
|
77
|
+
return (supported ?? VITEST_COMPAT[0]).versions;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `@analogjs/vitest-angular` at the version Nx installs, and `@angular/build`,
|
|
81
|
+
* both peer vitest 4. A workspace carrying either cannot take vitest 5.
|
|
82
|
+
*/
|
|
83
|
+
function highestMajorTheWorkspacePeersAllow(tree) {
|
|
84
|
+
return (0, devkit_1.getDependencyVersionFromPackageJson)(tree, '@analogjs/vitest-angular') || (0, devkit_1.getDependencyVersionFromPackageJson)(tree, '@angular/build')
|
|
85
|
+
? 4
|
|
86
|
+
: 5;
|
|
49
87
|
}
|
|
50
88
|
function getInstalledVitestVersion(tree) {
|
|
51
89
|
if (!tree) {
|
package/migrations.json
CHANGED
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
"description": "Replace `__dirname` with `import.meta.dirname` in `vite.config.mts`/`vitest.config.mts` files so they work with Vite's `configLoader: 'native'`.",
|
|
35
35
|
"implementation": "./dist/src/migrations/update-23-2-0/use-import-meta-dirname",
|
|
36
36
|
"documentation": "./dist/src/migrations/update-23-2-0/use-import-meta-dirname.md"
|
|
37
|
+
},
|
|
38
|
+
"migrate-to-vitest-5": {
|
|
39
|
+
"version": "23.3.0-beta.8",
|
|
40
|
+
"requires": {
|
|
41
|
+
"vitest": ">=5.0.0"
|
|
42
|
+
},
|
|
43
|
+
"description": "Migrate workspaces past breaking changes for Vitest 5.",
|
|
44
|
+
"implementation": "./dist/src/migrations/update-23-3-0/migrate-to-vitest-5",
|
|
45
|
+
"prompt": "./dist/src/migrations/update-23-3-0/ai-instructions-for-vitest-5.md"
|
|
37
46
|
}
|
|
38
47
|
},
|
|
39
48
|
"packageJsonUpdates": {
|
|
@@ -140,6 +149,86 @@
|
|
|
140
149
|
"alwaysAddToPackageJson": false
|
|
141
150
|
}
|
|
142
151
|
}
|
|
152
|
+
},
|
|
153
|
+
"23.3.0-vitest-v5": {
|
|
154
|
+
"version": "23.3.0-beta.8",
|
|
155
|
+
"requires": {
|
|
156
|
+
"vitest": ">=4.0.0 <5.0.0"
|
|
157
|
+
},
|
|
158
|
+
"incompatibleWith": {
|
|
159
|
+
"vite": "<6.4.0",
|
|
160
|
+
"@analogjs/vitest-angular": "*",
|
|
161
|
+
"@angular/build": "*",
|
|
162
|
+
"@vitest/browser-playwright": "*",
|
|
163
|
+
"@vitest/browser-preview": "*",
|
|
164
|
+
"@vitest/browser-webdriverio": "*"
|
|
165
|
+
},
|
|
166
|
+
"packages": {
|
|
167
|
+
"vitest": {
|
|
168
|
+
"version": "^5.0.0",
|
|
169
|
+
"alwaysAddToPackageJson": false
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"23.3.0-vitest-v5-coverage-v8": {
|
|
174
|
+
"version": "23.3.0-beta.8",
|
|
175
|
+
"requires": {
|
|
176
|
+
"@vitest/coverage-v8": ">=4.0.0 <5.0.0"
|
|
177
|
+
},
|
|
178
|
+
"incompatibleWith": {
|
|
179
|
+
"vite": "<6.4.0",
|
|
180
|
+
"@analogjs/vitest-angular": "*",
|
|
181
|
+
"@angular/build": "*",
|
|
182
|
+
"@vitest/browser-playwright": "*",
|
|
183
|
+
"@vitest/browser-preview": "*",
|
|
184
|
+
"@vitest/browser-webdriverio": "*"
|
|
185
|
+
},
|
|
186
|
+
"packages": {
|
|
187
|
+
"@vitest/coverage-v8": {
|
|
188
|
+
"version": "^5.0.0",
|
|
189
|
+
"alwaysAddToPackageJson": false
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"23.3.0-vitest-v5-coverage-istanbul": {
|
|
194
|
+
"version": "23.3.0-beta.8",
|
|
195
|
+
"requires": {
|
|
196
|
+
"@vitest/coverage-istanbul": ">=4.0.0 <5.0.0"
|
|
197
|
+
},
|
|
198
|
+
"incompatibleWith": {
|
|
199
|
+
"vite": "<6.4.0",
|
|
200
|
+
"@analogjs/vitest-angular": "*",
|
|
201
|
+
"@angular/build": "*",
|
|
202
|
+
"@vitest/browser-playwright": "*",
|
|
203
|
+
"@vitest/browser-preview": "*",
|
|
204
|
+
"@vitest/browser-webdriverio": "*"
|
|
205
|
+
},
|
|
206
|
+
"packages": {
|
|
207
|
+
"@vitest/coverage-istanbul": {
|
|
208
|
+
"version": "^5.0.0",
|
|
209
|
+
"alwaysAddToPackageJson": false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"23.3.0-vitest-v5-ui": {
|
|
214
|
+
"version": "23.3.0-beta.8",
|
|
215
|
+
"requires": {
|
|
216
|
+
"@vitest/ui": ">=4.0.0 <5.0.0"
|
|
217
|
+
},
|
|
218
|
+
"incompatibleWith": {
|
|
219
|
+
"vite": "<6.4.0",
|
|
220
|
+
"@analogjs/vitest-angular": "*",
|
|
221
|
+
"@angular/build": "*",
|
|
222
|
+
"@vitest/browser-playwright": "*",
|
|
223
|
+
"@vitest/browser-preview": "*",
|
|
224
|
+
"@vitest/browser-webdriverio": "*"
|
|
225
|
+
},
|
|
226
|
+
"packages": {
|
|
227
|
+
"@vitest/ui": {
|
|
228
|
+
"version": "^5.0.0",
|
|
229
|
+
"alwaysAddToPackageJson": false
|
|
230
|
+
}
|
|
231
|
+
}
|
|
143
232
|
}
|
|
144
233
|
}
|
|
145
234
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/vitest",
|
|
3
3
|
"description": "The Nx Plugin for Vitest to enable fast unit testing with Vitest.",
|
|
4
|
-
"version": "23.3.0-beta.
|
|
4
|
+
"version": "23.3.0-beta.8",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -78,14 +78,14 @@
|
|
|
78
78
|
"executors": "./executors.json",
|
|
79
79
|
"generators": "./generators.json",
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@nx/devkit": "23.3.0-beta.
|
|
82
|
-
"@nx/js": "23.3.0-beta.
|
|
81
|
+
"@nx/devkit": "23.3.0-beta.8",
|
|
82
|
+
"@nx/js": "23.3.0-beta.8",
|
|
83
83
|
"tslib": "^2.3.0",
|
|
84
84
|
"semver": "^7.6.3",
|
|
85
85
|
"@phenomnomnominal/tsquery": "~6.2.0"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
|
-
"@nx/eslint": "23.3.0-beta.
|
|
88
|
+
"@nx/eslint": "23.3.0-beta.8",
|
|
89
89
|
"vitest": "^3.0.0 || ^4.0.0 || ^5.0.0",
|
|
90
90
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
91
91
|
},
|
|
@@ -101,6 +101,6 @@
|
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"nx": "23.3.0-beta.
|
|
104
|
+
"nx": "23.3.0-beta.8"
|
|
105
105
|
}
|
|
106
106
|
}
|