@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/LICENSE +21 -0
  3. package/README.md +349 -0
  4. package/dist/esm/app-test.js +9 -0
  5. package/dist/esm/app-test.js.map +1 -0
  6. package/dist/esm/define-project.js +68 -0
  7. package/dist/esm/define-project.js.map +1 -0
  8. package/dist/esm/index.js +99 -0
  9. package/dist/esm/index.js.map +1 -0
  10. package/dist/esm/render-app-component.js +52 -0
  11. package/dist/esm/render-app-component.js.map +1 -0
  12. package/dist/esm/render-app-hook.js +63 -0
  13. package/dist/esm/render-app-hook.js.map +1 -0
  14. package/dist/esm/render.js +49 -0
  15. package/dist/esm/render.js.map +1 -0
  16. package/dist/esm/resolve-app-test-env.js +85 -0
  17. package/dist/esm/resolve-app-test-env.js.map +1 -0
  18. package/dist/esm/scope/create-app-scope-wrapper.js +15 -0
  19. package/dist/esm/scope/create-app-scope-wrapper.js.map +1 -0
  20. package/dist/esm/scope/default-app-env.js +12 -0
  21. package/dist/esm/scope/default-app-env.js.map +1 -0
  22. package/dist/esm/scope/index.js +5 -0
  23. package/dist/esm/scope/index.js.map +1 -0
  24. package/dist/esm/scope/resolve-app-scope.js +21 -0
  25. package/dist/esm/scope/resolve-app-scope.js.map +1 -0
  26. package/dist/esm/scope/resolve-fusion.js +18 -0
  27. package/dist/esm/scope/resolve-fusion.js.map +1 -0
  28. package/dist/esm/test-app.js +59 -0
  29. package/dist/esm/test-app.js.map +1 -0
  30. package/dist/esm/test.js +33 -0
  31. package/dist/esm/test.js.map +1 -0
  32. package/dist/esm/version.js +3 -0
  33. package/dist/esm/version.js.map +1 -0
  34. package/dist/tsconfig.tsbuildinfo +1 -0
  35. package/dist/types/app-test.d.ts +6 -0
  36. package/dist/types/define-project.d.ts +41 -0
  37. package/dist/types/index.d.ts +45 -0
  38. package/dist/types/render-app-component.d.ts +86 -0
  39. package/dist/types/render-app-hook.d.ts +99 -0
  40. package/dist/types/render.d.ts +37 -0
  41. package/dist/types/resolve-app-test-env.d.ts +56 -0
  42. package/dist/types/scope/create-app-scope-wrapper.d.ts +14 -0
  43. package/dist/types/scope/default-app-env.d.ts +5 -0
  44. package/dist/types/scope/index.d.ts +4 -0
  45. package/dist/types/scope/resolve-app-scope.d.ts +31 -0
  46. package/dist/types/scope/resolve-fusion.d.ts +13 -0
  47. package/dist/types/test-app.d.ts +68 -0
  48. package/dist/types/test.d.ts +66 -0
  49. package/dist/types/version.d.ts +1 -0
  50. package/docs/advanced.md +140 -0
  51. package/docs/configuration.md +109 -0
  52. package/docs/getting-started.md +66 -0
  53. package/docs/migrating-an-existing-app.md +197 -0
  54. package/docs/module-mocks.md +120 -0
  55. package/docs/overview.md +49 -0
  56. package/docs/troubleshooting.md +64 -0
  57. package/docs/why-browser-mode.md +113 -0
  58. package/package.json +87 -0
  59. package/src/__tests__/app-test-vite-plugin.test.ts +109 -0
  60. package/src/__tests__/resolve-app-test-env.test.ts +83 -0
  61. package/src/app-test.ts +18 -0
  62. package/src/define-project.ts +84 -0
  63. package/src/index.ts +124 -0
  64. package/src/render-app-component.tsx +116 -0
  65. package/src/render-app-hook.tsx +135 -0
  66. package/src/render.tsx +64 -0
  67. package/src/resolve-app-test-env.ts +132 -0
  68. package/src/scope/create-app-scope-wrapper.tsx +26 -0
  69. package/src/scope/default-app-env.ts +13 -0
  70. package/src/scope/index.ts +4 -0
  71. package/src/scope/resolve-app-scope.ts +46 -0
  72. package/src/scope/resolve-fusion.ts +29 -0
  73. package/src/test-app.tsx +72 -0
  74. package/src/test.tsx +35 -0
  75. package/src/version.ts +2 -0
  76. package/src/virtual-modules.d.ts +12 -0
  77. package/tsconfig.json +24 -0
  78. package/vitest.config.ts +10 -0
@@ -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.