nuxt-vitest-shared-app 0.1.0 → 0.1.1
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 +30 -31
- package/dist/runtime/lifecycle.mjs +6 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Tests that need plugins re-initialized or a different startup config belong in a
|
|
|
15
15
|
- [The problem](#the-problem)
|
|
16
16
|
- [Install](#install)
|
|
17
17
|
- [Quick start](#quick-start)
|
|
18
|
-
- [
|
|
18
|
+
- [Switching from per-file startup](#switching-from-per-file-startup)
|
|
19
19
|
- [Per-test overrides](#per-test-overrides)
|
|
20
20
|
- [Project resets](#project-resets)
|
|
21
21
|
- [Worker state](#worker-state)
|
|
@@ -64,9 +64,8 @@ import { defineSharedNuxtVitestConfig } from "nuxt-vitest-shared-app/config"
|
|
|
64
64
|
|
|
65
65
|
export default defineSharedNuxtVitestConfig({
|
|
66
66
|
test: {
|
|
67
|
-
include: ["
|
|
67
|
+
include: ["test/nuxt/**/*.test.ts"],
|
|
68
68
|
environment: "nuxt",
|
|
69
|
-
pool: "threads",
|
|
70
69
|
isolate: false,
|
|
71
70
|
},
|
|
72
71
|
})
|
|
@@ -92,24 +91,23 @@ defineSharedNuxtVitestConfig(config, { silenceSuspenseInfo: true })
|
|
|
92
91
|
|
|
93
92
|
With `silenceSuspenseInfo: true`, Vue's `<Suspense> is an experimental feature and its API will likely change.` message, which every `mountSuspended` call prints, is filtered through `test.onConsoleLog`; your own `onConsoleLog` still receives all other messages.
|
|
94
93
|
|
|
95
|
-
##
|
|
94
|
+
## Switching from per-file startup
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
With plain `@nuxt/test-utils`, every test file starts from fresh modules and globals.
|
|
97
|
+
With a shared app, modules stay cached for the following files of the worker, so objects created at module level live for the whole environment.
|
|
98
|
+
Tests that passed with per-file startup may rely on that implicitly and start to depend on the order of files.
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- local patches or workarounds that start Nuxt once per worker.
|
|
102
|
-
|
|
103
|
-
Keep project-specific cleanup, and move hooks that must run after every test into [project resets](#project-resets).
|
|
104
|
-
|
|
105
|
-
Because modules stay cached, objects created at module level live for the whole environment, not for one file.
|
|
106
|
-
A global replaced by one test file affects every file that runs after it in the worker, including objects that other modules already created from the previous global.
|
|
100
|
+
A typical case is a global replaced by one test file.
|
|
101
|
+
It affects every file that runs after it in the worker, including objects that other modules already created from the previous global.
|
|
107
102
|
For example, a test file that imports a forced `Intl` polyfill replaces the global constructor, while a formatter created earlier at module level still belongs to the previous implementation.
|
|
108
103
|
|
|
109
104
|
- Install global polyfills once in a project setup file, before components are imported, and prefer polyfills that keep an existing implementation.
|
|
110
105
|
- Do not replace global constructors with side-effect imports in individual test files.
|
|
111
106
|
- Review module-level objects that depend on such globals, such as formatters, clients, and caches.
|
|
112
|
-
-
|
|
107
|
+
- Run the suite on one worker with shuffled files, so all files share one environment in a different order, e.g. `vitest run --maxWorkers=1 --sequence.shuffle.files --sequence.seed=1`.
|
|
108
|
+
|
|
109
|
+
Remove `enableAutoUnmount` calls from project setup files: the library installs its own, and `@vue/test-utils` throws on a second call.
|
|
110
|
+
Hooks that clean up what the library [cleans up](#cleanup) keep working, and you can remove them.
|
|
113
111
|
|
|
114
112
|
## Per-test overrides
|
|
115
113
|
|
|
@@ -123,13 +121,13 @@ Register overridable imports in a project setup file:
|
|
|
123
121
|
export default defineSharedNuxtVitestConfig({
|
|
124
122
|
test: {
|
|
125
123
|
// ...
|
|
126
|
-
setupFiles: ["./
|
|
124
|
+
setupFiles: ["./test/nuxt/setup.ts"],
|
|
127
125
|
},
|
|
128
126
|
})
|
|
129
127
|
```
|
|
130
128
|
|
|
131
129
|
```ts
|
|
132
|
-
//
|
|
130
|
+
// test/nuxt/setup.ts
|
|
133
131
|
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
|
|
134
132
|
import { overridableNuxtImport } from "nuxt-vitest-shared-app"
|
|
135
133
|
|
|
@@ -174,7 +172,7 @@ Use a fallback for behavior that the app needs while it starts.
|
|
|
174
172
|
First add `useUser` and its type to `TestNuxtImports`, as in the setup file above, then:
|
|
175
173
|
|
|
176
174
|
```ts
|
|
177
|
-
//
|
|
175
|
+
// test/nuxt/setup.ts
|
|
178
176
|
mockNuxtImport(
|
|
179
177
|
"useUser",
|
|
180
178
|
overridableNuxtImport("useUser", () => ref(null)),
|
|
@@ -218,11 +216,11 @@ It requires `useRoute` in `TestNuxtImports` and an overridable `useRoute` mock.
|
|
|
218
216
|
registerNuxtTestReset(key: string, reset: () => unknown): () => void
|
|
219
217
|
```
|
|
220
218
|
|
|
221
|
-
Registers a reset that runs after each test, see step
|
|
219
|
+
Registers a reset that runs after each test, see step 7 of the [cleanup](#cleanup).
|
|
222
220
|
Use it for application resources that the library does not know about, such as test API clients, subscriptions, and caches:
|
|
223
221
|
|
|
224
222
|
```ts
|
|
225
|
-
//
|
|
223
|
+
// test/nuxt/setup.ts
|
|
226
224
|
import { registerNuxtTestReset } from "nuxt-vitest-shared-app"
|
|
227
225
|
|
|
228
226
|
import { testApiClient } from "./api-client"
|
|
@@ -303,20 +301,21 @@ In such files, `overridableNuxtImport` factories return the fallback or the orig
|
|
|
303
301
|
After each test, the library runs these steps in order.
|
|
304
302
|
They run after all `afterEach` hooks of the project, also when one of these hooks fails.
|
|
305
303
|
|
|
306
|
-
1.
|
|
307
|
-
2. `
|
|
308
|
-
3.
|
|
309
|
-
4.
|
|
310
|
-
5.
|
|
311
|
-
6.
|
|
312
|
-
7.
|
|
313
|
-
8. `
|
|
314
|
-
9.
|
|
304
|
+
1. `vi.clearAllTimers()` and `vi.useRealTimers()`, so the following steps and project resets run under real timers and the test does not need to restore them;
|
|
305
|
+
2. unmounts wrappers mounted in the test through `@vue/test-utils` (including `mountSuspended`), using `enableAutoUnmount`;
|
|
306
|
+
3. `await nextTick()` and `await flushPromises()`;
|
|
307
|
+
4. navigates the app's own router back to the route baseline with `router.replace` if the route differs, and verifies the resulting route;
|
|
308
|
+
5. clears import overrides;
|
|
309
|
+
6. `await clearError()`, `clearNuxtData()`, `clearNuxtState(undefined, { reset: false })`;
|
|
310
|
+
7. runs project resets registered with `registerNuxtTestReset`, in registration order, awaiting each;
|
|
311
|
+
8. `localStorage.clear()`, `sessionStorage.clear()`, and expires every cookie visible in `document.cookie` for path `/` of the current origin;
|
|
312
|
+
9. `vi.unstubAllEnvs()` and `vi.unstubAllGlobals()`;
|
|
313
|
+
10. removes direct children of `document.body` that were not present at startup.
|
|
315
314
|
|
|
316
315
|
The exact scope:
|
|
317
316
|
|
|
318
317
|
- `flushPromises` only settles handlers of promises that are already resolved; subscriptions, requests, and other background work need their own project resets;
|
|
319
|
-
- step
|
|
318
|
+
- step 4 uses the router instance of the app, never the overridable `useRoute`/`navigateTo`;
|
|
320
319
|
- DOM cleanup removes only new direct children of `document.body`; changes to nodes that existed at startup are not reverted;
|
|
321
320
|
- cookies are expired only as listed above; `HttpOnly` cookies and cookies of other paths or domains are not touched;
|
|
322
321
|
- state created by startup plugins is not re-created.
|
|
@@ -355,7 +354,7 @@ import { defineVitestConfig } from "@nuxt/test-utils/config"
|
|
|
355
354
|
|
|
356
355
|
export default defineVitestConfig({
|
|
357
356
|
test: {
|
|
358
|
-
include: ["
|
|
357
|
+
include: ["test/nuxt-isolated/**/*.test.ts"],
|
|
359
358
|
environment: "nuxt",
|
|
360
359
|
},
|
|
361
360
|
})
|
|
@@ -366,7 +365,7 @@ vitest run
|
|
|
366
365
|
vitest run --config vitest.isolated.config.ts
|
|
367
366
|
```
|
|
368
367
|
|
|
369
|
-
|
|
368
|
+
Keep the `include` patterns of the two configs from overlapping, so each file runs in one of them.
|
|
370
369
|
|
|
371
370
|
## Compatibility and upstream
|
|
372
371
|
|
|
@@ -42,6 +42,7 @@ async function cleanupAfterTest(store, baseline, unmountWrappers) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
for (const [name, run] of TIMER_STEPS) await step(name, run);
|
|
45
46
|
await step("unmount wrappers", () => unmountWrappers?.());
|
|
46
47
|
await step("flush promises", async () => {
|
|
47
48
|
await nextTick();
|
|
@@ -73,7 +74,7 @@ async function cleanupAfterTest(store, baseline, unmountWrappers) {
|
|
|
73
74
|
await step("clear localStorage", () => localStorage.clear());
|
|
74
75
|
await step("clear sessionStorage", () => sessionStorage.clear());
|
|
75
76
|
await step("expire cookies", expireCookies);
|
|
76
|
-
for (const [name, run] of
|
|
77
|
+
for (const [name, run] of STUB_STEPS) await step(name, run);
|
|
77
78
|
await step("remove body children", () => {
|
|
78
79
|
for (const child of Array.from(document.body.children)) if (!baseline.bodyChildren.has(child)) child.remove();
|
|
79
80
|
});
|
|
@@ -82,12 +83,9 @@ async function cleanupAfterTest(store, baseline, unmountWrappers) {
|
|
|
82
83
|
function importNuxt() {
|
|
83
84
|
return import("nuxt/app");
|
|
84
85
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
["unstub envs", () => vi.unstubAllEnvs()],
|
|
89
|
-
["unstub globals", () => vi.unstubAllGlobals()]
|
|
90
|
-
];
|
|
86
|
+
const TIMER_STEPS = [["clear timers", () => vi.clearAllTimers()], ["use real timers", () => vi.useRealTimers()]];
|
|
87
|
+
const STUB_STEPS = [["unstub envs", () => vi.unstubAllEnvs()], ["unstub globals", () => vi.unstubAllGlobals()]];
|
|
88
|
+
const RUNNER_STEPS = [...TIMER_STEPS, ...STUB_STEPS];
|
|
91
89
|
/** Restores timers, envs and globals as far as possible, ignoring failures, for an environment that is already contaminated. */
|
|
92
90
|
function restoreTestRunner() {
|
|
93
91
|
for (const [, run] of RUNNER_STEPS) try {
|
|
@@ -132,9 +130,9 @@ function installLifecycle() {
|
|
|
132
130
|
beforeAll(async () => {
|
|
133
131
|
const store = getEnvironmentStore();
|
|
134
132
|
assertNotContaminated(store);
|
|
133
|
+
if (store.startupFailure) throw new Error("nuxt-vitest-shared-app: Nuxt startup failed earlier in this environment.", { cause: store.startupFailure });
|
|
135
134
|
const { tryUseNuxtApp } = await import("nuxt/app");
|
|
136
135
|
const nuxtApp = assertAppRunning(tryUseNuxtApp(), document);
|
|
137
|
-
if (store.startupFailure) throw new Error("nuxt-vitest-shared-app: Nuxt startup failed earlier in this environment.", { cause: store.startupFailure });
|
|
138
136
|
if (!store.baseline) {
|
|
139
137
|
const startupError = getStartupError(nuxtApp);
|
|
140
138
|
if (startupError) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-vitest-shared-app",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Run Nuxt component tests in Vitest on one
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Run Nuxt component tests in Vitest on one shared app per worker, with per-test cleanup.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
7
7
|
"nuxt-test-utils",
|