@equinor/fusion-framework-vitest-plugin-react-app 0.2.0-next.0
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/CHANGELOG.md +104 -0
- package/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/esm/app-test.js +9 -0
- package/dist/esm/app-test.js.map +1 -0
- package/dist/esm/define-project.js +68 -0
- package/dist/esm/define-project.js.map +1 -0
- package/dist/esm/index.js +99 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/render-app-component.js +52 -0
- package/dist/esm/render-app-component.js.map +1 -0
- package/dist/esm/render-app-hook.js +63 -0
- package/dist/esm/render-app-hook.js.map +1 -0
- package/dist/esm/render.js +49 -0
- package/dist/esm/render.js.map +1 -0
- package/dist/esm/resolve-app-test-env.js +85 -0
- package/dist/esm/resolve-app-test-env.js.map +1 -0
- package/dist/esm/scope/create-app-scope-wrapper.js +15 -0
- package/dist/esm/scope/create-app-scope-wrapper.js.map +1 -0
- package/dist/esm/scope/default-app-env.js +12 -0
- package/dist/esm/scope/default-app-env.js.map +1 -0
- package/dist/esm/scope/index.js +5 -0
- package/dist/esm/scope/index.js.map +1 -0
- package/dist/esm/scope/resolve-app-scope.js +21 -0
- package/dist/esm/scope/resolve-app-scope.js.map +1 -0
- package/dist/esm/scope/resolve-fusion.js +18 -0
- package/dist/esm/scope/resolve-fusion.js.map +1 -0
- package/dist/esm/test-app.js +59 -0
- package/dist/esm/test-app.js.map +1 -0
- package/dist/esm/test.js +33 -0
- package/dist/esm/test.js.map +1 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/app-test.d.ts +6 -0
- package/dist/types/define-project.d.ts +41 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/render-app-component.d.ts +86 -0
- package/dist/types/render-app-hook.d.ts +99 -0
- package/dist/types/render.d.ts +37 -0
- package/dist/types/resolve-app-test-env.d.ts +56 -0
- package/dist/types/scope/create-app-scope-wrapper.d.ts +14 -0
- package/dist/types/scope/default-app-env.d.ts +5 -0
- package/dist/types/scope/index.d.ts +4 -0
- package/dist/types/scope/resolve-app-scope.d.ts +31 -0
- package/dist/types/scope/resolve-fusion.d.ts +13 -0
- package/dist/types/test-app.d.ts +68 -0
- package/dist/types/test.d.ts +66 -0
- package/dist/types/version.d.ts +1 -0
- package/docs/advanced.md +140 -0
- package/docs/configuration.md +109 -0
- package/docs/getting-started.md +66 -0
- package/docs/migrating-an-existing-app.md +197 -0
- package/docs/module-mocks.md +120 -0
- package/docs/overview.md +49 -0
- package/docs/troubleshooting.md +64 -0
- package/docs/why-browser-mode.md +113 -0
- package/package.json +87 -0
- package/src/__tests__/app-test-vite-plugin.test.ts +109 -0
- package/src/__tests__/resolve-app-test-env.test.ts +83 -0
- package/src/app-test.ts +18 -0
- package/src/define-project.ts +84 -0
- package/src/index.ts +124 -0
- package/src/render-app-component.tsx +116 -0
- package/src/render-app-hook.tsx +135 -0
- package/src/render.tsx +64 -0
- package/src/resolve-app-test-env.ts +132 -0
- package/src/scope/create-app-scope-wrapper.tsx +26 -0
- package/src/scope/default-app-env.ts +13 -0
- package/src/scope/index.ts +4 -0
- package/src/scope/resolve-app-scope.ts +46 -0
- package/src/scope/resolve-fusion.ts +29 -0
- package/src/test-app.tsx +72 -0
- package/src/test.tsx +35 -0
- package/src/version.ts +2 -0
- package/src/virtual-modules.d.ts +12 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +10 -0
package/docs/advanced.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Advanced Fusion app testing
|
|
2
|
+
|
|
3
|
+
The `/test` entry point extends Vitest's test context with `env`, `configure`, `fusion`, `app`,
|
|
4
|
+
`render`, and `renderHook`. Fixture declarations are reusable, while each test receives fresh
|
|
5
|
+
framework and app module instances.
|
|
6
|
+
|
|
7
|
+
This guide covers composing and overriding those six 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
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
|
|
31
|
+
import { test as baseTest } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
32
|
+
|
|
33
|
+
const project = {
|
|
34
|
+
id: 'project-a',
|
|
35
|
+
title: 'Project A',
|
|
36
|
+
type: { id: 'ProjectMaster' },
|
|
37
|
+
value: {},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const test = baseTest.extend('configure', ({ configure }) => (configurator, args) => {
|
|
41
|
+
configure?.(configurator, args);
|
|
42
|
+
enableContextMock(configurator, (mock) => mock.setCurrentContext(project));
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Composing the original `configure` fixture preserves the application's production module
|
|
47
|
+
configuration. See [Module mocks](module-mocks.md) for authentication, context, bookmark,
|
|
48
|
+
feature-flag, HTTP, analytics, and telemetry boundaries.
|
|
49
|
+
|
|
50
|
+
`.extend(...)` returns a new, separately exported `test` — reach for it when several test
|
|
51
|
+
*files* need the same fixture default. Within one file, prefer `test.override(...)` (below):
|
|
52
|
+
it replaces a fixture in place, so every test in that file keeps importing the same `test`.
|
|
53
|
+
|
|
54
|
+
## Override a fixture for one test or a `describe` block
|
|
55
|
+
|
|
56
|
+
`test.override('name', ...)` replaces a fixture's resolved value without creating a new `test`
|
|
57
|
+
export. Called at the top of a `describe` block, the override applies to every test inside it
|
|
58
|
+
and does not leak to sibling blocks or other files — each `describe` starts from the file's
|
|
59
|
+
base `test` again. Called at the top of the file (outside any `describe`), it applies to every
|
|
60
|
+
test in that file.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { test } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
64
|
+
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
|
|
65
|
+
import { describe } from 'vitest';
|
|
66
|
+
|
|
67
|
+
import { configure } from '../config'; // the app's own, real module configurator
|
|
68
|
+
|
|
69
|
+
const project = { id: 'project-a', title: 'Project A', type: { id: 'ProjectMaster' }, value: {} };
|
|
70
|
+
|
|
71
|
+
describe('with an initial project', () => {
|
|
72
|
+
test.override('configure', { injected: true }, () => (configurator, args) => {
|
|
73
|
+
configure(configurator, args); // compose the app's real configure, same as `.extend`
|
|
74
|
+
enableContextMock(configurator, (mock) => mock.setCurrentContext(project));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('displays the initial context the app resolves on startup', async ({ render }) => {
|
|
78
|
+
const { getByText } = await render(<App />);
|
|
79
|
+
await expect.element(getByText(/project-a/)).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`fusion` itself can be overridden the same way, when a case needs a differently configured
|
|
85
|
+
parent framework instance rather than a change to the app's own `configure`:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
describe('with a parent framework context', () => {
|
|
89
|
+
test.override('fusion', async ({ env }) =>
|
|
90
|
+
mockFramework<[AppModule, ContextModule]>((configurator) => {
|
|
91
|
+
enableAppManifestMock(configurator, env);
|
|
92
|
+
enableContextMock(configurator, (mock) => mock.setCurrentContext(project));
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
test('mirrors the context the parent sets', async ({ render, fusion }) => {
|
|
97
|
+
/* ... */
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `{ injected: true }` option matches the fixture's original declaration (see
|
|
103
|
+
`test-app.tsx`/`test.tsx`); it is not required for `.override(...)` itself, but keeping it
|
|
104
|
+
consistent avoids re-deriving whether the base fixture accepts a config-injected value. See
|
|
105
|
+
the [React context cookbook](../../../../cookbooks/app-react-context/src/App.test.tsx) and
|
|
106
|
+
[feature-flag cookbook](../../../../cookbooks/app-react-feature-flag/src/components/FeatureFlags.test.tsx)
|
|
107
|
+
for full working examples, and Vitest's own
|
|
108
|
+
[Test Context](https://vitest.dev/guide/test-context) guide for fixture scope and cleanup
|
|
109
|
+
mechanics beyond what Fusion adds.
|
|
110
|
+
|
|
111
|
+
## Supply a custom parent framework
|
|
112
|
+
|
|
113
|
+
Override or explicitly pass `fusion` when the scenario depends on portal-level context,
|
|
114
|
+
service discovery, authentication, or app manifests. Build the parent with `mockFramework`,
|
|
115
|
+
then initialize the app beneath it. This exercises parent-to-app propagation without a
|
|
116
|
+
running portal.
|
|
117
|
+
|
|
118
|
+
Use [`enableAppManifestMock`](../../../app/docs/testing.md#enableappmanifestmockconfigurator-env)
|
|
119
|
+
when the custom parent must serve the app's manifest and config. Use
|
|
120
|
+
[`mockFramework`](../../../framework/docs/testing.md) to seed the parent modules.
|
|
121
|
+
|
|
122
|
+
## Test routes and app lifecycle
|
|
123
|
+
|
|
124
|
+
Render the complete `App` when assertions depend on route loaders, navigation, app
|
|
125
|
+
configuration, or several modules working together. Set the initial browser URL before
|
|
126
|
+
rendering, interact through visible controls or `app.navigation`, and assert the rendered
|
|
127
|
+
result.
|
|
128
|
+
|
|
129
|
+
The [React router cookbook](../../../../cookbooks/app-react-router/README.md) demonstrates
|
|
130
|
+
route-unit tests, complete application navigation, and HTTP-backed loaders.
|
|
131
|
+
|
|
132
|
+
## Runner-level advanced behavior
|
|
133
|
+
|
|
134
|
+
Fusion does not wrap these Vitest capabilities:
|
|
135
|
+
|
|
136
|
+
- [Browser interactions and assertions](https://vitest.dev/guide/browser/)
|
|
137
|
+
- [Asynchronous tests](https://vitest.dev/guide/learn/async)
|
|
138
|
+
- [Test projects](https://vitest.dev/guide/projects)
|
|
139
|
+
- [Coverage](https://vitest.dev/guide/coverage)
|
|
140
|
+
- [Debugging](https://vitest.dev/guide/debugging)
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
- Vite warmup for `src/**/*.{ts,tsx}` to discover lazy imports before tests run
|
|
26
|
+
|
|
27
|
+
## Merge ordinary Vitest options
|
|
28
|
+
|
|
29
|
+
Pass an object to deep-merge it with the defaults through Vite's `mergeConfig`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
33
|
+
import { name, version } from './package.json' with { type: 'json' };
|
|
34
|
+
|
|
35
|
+
export default defineProject({
|
|
36
|
+
test: {
|
|
37
|
+
name: `${name}@${version}`,
|
|
38
|
+
browser: { viewport: { width: 1280, height: 720 } },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
These are standard Vitest and Vite options. Use the official Vitest configuration reference
|
|
44
|
+
for runner behavior rather than looking for Fusion-specific equivalents.
|
|
45
|
+
|
|
46
|
+
## Replace defaults deliberately
|
|
47
|
+
|
|
48
|
+
Pass a function only when a deep merge cannot express the change. The function's return value
|
|
49
|
+
replaces the defaults outright, so preserve every default the project still needs:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
53
|
+
|
|
54
|
+
export default defineProject((defaults) => ({
|
|
55
|
+
...defaults,
|
|
56
|
+
test: {
|
|
57
|
+
...defaults.test,
|
|
58
|
+
include: ['tests/browser/**/*.test.tsx'],
|
|
59
|
+
},
|
|
60
|
+
}));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When test files move outside `src`, update both `test.include` and `server.warmup.clientFiles`
|
|
64
|
+
if those tests load lazy application modules.
|
|
65
|
+
|
|
66
|
+
## Resolve non-standard app files
|
|
67
|
+
|
|
68
|
+
Use `appTestVitePlugin` directly when the app manifest, app configuration, or module
|
|
69
|
+
configurator does not use Fusion's normal file conventions:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { playwright } from '@vitest/browser-playwright';
|
|
73
|
+
import { appTestVitePlugin } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
74
|
+
import { defineProject } from 'vitest/config';
|
|
75
|
+
|
|
76
|
+
export default defineProject({
|
|
77
|
+
plugins: [
|
|
78
|
+
appTestVitePlugin({
|
|
79
|
+
manifest: './config/app.manifest.ts',
|
|
80
|
+
config: './config/app.config.ts',
|
|
81
|
+
configure: './config/modules.ts',
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
test: {
|
|
85
|
+
browser: {
|
|
86
|
+
enabled: true,
|
|
87
|
+
provider: playwright(),
|
|
88
|
+
headless: true,
|
|
89
|
+
instances: [{ browser: 'chromium' }],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
server: { warmup: { clientFiles: ['src/**/*.{ts,tsx}'] } },
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`entrypoint`, `manifest`, and `config` use the same resolution pipeline as `ffc app build`
|
|
97
|
+
and `ffc app dev`. `configure` must identify a real source file because the plugin re-exports
|
|
98
|
+
its live application code into the test bundle. Explicit files that do not exist throw
|
|
99
|
+
`FileNotFoundError`; convention-based lookups fall back to defaults.
|
|
100
|
+
|
|
101
|
+
## Choose fixtures or explicit render options
|
|
102
|
+
|
|
103
|
+
Use `/test` when the app's own files should resolve automatically. Use `renderAppHook`,
|
|
104
|
+
`renderAppComponent`, or `testApp` from the root entry point when the test should pass `env`,
|
|
105
|
+
`configure`, or a parent `fusion` instance explicitly.
|
|
106
|
+
|
|
107
|
+
- [`test` and app rendering](getting-started.md)
|
|
108
|
+
- [Detailed rendering and fixture APIs](../README.md#renderapphook)
|
|
109
|
+
- [Troubleshooting configuration](troubleshooting.md)
|
|
@@ -0,0 +1,66 @@
|
|
|
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)
|
|
@@ -0,0 +1,197 @@
|
|
|
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`/`configure` 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('configure', ({ configure }) => (configurator, args) => {
|
|
75
|
+
+ configure?.(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 `configure`
|
|
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
|
+
`configure` 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 `configure` extension.
|
|
147
|
+
export const test = testWithRouter.extend('configure', ({ configure }) => (configurator, args) => {
|
|
148
|
+
configure?.(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 `configure` 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
|
+
Do not remove unrelated suppressions automatically. AG Grid license messages, Lit dev-mode
|
|
182
|
+
warnings, network behavior, and app-specific test doubles may still apply in Browser Mode.
|
|
183
|
+
|
|
184
|
+
## Migrate incrementally
|
|
185
|
+
|
|
186
|
+
Nothing requires migrating a whole app in one pass. A common order: pick one page or route-level
|
|
187
|
+
test that already hand-mocks several Fusion hooks, migrate it end to end (steps 1-4 above),
|
|
188
|
+
confirm it's green, then migrate the next. Leave tests with no Fusion dependency on their
|
|
189
|
+
current renderer for as long as that remains the cheaper option.
|
|
190
|
+
|
|
191
|
+
## Related documentation
|
|
192
|
+
|
|
193
|
+
- [Choose a Fusion testing layer](../../../framework/docs/testing-choosing-a-layer.md)
|
|
194
|
+
- [Module mocks](module-mocks.md)
|
|
195
|
+
- [HTTP testing](../../../modules/http/docs/testing.md)
|
|
196
|
+
- [Advanced usage](advanced.md): composing fixtures and explicit render options
|
|
197
|
+
- [Troubleshooting](troubleshooting.md)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Mock Fusion dependencies in app tests
|
|
2
|
+
|
|
3
|
+
Use module-owned `/mock` entry points to seed deterministic Fusion state while retaining each
|
|
4
|
+
module's real configurator, provider, validation, and lifecycle. Import only the boundary the
|
|
5
|
+
app test needs; the React app test fixture already composes the normal framework and app
|
|
6
|
+
module scopes.
|
|
7
|
+
|
|
8
|
+
| App dependency | Import path | Primary API |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| Analytics | `@equinor/fusion-framework-module-analytics/mock` | `MockAnalyticsAdapter` |
|
|
11
|
+
| App manifests and config | `@equinor/fusion-framework-module-app/mock` | `MockAppClient` |
|
|
12
|
+
| Browser authentication | `@equinor/fusion-framework-module-msal/mock` | `enableMsalMock`, `MsalMockClient`, `createMockToken` |
|
|
13
|
+
| Node authentication with Azure Identity | `@equinor/fusion-framework-module-azure-identity/mock` | `enableAuthMock`, `MockAuthProvider` |
|
|
14
|
+
| Node authentication with MSAL | `@equinor/fusion-framework-module-msal-node/mock` | `enableAuthMock`, `MockAuthProvider` |
|
|
15
|
+
| Bookmarks | `@equinor/fusion-framework-module-bookmark/mock` | `enableBookmarkMock`, `BookmarkMockConfigurator` |
|
|
16
|
+
| Context | `@equinor/fusion-framework-module-context/mock` | `enableContextMock`, `ContextMockConfigurator` |
|
|
17
|
+
| Context fixture generation | `@equinor/fusion-framework-module-context/mock/fixtures` | `createContextItemFactory`, `createContextItems` |
|
|
18
|
+
| Feature flags | `@equinor/fusion-framework-module-feature-flag/mock` | `enableFeatureFlagMock`, `FeatureFlagMockConfigurator` |
|
|
19
|
+
| HTTP routes and OpenAPI responses | `@equinor/fusion-framework-module-http/mock` | `createRouterMiddleware`, `createOpenApiMockMiddleware` |
|
|
20
|
+
| Service discovery | `@equinor/fusion-framework-module-service-discovery/mock` | `enableServiceDiscoveryMock`, `ServiceDiscoveryMockConfigurator` |
|
|
21
|
+
| Telemetry | `@equinor/fusion-framework-module-telemetry/mock` | `enableTelemetryMock`, `MockTelemetryAdapter` |
|
|
22
|
+
|
|
23
|
+
The event module has no mock entry point. `waitForEvent` and `watchEvents` from
|
|
24
|
+
`@equinor/fusion-framework-module-event/utils` observe the real event provider initialized by
|
|
25
|
+
the app test fixture.
|
|
26
|
+
|
|
27
|
+
## Authentication
|
|
28
|
+
|
|
29
|
+
App tests start with a deterministic signed-in `Test User`. Set a named account or `null`
|
|
30
|
+
through `configurator.msal.setAccount(...)` before rendering. The real MSAL provider still
|
|
31
|
+
runs its initialization, account, token, and logout behavior against the in-process client.
|
|
32
|
+
|
|
33
|
+
See [MSAL testing](../../../modules/msal/docs/testing.md) for signed-out startup, login flows,
|
|
34
|
+
deterministic JWTs, and individual client spies.
|
|
35
|
+
|
|
36
|
+
## App manifest and configuration
|
|
37
|
+
|
|
38
|
+
The `/test` entry point automatically resolves the current app's manifest, `app.config.ts`,
|
|
39
|
+
and module configurator. Use `enableAppManifestMock` when a custom parent framework must serve
|
|
40
|
+
those values, or `mockAppModules` for app module testing without React.
|
|
41
|
+
|
|
42
|
+
See [Application module testing](../../../app/docs/testing.md).
|
|
43
|
+
|
|
44
|
+
## Context
|
|
45
|
+
|
|
46
|
+
Use `enableContextMock` for known domain state without a context API or HTTP setup. The pool
|
|
47
|
+
starts empty; `setCurrentContext` seeds and selects one item, while `setContexts` only makes
|
|
48
|
+
items resolvable. Use HTTP middleware instead when the context service and transport pipeline
|
|
49
|
+
are part of the test.
|
|
50
|
+
|
|
51
|
+
See [Context testing](../../../modules/context/README.md#testing) for defaults, related-context
|
|
52
|
+
behavior, and deterministic fixture generators.
|
|
53
|
+
|
|
54
|
+
## Service discovery and HTTP
|
|
55
|
+
|
|
56
|
+
Use `enableServiceDiscoveryMock` to resolve services from an in-memory registry. Use
|
|
57
|
+
`configurator.http.addMiddleware` to answer a few explicit requests through the app's real
|
|
58
|
+
named clients. Use `createOpenApiMockMiddleware` when an OpenAPI document describes many
|
|
59
|
+
operations.
|
|
60
|
+
|
|
61
|
+
- [Service discovery testing](../../../modules/service-discovery/docs/testing.md)
|
|
62
|
+
- [HTTP testing](../../../modules/http/docs/testing.md)
|
|
63
|
+
- [OpenAPI mock](../../../utils/openapi-mock/README.md)
|
|
64
|
+
|
|
65
|
+
An HTTP middleware handles only requests for which it returns a `Response`. Calling
|
|
66
|
+
`next(uri, init)` delegates to the next middleware and eventually the real network.
|
|
67
|
+
|
|
68
|
+
## Events, analytics, and telemetry
|
|
69
|
+
|
|
70
|
+
- Observe the real event provider with `waitForEvent` or `watchEvents` from
|
|
71
|
+
`@equinor/fusion-framework-module-event/utils`.
|
|
72
|
+
- Register `MockAnalyticsAdapter` to record analytics without exporting them.
|
|
73
|
+
- Use `enableTelemetryMock` and assert through `MockTelemetryAdapter.getItems` or
|
|
74
|
+
`waitForItem`.
|
|
75
|
+
|
|
76
|
+
- [Event testing utilities](../../../modules/event/docs/testing.md)
|
|
77
|
+
- [Analytics testing](../../../modules/analytics/docs/testing.md)
|
|
78
|
+
- [Telemetry testing](../../../modules/telemetry/README.md#testing)
|
|
79
|
+
|
|
80
|
+
## Seed several app dependencies
|
|
81
|
+
|
|
82
|
+
Compose the app's real configuration with the module mocks required by the scenario:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
|
|
86
|
+
import { enableFeatureFlagMock } from '@equinor/fusion-framework-module-feature-flag/mock';
|
|
87
|
+
import { test as baseTest } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
88
|
+
|
|
89
|
+
const project = {
|
|
90
|
+
id: 'project-a',
|
|
91
|
+
title: 'Project A',
|
|
92
|
+
type: { id: 'ProjectMaster' },
|
|
93
|
+
value: {},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const test = baseTest.extend('configure', ({ configure }) => (configurator, args) => {
|
|
97
|
+
configure?.(configurator, args);
|
|
98
|
+
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
99
|
+
enableContextMock(configurator, (mock) => mock.setCurrentContext(project));
|
|
100
|
+
enableFeatureFlagMock(configurator, (mock) => {
|
|
101
|
+
mock.addFeature({ key: 'new-search', enabled: true });
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The fixture declarations are reusable, while every test still receives fresh framework and
|
|
107
|
+
app module instances.
|
|
108
|
+
|
|
109
|
+
## Choose direct module mocks or HTTP
|
|
110
|
+
|
|
111
|
+
- Use a module mock for known domain state such as one current context, one account, or a set
|
|
112
|
+
of feature flags.
|
|
113
|
+
- Use `configurator.http.addMiddleware` when the test must exercise the module's production
|
|
114
|
+
service-discovery, HTTP-client, and response-handling pipeline.
|
|
115
|
+
- Use `createOpenApiMockMiddleware` when an OpenAPI document describes many operations.
|
|
116
|
+
- Use Vitest `vi.fn`, `vi.spyOn`, or `vi.mock` for individual JavaScript calls rather than a
|
|
117
|
+
Fusion-specific wrapper.
|
|
118
|
+
|
|
119
|
+
See each owning package README or `docs/testing.md` for its defaults, builder methods, and
|
|
120
|
+
failure behavior.
|