@equinor/fusion-framework-vitest-plugin-react-app 1.0.0-next.6 → 1.0.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/dist/esm/index.js +9 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/resolve-app-test-env.js +8 -2
- package/dist/esm/resolve-app-test-env.js.map +1 -1
- package/dist/esm/scope/resolve-fusion.js +22 -24
- package/dist/esm/scope/resolve-fusion.js.map +1 -1
- package/dist/esm/test-app.js +5 -8
- package/dist/esm/test-app.js.map +1 -1
- package/dist/esm/test.js +2 -1
- package/dist/esm/test.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/resolve-app-test-env.d.ts +3 -1
- package/dist/types/scope/resolve-fusion.d.ts +6 -7
- package/dist/types/test-app.d.ts +12 -11
- package/dist/types/test.d.ts +20 -9
- package/dist/types/version.d.ts +1 -1
- package/package.json +17 -19
- package/CHANGELOG.md +0 -196
- package/docs/advanced.md +0 -209
- package/docs/configuration.md +0 -112
- package/docs/getting-started.md +0 -66
- package/docs/migrating-an-existing-app.md +0 -202
- package/docs/module-mocks.md +0 -120
- package/docs/overview.md +0 -49
- package/docs/troubleshooting.md +0 -67
- package/docs/why-browser-mode.md +0 -128
- package/src/__tests__/app-test-vite-plugin.test.ts +0 -109
- package/src/__tests__/merge-env-config.test.ts +0 -67
- package/src/__tests__/resolve-app-test-env.test.ts +0 -83
- package/src/app-test.ts +0 -19
- package/src/define-project.ts +0 -91
- package/src/index.ts +0 -124
- package/src/merge-env-config.ts +0 -58
- package/src/render-app-component.tsx +0 -116
- package/src/render-app-hook.tsx +0 -135
- package/src/render.tsx +0 -64
- package/src/resolve-app-test-env.ts +0 -132
- package/src/scope/create-app-scope-wrapper.tsx +0 -26
- package/src/scope/default-app-env.ts +0 -13
- package/src/scope/index.ts +0 -4
- package/src/scope/resolve-app-scope.ts +0 -46
- package/src/scope/resolve-fusion.ts +0 -74
- package/src/test-app.tsx +0 -148
- package/src/test.tsx +0 -42
- package/src/version.ts +0 -2
- package/src/virtual-modules.d.ts +0 -12
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -10
package/docs/advanced.md
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# Advanced Fusion app testing
|
|
2
|
-
|
|
3
|
-
The `/test` entry point extends Vitest's test context with `appEnv`, `configureApp`, `configureFusion`,
|
|
4
|
-
`fusion`, `app`, `render`, and `renderHook`. Fixture declarations are reusable, while each test
|
|
5
|
-
receives fresh framework and app module instances.
|
|
6
|
-
|
|
7
|
-
This guide covers composing and overriding those Fusion fixture values; see Vitest's own
|
|
8
|
-
[Test Context](https://vitest.dev/guide/test-context) documentation for fixture scope, cleanup,
|
|
9
|
-
and the general `test.extend`/`test.override` mechanics Fusion builds on.
|
|
10
|
-
|
|
11
|
-
## Choose a rendering API
|
|
12
|
-
|
|
13
|
-
| API | Use it when |
|
|
14
|
-
| --- | --- |
|
|
15
|
-
| `test` from `/test` | The app's manifest, config, and module configurator should resolve automatically |
|
|
16
|
-
| `render` from `/test` | A standard `describe` or `it` block needs the automatically resolved app scope |
|
|
17
|
-
| `renderAppHook` | A hook needs an app scope with explicitly supplied options |
|
|
18
|
-
| `renderAppComponent` | A component needs explicit `env`, `configure`, or parent `fusion` options |
|
|
19
|
-
| `testApp` | A reusable fixture should not depend on Vite's automatic app-file resolution |
|
|
20
|
-
|
|
21
|
-
All Fusion rendering APIs initialize modules asynchronously and must be awaited. The lower
|
|
22
|
-
level helpers return `fusion.framework` and `fusion.app`, allowing a test to drive the exact
|
|
23
|
-
module instances consumed by the rendered subject.
|
|
24
|
-
|
|
25
|
-
## Extend app configuration
|
|
26
|
-
|
|
27
|
-
Create one reusable extended test when several cases need the same deterministic modules.
|
|
28
|
-
This must extend `test` from `/test`, not `testApp` from the main entry point:
|
|
29
|
-
`testApp`'s `configureApp` defaults to `undefined` (no Vite dependency, so no way to load the
|
|
30
|
-
real `src/config.ts` as live code), while `/test`'s `test` seeds it with the app's real
|
|
31
|
-
`configure` export via `appTestVitePlugin`'s virtual modules.
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
|
|
35
|
-
import { test as baseTest } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
36
|
-
|
|
37
|
-
const project = {
|
|
38
|
-
id: 'project-a',
|
|
39
|
-
title: 'Project A',
|
|
40
|
-
type: { id: 'ProjectMaster' },
|
|
41
|
-
value: {},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const test = baseTest.extend('configureApp', ({ configureApp }) => (configurator, args) => {
|
|
45
|
-
configureApp?.(configurator, args);
|
|
46
|
-
enableContextMock(configurator, (mock) => mock.setCurrentContext(project));
|
|
47
|
-
});
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Composing the original `configureApp` fixture preserves the application's production module
|
|
51
|
-
configuration. See [Module mocks](module-mocks.md) for authentication, context, bookmark,
|
|
52
|
-
feature-flag, HTTP, analytics, and telemetry boundaries.
|
|
53
|
-
|
|
54
|
-
`.extend(...)` returns a new, separately exported `test` — reach for it when several test
|
|
55
|
-
*files* need the same fixture default. Within one file, prefer `test.override(...)` (below):
|
|
56
|
-
it replaces a fixture in place, so every test in that file keeps importing the same `test`.
|
|
57
|
-
|
|
58
|
-
## Fake an endpoint URL with `mergeEnvConfig`
|
|
59
|
-
|
|
60
|
-
`appEnv`'s `config` is an `AppConfig` instance — its `environment`/`endpoints` live behind
|
|
61
|
-
private fields exposed only through getters, so `{ ...appEnv.config, endpoints: {...} }` copies
|
|
62
|
-
nothing and silently drops the rest of the config. Use `mergeEnvConfig` to fake one endpoint's
|
|
63
|
-
URL (e.g. the app's real backend, resolved from `app.config.ts`) while keeping every other
|
|
64
|
-
endpoint and environment variable intact:
|
|
65
|
-
|
|
66
|
-
```tsx
|
|
67
|
-
import { mergeEnvConfig, test as baseTest } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
68
|
-
|
|
69
|
-
export const test = baseTest.extend('appEnv', ({ appEnv }) =>
|
|
70
|
-
mergeEnvConfig(appEnv, { endpoints: { 'cpr-api': { url: backendBaseUrl } } }),
|
|
71
|
-
);
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Override a fixture for one test or a `describe` block
|
|
75
|
-
|
|
76
|
-
`test.override('name', ...)` replaces a fixture's resolved value without creating a new `test`
|
|
77
|
-
export. Called at the top of a `describe` block, the override applies to every test inside it
|
|
78
|
-
and does not leak to sibling blocks or other files — each `describe` starts from the file's
|
|
79
|
-
base `test` again. Called at the top of the file (outside any `describe`), it applies to every
|
|
80
|
-
test in that file.
|
|
81
|
-
|
|
82
|
-
**`configureApp`'s default value, from `/test`, *is* the app's real `src/config.ts` `configure`
|
|
83
|
-
export.** `test.override('configureApp', ...)` replaces that default outright — an override
|
|
84
|
-
that doesn't call the real `configure(configurator, args)` itself, as below, skips the app's
|
|
85
|
-
production module setup entirely rather than composing with it.
|
|
86
|
-
|
|
87
|
-
```tsx
|
|
88
|
-
import { test } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
89
|
-
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
|
|
90
|
-
import { describe } from 'vitest';
|
|
91
|
-
|
|
92
|
-
import { configure } from '../config'; // the app's own, real module configurator
|
|
93
|
-
|
|
94
|
-
const project = { id: 'project-a', title: 'Project A', type: { id: 'ProjectMaster' }, value: {} };
|
|
95
|
-
|
|
96
|
-
describe('with an initial project', () => {
|
|
97
|
-
test.override('configureApp', { injected: true }, () => (configurator, args) => {
|
|
98
|
-
configure(configurator, args); // compose the app's real configure, same as `.extend`
|
|
99
|
-
enableContextMock(configurator, (mock) => mock.setCurrentContext(project));
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('displays the initial context the app resolves on startup', async ({ render }) => {
|
|
103
|
-
const { getByText } = await render(<App />);
|
|
104
|
-
await expect.element(getByText(/project-a/)).toBeInTheDocument();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
`fusion` itself can be overridden the same way, when a case needs a differently configured
|
|
110
|
-
parent framework instance rather than a change to the app's own `configureApp`:
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
describe('with a parent framework context', () => {
|
|
114
|
-
test.override('fusion', async ({ appEnv }) =>
|
|
115
|
-
mockFramework<[AppModule, ContextModule]>((configurator) => {
|
|
116
|
-
enableAppManifestMock(configurator, appEnv);
|
|
117
|
-
enableContextMock(configurator, (mock) => mock.setCurrentContext(project));
|
|
118
|
-
}),
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
test('mirrors the context the parent sets', async ({ render, fusion }) => {
|
|
122
|
-
/* ... */
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
The `{ injected: true }` option matches the fixture's original declaration (see
|
|
128
|
-
`test-app.tsx`/`test.tsx`); it is not required for `.override(...)` itself, but keeping it
|
|
129
|
-
consistent avoids re-deriving whether the base fixture accepts a config-injected value. See
|
|
130
|
-
the [React context cookbook](../../../../cookbooks/app-react-context/src/App.test.tsx) and
|
|
131
|
-
[feature-flag cookbook](../../../../cookbooks/app-react-feature-flag/src/components/FeatureFlags.test.tsx)
|
|
132
|
-
for full working examples, and Vitest's own
|
|
133
|
-
[Test Context](https://vitest.dev/guide/test-context) guide for fixture scope and cleanup
|
|
134
|
-
mechanics beyond what Fusion adds.
|
|
135
|
-
|
|
136
|
-
## Supply a custom parent framework
|
|
137
|
-
|
|
138
|
-
Override or explicitly pass `fusion` when the scenario depends on portal-level context,
|
|
139
|
-
service discovery, authentication, or app manifests. Build the parent with `mockFramework`,
|
|
140
|
-
then initialize the app beneath it. This exercises parent-to-app propagation without a
|
|
141
|
-
running portal.
|
|
142
|
-
|
|
143
|
-
Use [`enableAppManifestMock`](../../../app/docs/testing.md#enableappmanifestmockconfigurator-env)
|
|
144
|
-
when the custom parent must serve the app's manifest and config. Use
|
|
145
|
-
[`mockFramework`](../../../framework/docs/testing.md) to seed the parent modules.
|
|
146
|
-
|
|
147
|
-
## Extend the parent framework mock with `configureFusion`
|
|
148
|
-
|
|
149
|
-
`fusion` (built by [`resolveFusion`](../src/scope/resolve-fusion.ts)) always carries a mocked
|
|
150
|
-
`app` module (serving this app's manifest) and a `navigation` module with in-memory history, so
|
|
151
|
-
tests don't leak URL/history state between runs. It also mocks the `featureFlag` module — with
|
|
152
|
-
no flags enabled, so `useFeature` needs no `localStorage` or URL seeding — but only when
|
|
153
|
-
`@equinor/fusion-framework-module-feature-flag` (an optional peer dependency) is actually
|
|
154
|
-
installed; apps that don't use feature flags aren't forced to add it. `configureFusion` runs on
|
|
155
|
-
the same configurator afterwards, so a test can register extra framework-level modules or
|
|
156
|
-
override that setup, without reimplementing it:
|
|
157
|
-
|
|
158
|
-
```tsx
|
|
159
|
-
import { enableFeatureFlagMock } from '@equinor/fusion-framework-module-feature-flag/mock';
|
|
160
|
-
|
|
161
|
-
const test = baseTest.extend('configureFusion', { injected: true }, () => (configurator) => {
|
|
162
|
-
// seed a specific flag rather than merely enabling the mock — that's already the default
|
|
163
|
-
enableFeatureFlagMock(configurator, (mock) => mock.addFeature({ key: 'new-search', enabled: true }));
|
|
164
|
-
configurator.serviceDiscovery.addServices([{ key: 'people', uri: baseUrl('people') }]);
|
|
165
|
-
});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Note this is a **framework-scope** module set, distinct from the app-scope one `configureApp`
|
|
169
|
-
seeds. A typical app registers its own `navigation` module independently, inside its own
|
|
170
|
-
`config.ts`'s `configure` callback (what `configureApp` composes with) — so overriding history
|
|
171
|
-
through `configureFusion` only affects framework-scope consumers, such as
|
|
172
|
-
`useFramework<[NavigationModule]>().modules.navigation` or `useBookmarkNavigate`, not a
|
|
173
|
-
rendered app's own router:
|
|
174
|
-
|
|
175
|
-
```tsx
|
|
176
|
-
import { enableNavigation, createHistory } from '@equinor/fusion-framework-module-navigation';
|
|
177
|
-
|
|
178
|
-
test.override('configureFusion', { injected: true }, () => (configurator) =>
|
|
179
|
-
enableNavigation(configurator, {
|
|
180
|
-
configure: (config) => config.setHistory(createHistory('browser')),
|
|
181
|
-
}),
|
|
182
|
-
);
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**`.override('fusion', ...)` bypasses `configureFusion` entirely** — it replaces the resolver
|
|
186
|
-
that calls it, so a `configureFusion` override on the same test/suite is silently never called.
|
|
187
|
-
Reach for `configureFusion` to extend the base mock; reach for `fusion` only to replace it
|
|
188
|
-
outright (e.g. with a fully custom or non-mocked instance) — see
|
|
189
|
-
[Supply a custom parent framework](#supply-a-custom-parent-framework) above.
|
|
190
|
-
|
|
191
|
-
## Test routes and app lifecycle
|
|
192
|
-
|
|
193
|
-
Render the complete `App` when assertions depend on route loaders, navigation, app
|
|
194
|
-
configuration, or several modules working together. Set the initial browser URL before
|
|
195
|
-
rendering, interact through visible controls or `app.navigation`, and assert the rendered
|
|
196
|
-
result.
|
|
197
|
-
|
|
198
|
-
The [React router cookbook](../../../../cookbooks/app-react-router/README.md) demonstrates
|
|
199
|
-
route-unit tests, complete application navigation, and HTTP-backed loaders.
|
|
200
|
-
|
|
201
|
-
## Runner-level advanced behavior
|
|
202
|
-
|
|
203
|
-
Fusion does not wrap these Vitest capabilities:
|
|
204
|
-
|
|
205
|
-
- [Browser interactions and assertions](https://vitest.dev/guide/browser/)
|
|
206
|
-
- [Asynchronous tests](https://vitest.dev/guide/learn/async)
|
|
207
|
-
- [Test projects](https://vitest.dev/guide/projects)
|
|
208
|
-
- [Coverage](https://vitest.dev/guide/coverage)
|
|
209
|
-
- [Debugging](https://vitest.dev/guide/debugging)
|
package/docs/configuration.md
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Configure Fusion React app tests
|
|
2
|
-
|
|
3
|
-
The package exposes three entry points with different responsibilities:
|
|
4
|
-
|
|
5
|
-
| Import | Responsibility |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| `@equinor/fusion-framework-vitest-plugin-react-app/config` | Build a browser-ready Vitest project with `defineProject` |
|
|
8
|
-
| `@equinor/fusion-framework-vitest-plugin-react-app/test` | Resolve the app's files and provide `test` and `render` fixtures |
|
|
9
|
-
| `@equinor/fusion-framework-vitest-plugin-react-app` | Configure the Vite plugin manually or render with explicit `env`, `configure`, and `fusion` options |
|
|
10
|
-
|
|
11
|
-
## `defineProject` defaults
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
15
|
-
|
|
16
|
-
export default defineProject();
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
The default project registers `appTestVitePlugin()` and configures:
|
|
20
|
-
|
|
21
|
-
- test files under `src/**/*.{test,spec}.{ts,tsx}`
|
|
22
|
-
- Vitest Browser Mode enabled
|
|
23
|
-
- the Playwright provider with one Chromium instance
|
|
24
|
-
- headless browser execution
|
|
25
|
-
- a `1024x768` default viewport, matching a typical low-resolution Citrix session rather than
|
|
26
|
-
Vitest's own mobile-sized default
|
|
27
|
-
- Vite warmup for `src/**/*.{ts,tsx}` to discover lazy imports before tests run
|
|
28
|
-
|
|
29
|
-
## Merge ordinary Vitest options
|
|
30
|
-
|
|
31
|
-
Pass an object to deep-merge it with the defaults through Vite's `mergeConfig`:
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
35
|
-
import { name, version } from './package.json' with { type: 'json' };
|
|
36
|
-
|
|
37
|
-
export default defineProject({
|
|
38
|
-
test: {
|
|
39
|
-
name: `${name}@${version}`,
|
|
40
|
-
// opt into a wider viewport for an app that only needs to support desktop
|
|
41
|
-
browser: { viewport: { width: 1920, height: 1080 } },
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
These are standard Vitest and Vite options. Use the official Vitest configuration reference
|
|
47
|
-
for runner behavior rather than looking for Fusion-specific equivalents.
|
|
48
|
-
|
|
49
|
-
## Replace defaults deliberately
|
|
50
|
-
|
|
51
|
-
Pass a function only when a deep merge cannot express the change. The function's return value
|
|
52
|
-
replaces the defaults outright, so preserve every default the project still needs:
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
56
|
-
|
|
57
|
-
export default defineProject((defaults) => ({
|
|
58
|
-
...defaults,
|
|
59
|
-
test: {
|
|
60
|
-
...defaults.test,
|
|
61
|
-
include: ['tests/browser/**/*.test.tsx'],
|
|
62
|
-
},
|
|
63
|
-
}));
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
When test files move outside `src`, update both `test.include` and `server.warmup.clientFiles`
|
|
67
|
-
if those tests load lazy application modules.
|
|
68
|
-
|
|
69
|
-
## Resolve non-standard app files
|
|
70
|
-
|
|
71
|
-
Use `appTestVitePlugin` directly when the app manifest, app configuration, or module
|
|
72
|
-
configurator does not use Fusion's normal file conventions:
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
import { playwright } from '@vitest/browser-playwright';
|
|
76
|
-
import { appTestVitePlugin } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
77
|
-
import { defineProject } from 'vitest/config';
|
|
78
|
-
|
|
79
|
-
export default defineProject({
|
|
80
|
-
plugins: [
|
|
81
|
-
appTestVitePlugin({
|
|
82
|
-
manifest: './config/app.manifest.ts',
|
|
83
|
-
config: './config/app.config.ts',
|
|
84
|
-
configure: './config/modules.ts',
|
|
85
|
-
}),
|
|
86
|
-
],
|
|
87
|
-
test: {
|
|
88
|
-
browser: {
|
|
89
|
-
enabled: true,
|
|
90
|
-
provider: playwright(),
|
|
91
|
-
headless: true,
|
|
92
|
-
instances: [{ browser: 'chromium' }],
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
server: { warmup: { clientFiles: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'] } },
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`entrypoint`, `manifest`, and `config` use the same resolution pipeline as `ffc app build`
|
|
100
|
-
and `ffc app dev`. `configure` must identify a real source file because the plugin re-exports
|
|
101
|
-
its live application code into the test bundle. Explicit files that do not exist throw
|
|
102
|
-
`FileNotFoundError`; convention-based lookups fall back to defaults.
|
|
103
|
-
|
|
104
|
-
## Choose fixtures or explicit render options
|
|
105
|
-
|
|
106
|
-
Use `/test` when the app's own files should resolve automatically. Use `renderAppHook`,
|
|
107
|
-
`renderAppComponent`, or `testApp` from the root entry point when the test should pass `env`,
|
|
108
|
-
`configure`, or a parent `fusion` instance explicitly.
|
|
109
|
-
|
|
110
|
-
- [`test` and app rendering](getting-started.md)
|
|
111
|
-
- [Detailed rendering and fixture APIs](../README.md#renderapphook)
|
|
112
|
-
- [Troubleshooting configuration](troubleshooting.md)
|
package/docs/getting-started.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Test a Fusion React app with Vitest
|
|
2
|
-
|
|
3
|
-
Use `@equinor/fusion-framework-vitest-plugin-react-app` to render an existing Fusion React
|
|
4
|
-
app in Vitest Browser Mode. The test runs in Chromium with a fresh framework and application
|
|
5
|
-
module scope, a deterministic signed-in `Test User`, and no running portal or real credentials.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
pnpm add -D vitest playwright @vitest/browser-playwright vitest-browser-react \
|
|
11
|
-
@equinor/fusion-framework-vitest-plugin-react-app
|
|
12
|
-
pnpm exec playwright install chromium
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
React, React DOM, RxJS, and Vite are peer dependencies that a Fusion React app normally
|
|
16
|
-
already provides.
|
|
17
|
-
|
|
18
|
-
## Configure Vitest
|
|
19
|
-
|
|
20
|
-
Create `vitest.config.ts` at the app project root:
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
24
|
-
|
|
25
|
-
export default defineProject();
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`defineProject` registers `appTestVitePlugin`, enables headless Chromium through Playwright,
|
|
29
|
-
includes `src/**/*.{test,spec}.{ts,tsx}`, and warms application source files so lazy route
|
|
30
|
-
imports do not reload the browser during a test.
|
|
31
|
-
|
|
32
|
-
## Write the first app test
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
import { expect } from 'vitest';
|
|
36
|
-
import { test } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
37
|
-
import { App } from './App';
|
|
38
|
-
|
|
39
|
-
test('renders the app', async ({ render }) => {
|
|
40
|
-
const screen = await render(<App />);
|
|
41
|
-
await expect.element(screen.getByRole('heading')).toBeVisible();
|
|
42
|
-
});
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
The `/test` entry point resolves the app's manifest, `app.config.ts`, and module configurator
|
|
46
|
-
through the registered Vite plugin. Always await `render`, `renderHook`,
|
|
47
|
-
`renderAppComponent`, and `renderAppHook`; Fusion module initialization completes before the
|
|
48
|
-
first render.
|
|
49
|
-
|
|
50
|
-
## Run
|
|
51
|
-
|
|
52
|
-
```sh
|
|
53
|
-
pnpm exec vitest
|
|
54
|
-
pnpm exec vitest run
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The first command watches for changes. The second performs one run for CI. A successful run
|
|
58
|
-
reports one passing test and requires no portal, credentials, or backend service.
|
|
59
|
-
|
|
60
|
-
## Next steps
|
|
61
|
-
|
|
62
|
-
- [Configure the test project and app-file resolution](configuration.md)
|
|
63
|
-
- [Resolve common setup, browser, and network failures](troubleshooting.md)
|
|
64
|
-
- [Seed Fusion module dependencies](module-mocks.md)
|
|
65
|
-
- [Choose between app, framework, module, and HTTP test layers](../../../framework/docs/testing-choosing-a-layer.md)
|
|
66
|
-
- [Seed authentication, context, service discovery, and HTTP](../README.md#overview)
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
# Migrate an existing app to Fusion Vitest
|
|
2
|
-
|
|
3
|
-
Use this guide to move existing application tests to
|
|
4
|
-
`@equinor/fusion-framework-vitest-plugin-react-app`. It covers suites built with
|
|
5
|
-
`@testing-library/react`, `jsdom` or `happy-dom`, hand-written Fusion hook mocks, and mock HTTP
|
|
6
|
-
servers such as MirageJS, MSW, or Nock.
|
|
7
|
-
|
|
8
|
-
You can migrate one test at a time. A mixed suite is a valid final state.
|
|
9
|
-
|
|
10
|
-
## Choose which tests to migrate
|
|
11
|
-
|
|
12
|
-
Start with tests that read from the application module scope through
|
|
13
|
-
`useAppModule`, `useAccessToken`, `useCurrentContext`, `useCurrentBookmark`, `useFeature`, or a
|
|
14
|
-
route that uses several Fusion modules. The plugin can replace their hand-written hook mocks
|
|
15
|
-
with the real module pipeline and supported `enable*Mock` configuration.
|
|
16
|
-
|
|
17
|
-
Keep pure functions, presentational components, and hooks with no Fusion dependency on their
|
|
18
|
-
current runner when that is simpler. A test can also use the real Fusion module setup without
|
|
19
|
-
Browser Mode by providing its own renderer. See
|
|
20
|
-
[Why Browser Mode is the default](why-browser-mode.md#choose-a-different-runtime).
|
|
21
|
-
|
|
22
|
-
## What changes, concretely
|
|
23
|
-
|
|
24
|
-
| Existing pattern | Replace with |
|
|
25
|
-
| --- | --- |
|
|
26
|
-
| `@testing-library/react`'s `render`/`renderHook`/`waitFor`/`act`, on `jsdom` or `happy-dom` | `vitest-browser-react`'s `render`/`renderHook` and `vitest`'s `waitFor`/`act`, on real Chromium (Vitest Browser Mode) |
|
|
27
|
-
| `vi.mock('@equinor/fusion-framework-react/hooks', ...)`, `vi.mock('.../feature-flag', ...)`, and similar hand-rolled Fusion hook mocks | The owning module's `enable*Mock` from its own `/mock` entry point. See [Module mocks](module-mocks.md) |
|
|
28
|
-
| A hand-written mock HTTP server (MirageJS, `msw/node`, `nock`) answering every backend call | `configurator.http.addMiddleware(...)` with a hand-written `HttpMiddleware`, or `createRouterMiddleware` for several routes under one base URI. See [HTTP testing](../../../modules/http/docs/testing.md) |
|
|
29
|
-
| A hand-rolled test wrapper composing a router, context providers, and error boundaries around every render | `test.extend(...)` fixtures stacked on the base `render`/`configureApp` fixtures. See [Compose a router and a domain fixture](#compose-a-router-and-a-domain-fixture) below |
|
|
30
|
-
| Synchronous assertions (`expect(screen.getByText(...))`) | Browser locators and `await expect.element(screen.getByText(...)).toBeVisible()` |
|
|
31
|
-
|
|
32
|
-
## Step 1: install Browser Mode dependencies
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
pnpm add -D vitest playwright @vitest/browser-playwright vitest-browser-react \
|
|
36
|
-
@equinor/fusion-framework-vitest-plugin-react-app
|
|
37
|
-
pnpm exec playwright install chromium
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Remove `@testing-library/react`, `@testing-library/dom`, `@testing-library/user-event`, and
|
|
41
|
-
whichever `jsdom`/`happy-dom` package the app installed once every test that used them has
|
|
42
|
-
migrated. Keep them if part of the suite intentionally stays on the old renderer.
|
|
43
|
-
|
|
44
|
-
Replace the app's `vitest.config.ts` with the plugin's project config:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
48
|
-
|
|
49
|
-
export default defineProject();
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
`defineProject` registers `appTestVitePlugin`, enables headless Chromium, includes
|
|
53
|
-
`src/**/*.{test,spec}.{ts,tsx}`, and warms application source so lazy route imports don't reload
|
|
54
|
-
the browser mid-test. See [Configuration](configuration.md) for overriding any of that.
|
|
55
|
-
|
|
56
|
-
## Step 2: replace hand-rolled Fusion hook mocks
|
|
57
|
-
|
|
58
|
-
The `/test` entry point initializes the real module pipeline. This removes most `vi.mock`
|
|
59
|
-
implementations for hooks such as `useCurrentUser`, `useFeature`, and `useHttpClient`. The
|
|
60
|
-
default setup includes a signed-in `Test User` and initialized feature-flag, context, and
|
|
61
|
-
bookmark modules:
|
|
62
|
-
|
|
63
|
-
```diff
|
|
64
|
-
-vi.mock('@equinor/fusion-framework-react/hooks', async (importOriginal) => ({
|
|
65
|
-
- ...(await importOriginal()),
|
|
66
|
-
- useCurrentUser: vi.fn(() => undefined),
|
|
67
|
-
-}));
|
|
68
|
-
-vi.mock('@equinor/fusion-framework-react-app/feature-flag', () => ({
|
|
69
|
-
- useFeature: vi.fn(() => ({})),
|
|
70
|
-
-}));
|
|
71
|
-
+import { enableFeatureFlagMock } from '@equinor/fusion-framework-module-feature-flag/mock';
|
|
72
|
-
+import { test as baseTest } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
73
|
-
+
|
|
74
|
-
+export const test = baseTest.extend('configureApp', ({ configureApp }) => (configurator, args) => {
|
|
75
|
-
+ configureApp?.(configurator, args);
|
|
76
|
-
+ configurator.msal.setAccount(null); // was: useCurrentUser returning undefined
|
|
77
|
-
+ enableFeatureFlagMock(configurator, (mock) => mock.addFeature({ key: 'new-search', enabled: true }));
|
|
78
|
-
+});
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Each Fusion module owns its own mock entry point: `enableMsalMock`/`configurator.msal`,
|
|
82
|
-
`enableFeatureFlagMock`, `enableContextMock`, `enableBookmarkMock`, and more. See
|
|
83
|
-
[Module mocks](module-mocks.md) for the full list and each one's defaults. A hook or a small
|
|
84
|
-
piece of app config with no Fusion dependency (for example, reading `environment` from the
|
|
85
|
-
app's own manifest) stays a plain `vi.mock`.
|
|
86
|
-
|
|
87
|
-
Some suites override `useModuleCurrentContext` directly and change its return value with
|
|
88
|
-
`vi.spyOn`. Replace the initial value with `enableContextMock`. To change context after render,
|
|
89
|
-
call the real module:
|
|
90
|
-
`await fusion.app.context.setCurrentContextByIdAsync(otherProject.id)`. See the first
|
|
91
|
-
[`renderAppComponent`](../README.md#renderappcomponent) example.
|
|
92
|
-
|
|
93
|
-
## Step 3: port the mock HTTP server
|
|
94
|
-
|
|
95
|
-
Use `createRouterMiddleware` for a mock server that handles several routes under one base URL:
|
|
96
|
-
|
|
97
|
-
```diff
|
|
98
|
-
-import { createServer } from 'miragejs';
|
|
99
|
-
-
|
|
100
|
-
-export function createTestServer() {
|
|
101
|
-
- return createServer({
|
|
102
|
-
- routes() {
|
|
103
|
-
- this.get('/activities/:id', (schema, request) => ({ id: request.params.id, ... }));
|
|
104
|
-
- this.post('/activities', (schema, request) => ({ ... }));
|
|
105
|
-
- },
|
|
106
|
-
- });
|
|
107
|
-
-}
|
|
108
|
-
+import { createRouterMiddleware } from '@equinor/fusion-framework-module-http/mock';
|
|
109
|
-
+
|
|
110
|
-
+export const activityRoutes = createRouterMiddleware('https://cpr-api.example.com', (router) => {
|
|
111
|
-
+ router.get('/activities/:id', ({ params }) => Response.json({ id: params.id /* ... */ }));
|
|
112
|
-
+ router.post('/activities', async ({ request }) => Response.json({ /* ... */ }));
|
|
113
|
-
+});
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Register the result with `configurator.http.addMiddleware(activityRoutes)` inside a `configureApp`
|
|
117
|
-
fixture (see step 4). Port one route file at a time. Each ported file is independently testable.
|
|
118
|
-
An unported route can fall through to the real network, so keep the old interceptor
|
|
119
|
-
for tests that have not migrated yet or add a final fail-closed middleware; do not rely on a
|
|
120
|
-
missing route to fail by itself. If the backend has an OpenAPI document, prefer
|
|
121
|
-
`createOpenApiMockMiddleware` (`@equinor/fusion-framework-module-http/mock`) over hand-written
|
|
122
|
-
routes; see [HTTP testing](../../../modules/http/docs/testing.md).
|
|
123
|
-
|
|
124
|
-
If `useHttpClient` currently returns a plain `fetch` wrapper, remove that hook mock as well. The
|
|
125
|
-
real client reaches middleware registered with `configurator.http.addMiddleware`. Register the
|
|
126
|
-
router for the base URI used by the real client.
|
|
127
|
-
|
|
128
|
-
## Compose a router and a domain fixture
|
|
129
|
-
|
|
130
|
-
Use a `render` extension for JSX wrappers such as routers and app-owned React providers. Use a
|
|
131
|
-
`configureApp` extension for Fusion module state. Stack both extensions when a test needs both:
|
|
132
|
-
|
|
133
|
-
```tsx
|
|
134
|
-
import type { ReactElement } from 'react';
|
|
135
|
-
import { test as baseTest } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
136
|
-
import { Router } from '@equinor/fusion-framework-react-router';
|
|
137
|
-
import type { RouteObject } from '@equinor/fusion-framework-react-router';
|
|
138
|
-
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
|
|
139
|
-
|
|
140
|
-
// Every route matches, so the element under test stays mounted.
|
|
141
|
-
const testWithRouter = baseTest.extend('render', () => (ui: ReactElement) => {
|
|
142
|
-
const routes: RouteObject[] = [{ path: '*', Component: () => ui }];
|
|
143
|
-
return baseTest.render(<Router routes={routes} />);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// The domain fixture uses the same shape as any other `configureApp` extension.
|
|
147
|
-
export const test = testWithRouter.extend('configureApp', ({ configureApp }) => (configurator, args) => {
|
|
148
|
-
configureApp?.(configurator, args);
|
|
149
|
-
enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA));
|
|
150
|
-
});
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
A test importing this `test` gets both the router wrapper and seeded Fusion context.
|
|
154
|
-
|
|
155
|
-
Keep app-owned React providers, such as authorization, schema, or snackbar providers, in the
|
|
156
|
-
`render` extension. Move only Fusion module state to `configureApp` and `enable*Mock`.
|
|
157
|
-
|
|
158
|
-
## Step 4: update renders and assertions
|
|
159
|
-
|
|
160
|
-
Every render/hook helper is `async`, and most assertion helpers from `vitest-browser-react`
|
|
161
|
-
resolve against the real browser rather than synchronously:
|
|
162
|
-
|
|
163
|
-
```diff
|
|
164
|
-
-const { findByTestId } = render(<WrappedProcessPage {...props} />);
|
|
165
|
-
-await findByTestId('customization-panel');
|
|
166
|
-
+const { getByTestId } = await render(<WrappedProcessPage {...props} />);
|
|
167
|
-
+await expect.element(getByTestId('customization-panel')).toBeVisible();
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
`fireEvent`-style interactions become element method calls (`await screen.getByRole('button', {
|
|
171
|
-
name: 'Activate' }).click()`), and `act`/`waitFor` come from `vitest` rather than
|
|
172
|
-
`@testing-library/react`. See the [Vitest Browser Mode](https://vitest.dev/guide/browser/) and
|
|
173
|
-
[mocking](https://vitest.dev/guide/mocking) guides for the general-purpose parts of this that
|
|
174
|
-
aren't Fusion-specific.
|
|
175
|
-
|
|
176
|
-
## Step 5: reassess DOM-emulation-only workarounds
|
|
177
|
-
|
|
178
|
-
Review polyfills and component replacements that may only exist for `jsdom` or `happy-dom`.
|
|
179
|
-
Remove one workaround at a time and run the affected tests against the real component.
|
|
180
|
-
|
|
181
|
-
A common one: a no-op `window.ResizeObserver` stub carried over from `jsdom`/`happy-dom` setup.
|
|
182
|
-
Real Chromium doesn't need the stub, but leaving it in place silently breaks any component that
|
|
183
|
-
measures itself through `ResizeObserver` before rendering — for example a virtualized popover
|
|
184
|
-
list. Remove the stub rather than porting it.
|
|
185
|
-
|
|
186
|
-
Do not remove unrelated suppressions automatically. AG Grid license messages, Lit dev-mode
|
|
187
|
-
warnings, network behavior, and app-specific test doubles may still apply in Browser Mode.
|
|
188
|
-
|
|
189
|
-
## Migrate incrementally
|
|
190
|
-
|
|
191
|
-
Nothing requires migrating a whole app in one pass. A common order: pick one page or route-level
|
|
192
|
-
test that already hand-mocks several Fusion hooks, migrate it end to end (steps 1-4 above),
|
|
193
|
-
confirm it's green, then migrate the next. Leave tests with no Fusion dependency on their
|
|
194
|
-
current renderer for as long as that remains the cheaper option.
|
|
195
|
-
|
|
196
|
-
## Related documentation
|
|
197
|
-
|
|
198
|
-
- [Choose a Fusion testing layer](../../../framework/docs/testing-choosing-a-layer.md)
|
|
199
|
-
- [Module mocks](module-mocks.md)
|
|
200
|
-
- [HTTP testing](../../../modules/http/docs/testing.md)
|
|
201
|
-
- [Advanced usage](advanced.md): composing fixtures and explicit render options
|
|
202
|
-
- [Troubleshooting](troubleshooting.md)
|