@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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @equinor/fusion-framework-vitest-plugin-react-app
|
|
2
|
+
|
|
3
|
+
## 0.2.0-next.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2836e0b: Add a `/config` entry-point exporting `defineProject`: a drop-in for Vitest's own `defineProject`, pre-wired with `appTestVitePlugin` and the `@vitest/browser-playwright`/`chromium` browser provider, so a consuming app's own `vitest.config.ts` needs no browser-provider boilerplate.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { defineProject } from "@equinor/fusion-framework-vitest-plugin-react-app/config";
|
|
11
|
+
import { name, version } from "./package.json" with { type: "json" };
|
|
12
|
+
|
|
13
|
+
export default defineProject({ test: { name: `${name}@${version}` } });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`override` is deep-merged onto the default config via Vite's own `mergeConfig` (a plain object), or applied to the default config outright (a function receiving it) — for changes `mergeConfig` can't express, such as swapping `test.browser.provider` for a different `@vitest/browser-*` provider.
|
|
17
|
+
|
|
18
|
+
`@vitest/browser-playwright` and `playwright` are now explicit peer dependencies of the package.
|
|
19
|
+
|
|
20
|
+
Also fixes `appTestVitePlugin`'s `entrypoint` inference: when no `entrypoint` option is given, it now resolves from the Vitest project's own root (via `configResolved`) instead of `process.cwd()`, so each cookbook's `vitest.config.ts` no longer needs to pass `entrypoint` explicitly when run as part of a multi-project Vitest run.
|
|
21
|
+
|
|
22
|
+
- 2836e0b: Add `@equinor/fusion-framework-vitest-plugin-react-app`: Vitest/`vitest-browser-react` helpers for testing a Fusion Framework React application inside a real, mock-backed application module scope — the same `FrameworkProvider` + `ModuleProvider` nesting `renderApp`/`createComponent` wire up in production, backed by `mockFramework` and `mockAppModules` (`@equinor/fusion-framework-app/mock`).
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { renderAppHook } from "@equinor/fusion-framework-vitest-plugin-react-app";
|
|
26
|
+
import { useAccessToken } from "@equinor/fusion-framework-react-app/msal";
|
|
27
|
+
|
|
28
|
+
const { result } = await renderAppHook(() =>
|
|
29
|
+
useAccessToken({ scopes: ["User.Read"] }),
|
|
30
|
+
);
|
|
31
|
+
await vi.waitFor(() => expect(result.current.pending).toBe(false));
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Highlights:
|
|
35
|
+
|
|
36
|
+
- `renderAppHook`/`renderAppComponent` — render a hook or component against the real `event`/`http`/`msal` module pipeline, with only the network boundary faked; the result carries a nested `fusion: { framework, app }` for driving a module directly after the initial render.
|
|
37
|
+
- `testApp` — a `vitest` `test` extended with `env`/`configure`/`app`/`render`/`renderHook` fixtures, for a test file whose cases share one mocked scope.
|
|
38
|
+
- `appTestVitePlugin` — a Vite plugin resolving an application's own manifest, config, and module-configurator (the same pipeline `ffc app build`/`ffc app dev` use) as virtual modules.
|
|
39
|
+
- A `/test` entry-point exporting `test`/`render`, pre-seeded from the resolved manifest/config/configure once `appTestVitePlugin` is registered — no per-test `env`/`configure` wiring.
|
|
40
|
+
|
|
41
|
+
Package documentation provides task-focused guides for setup, configuration, advanced fixtures,
|
|
42
|
+
module mocks, and troubleshooting so npm, GitHub, and retrieval clients use the same canonical
|
|
43
|
+
source as the VuePress site.
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- e8aae1f: Internal: publish every package on the `next` pre-release tag so the whole framework can be installed as a coherent set.
|
|
48
|
+
|
|
49
|
+
Packages without their own changes are bumped only to receive a `-next.N` version and the `next` dist-tag on npm. Install with:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add @equinor/fusion-framework-react-app@next
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- 2836e0b: Fix `defineProject` not pre-transforming lazily/code-split-imported source (e.g. route
|
|
56
|
+
components reached only through dynamic `import()`), which could force Vite to reload the
|
|
57
|
+
page mid-test and fail the in-flight test file's import.
|
|
58
|
+
|
|
59
|
+
`defineProject`'s Vite config now sets `server.warmup.clientFiles: ['src/**/*.{ts,tsx}']`, so
|
|
60
|
+
all source under `src/` is transformed up front instead of on first request.
|
|
61
|
+
|
|
62
|
+
- 2836e0b: Document `test.override(...)` for overriding a fixture (`configure`, `fusion`, etc.) within a
|
|
63
|
+
single test file or `describe` block, alongside the existing `test.extend(...)` guide, in the
|
|
64
|
+
package's "Advanced usage" doc (`docs/advanced.md`).
|
|
65
|
+
- 2836e0b: Add a "Migrate an existing app" guide (`docs/migrating-an-existing-app.md`) covering how to
|
|
66
|
+
move a suite from `@testing-library/react`/jsdom/happy-dom onto this package: installing
|
|
67
|
+
Browser Mode, replacing hand-rolled `vi.mock`s of Fusion hooks with each module's `enable*Mock`
|
|
68
|
+
entry point, porting a hand-written mock HTTP server to `createRouterMiddleware`, composing a
|
|
69
|
+
router fixture with a domain-state fixture in one file, and updating renders/assertions to the
|
|
70
|
+
async `vitest-browser-react` API. Linked from the package README and from
|
|
71
|
+
`docs/overview.md` and `testing-choosing-a-layer.md`.
|
|
72
|
+
- 2836e0b: Add a "Why Browser Mode is the default" guide (`docs/why-browser-mode.md`) explaining
|
|
73
|
+
the rationale for defaulting to real Chromium over DOM emulation (the documented React 19 peer
|
|
74
|
+
dependency crash in the previous renderer, plus the framework's real-module testing philosophy),
|
|
75
|
+
the actual performance tradeoff (a real browser is slower, not faster — this was a fidelity
|
|
76
|
+
choice, not a speed optimization), and how to bring your own renderer for a single test file by
|
|
77
|
+
composing `mockFramework`/`mockAppModules`/`FrameworkProvider`/`ModuleProvider` directly with a
|
|
78
|
+
different render function (e.g. `@testing-library/react` on `happy-dom`). Linked from the
|
|
79
|
+
package README and `docs/overview.md`.
|
|
80
|
+
- Updated dependencies [e8aae1f]
|
|
81
|
+
- Updated dependencies [2836e0b]
|
|
82
|
+
- Updated dependencies [2836e0b]
|
|
83
|
+
- Updated dependencies [2836e0b]
|
|
84
|
+
- Updated dependencies [2836e0b]
|
|
85
|
+
- Updated dependencies [2836e0b]
|
|
86
|
+
- Updated dependencies [2836e0b]
|
|
87
|
+
- Updated dependencies [2836e0b]
|
|
88
|
+
- Updated dependencies [2836e0b]
|
|
89
|
+
- Updated dependencies [2836e0b]
|
|
90
|
+
- Updated dependencies [2836e0b]
|
|
91
|
+
- Updated dependencies [2836e0b]
|
|
92
|
+
- Updated dependencies [2836e0b]
|
|
93
|
+
- Updated dependencies [2836e0b]
|
|
94
|
+
- Updated dependencies [2836e0b]
|
|
95
|
+
- Updated dependencies [2836e0b]
|
|
96
|
+
- Updated dependencies [2836e0b]
|
|
97
|
+
- @equinor/fusion-framework@8.1.0-next.0
|
|
98
|
+
- @equinor/fusion-framework-app@14.0.0-next.0
|
|
99
|
+
- @equinor/fusion-framework-cli@15.2.8-next.0
|
|
100
|
+
- @equinor/fusion-framework-module@6.1.3-next.0
|
|
101
|
+
- @equinor/fusion-framework-module-app@8.1.0-next.0
|
|
102
|
+
- @equinor/fusion-framework-react@9.0.0-next.0
|
|
103
|
+
- @equinor/fusion-framework-react-module@4.0.3-next.0
|
|
104
|
+
- @equinor/fusion-imports@2.0.3-next.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Equinor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# @equinor/fusion-framework-vitest-plugin-react-app
|
|
2
|
+
|
|
3
|
+
Vite plugin and Vitest helpers for testing Fusion Framework React applications inside a real,
|
|
4
|
+
mock-backed application module scope — the same `FrameworkProvider` + `ModuleProvider` nesting
|
|
5
|
+
`renderApp`/`createComponent` wire up in production, with the real `event`/`http`/`msal` module
|
|
6
|
+
pipeline and only the network boundary faked.
|
|
7
|
+
|
|
8
|
+
> **Note:** Requires `vitest-browser-react` (peer dependency) and Vitest's
|
|
9
|
+
> [Browser Mode](https://vitest.dev/guide/browser/) configured for the project — these render
|
|
10
|
+
> helpers mount real components in a real browser, not `happy-dom`/`jsdom`.
|
|
11
|
+
|
|
12
|
+
Use this package when you need to:
|
|
13
|
+
|
|
14
|
+
- **Render a component or hook against real application modules** — `useAppModule`,
|
|
15
|
+
`useAccessToken`, `useCurrentContext`, `useCurrentBookmark`, `useAppSetting`, and similar
|
|
16
|
+
hooks resolve for real, instead of being hand-mocked per test.
|
|
17
|
+
- **Test against the application's own manifest, config, and module-configurator** — the same
|
|
18
|
+
resolution pipeline `ffc app build`/`ffc app dev` use, served as virtual modules by
|
|
19
|
+
`appTestVitePlugin`.
|
|
20
|
+
- **Share seeded fixture defaults across a test file** — `testApp`, a `vitest` `test` extended
|
|
21
|
+
with `env`/`configure`/`app`/`render`/`renderHook` fixtures, instead of repeating options on
|
|
22
|
+
every call. Each test still gets its own fresh `fusion`/`app` instances; only the seeded
|
|
23
|
+
defaults are shared, not state between tests.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pnpm add -D vitest playwright @vitest/browser-playwright vitest-browser-react \
|
|
29
|
+
@equinor/fusion-framework-vitest-plugin-react-app
|
|
30
|
+
pnpm exec playwright install chromium
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
React, React DOM, RxJS, and Vite are peer dependencies normally supplied by the app.
|
|
34
|
+
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
| Task | Guide |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| Understand the package and choose a subject | [Overview](docs/overview.md) |
|
|
40
|
+
| Install, configure, and run the first app test | [Getting started](docs/getting-started.md) |
|
|
41
|
+
| Override Vitest defaults or resolve non-standard app files | [Configuration](docs/configuration.md) |
|
|
42
|
+
| Compose fixtures, explicit render options, and app lifecycle tests | [Advanced usage](docs/advanced.md) |
|
|
43
|
+
| Diagnose setup, browser, app-resolution, and network failures | [Troubleshooting](docs/troubleshooting.md) |
|
|
44
|
+
| Seed authentication, context, bookmarks, feature flags, HTTP, analytics, and telemetry | [Module mocks](docs/module-mocks.md) |
|
|
45
|
+
| Understand the Browser Mode default or choose another renderer | [Why Browser Mode is the default](docs/why-browser-mode.md) |
|
|
46
|
+
| Move an existing `@testing-library/react`/jsdom suite onto this package | [Migrate an existing app](docs/migrating-an-existing-app.md) |
|
|
47
|
+
| Choose between app, framework, module, HTTP, and runner-level tests | [Choose a testing layer](../../framework/docs/testing-choosing-a-layer.md) |
|
|
48
|
+
|
|
49
|
+
## Overview
|
|
50
|
+
|
|
51
|
+
Any hook or component that reads from the application module scope — `useAppModule`,
|
|
52
|
+
`useAccessToken`, `useCurrentContext`, `useCurrentBookmark`, `useAppSetting`, and so on — or
|
|
53
|
+
from the parent framework via `useFramework`, needs to run inside the same provider nesting
|
|
54
|
+
`renderApp`/`createComponent` wire up in production: a `FrameworkProvider` (the parent Fusion
|
|
55
|
+
instance) around a `ModuleProvider` (the app's own modules).
|
|
56
|
+
|
|
57
|
+
`renderAppHook` and `renderAppComponent` build that nesting for you, using `mockFramework`
|
|
58
|
+
(`@equinor/fusion-framework/mock`) and `mockAppModules` (`@equinor/fusion-framework-app/mock`) —
|
|
59
|
+
the **real** `event`/`http`/`msal` module pipeline. Only requests a seeded middleware answers
|
|
60
|
+
are faked; a request with no matching middleware still reaches the real network. This means:
|
|
61
|
+
|
|
62
|
+
- MSAL signs in a default "Test User" with zero configuration — `useAccessToken`/`useToken`
|
|
63
|
+
resolve a real, structurally-valid (unsigned) JWT out of the box.
|
|
64
|
+
- `event` dispatches and listens for real `FrameworkEvent`s.
|
|
65
|
+
- Anything that talks to an HTTP endpoint (e.g. an app's own manifest, settings) is answered
|
|
66
|
+
by a mock client rather than a real network call — see each hook's own docs for what's
|
|
67
|
+
pre-wired versus what you need to seed yourself (e.g. via `enableBookmarkMock`,
|
|
68
|
+
`enableContextMock`, `enableFeatureFlagMock`, or a `configurator.http.addMiddleware(...)`
|
|
69
|
+
router).
|
|
70
|
+
|
|
71
|
+
Use `renderAppHook` for a hook in isolation; use `renderAppComponent` when you need to assert
|
|
72
|
+
on rendered output (e.g. loading/error states, DOM structure); use `testApp` or the `/test`
|
|
73
|
+
entry-point's `test`/`render` when several cases in a file share one seeded scope.
|
|
74
|
+
|
|
75
|
+
## Entry points
|
|
76
|
+
|
|
77
|
+
- **`@equinor/fusion-framework-vitest-plugin-react-app`** — `appTestVitePlugin`,
|
|
78
|
+
`renderAppComponent`, `renderAppHook`, `testApp`. No app-specific resolution; you pass
|
|
79
|
+
`env`/`configure` yourself.
|
|
80
|
+
- **`@equinor/fusion-framework-vitest-plugin-react-app/test`** — `test`, `render`. Require
|
|
81
|
+
`appTestVitePlugin` registered in `vitest.config.ts`; `env`/`configure` are resolved
|
|
82
|
+
automatically from the application's own manifest, config, and module-configurator.
|
|
83
|
+
- **`@equinor/fusion-framework-vitest-plugin-react-app/config`** — `defineProject`. Registers
|
|
84
|
+
`appTestVitePlugin`, headless Playwright/Chromium, test-file inclusion, and lazy-import
|
|
85
|
+
warmup while accepting ordinary Vitest configuration overrides.
|
|
86
|
+
|
|
87
|
+
## Quick start
|
|
88
|
+
|
|
89
|
+
Use the browser-ready project config in your app's `vitest.config.ts`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
93
|
+
|
|
94
|
+
export default defineProject();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then use the pre-seeded `test` fixture — no per-test `env`/`configure` wiring:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { expect } from 'vitest';
|
|
101
|
+
import { test } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
102
|
+
import { App } from '../App';
|
|
103
|
+
|
|
104
|
+
test('renders the app', async ({ render }) => {
|
|
105
|
+
const screen = await render(<App />);
|
|
106
|
+
await expect.element(screen.getByRole('heading')).toBeVisible();
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or, in a plain `describe`/`it` file, the equivalent `render` function:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { describe, expect, it } from 'vitest';
|
|
114
|
+
import { render } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
115
|
+
import { App } from '../App';
|
|
116
|
+
|
|
117
|
+
describe('App', () => {
|
|
118
|
+
it('renders the app', async () => {
|
|
119
|
+
const { getByRole } = await render(<App />);
|
|
120
|
+
await expect.element(getByRole('heading')).toBeVisible();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## renderAppHook
|
|
126
|
+
|
|
127
|
+
Renders a hook inside a real, mock-backed application module scope.
|
|
128
|
+
|
|
129
|
+
**Signature:**
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
function renderAppHook<Result, Props = undefined, TModules = unknown, TEnv extends AppEnv = AppEnv>(
|
|
133
|
+
render: (initialProps?: Props) => Result,
|
|
134
|
+
options?: {
|
|
135
|
+
configure?: AppMockConfigureFn<TModules, TEnv>;
|
|
136
|
+
env?: TEnv;
|
|
137
|
+
fusion?: Fusion;
|
|
138
|
+
} & Omit<RenderHookOptions<Props>, 'wrapper'>,
|
|
139
|
+
): Promise<RenderHookResult<Result, Props> & { fusion: { framework: Fusion; app: AppModulesInstance<TModules> } }>;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Option | Description |
|
|
143
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------|
|
|
144
|
+
| `configure` | Callback forwarded to `mockAppModules` for seeding app-scope modules (bookmark, context, feature-flag, msal, etc.) |
|
|
145
|
+
| `env` | The application environment (manifest); defaults to a generic standalone `test-app` |
|
|
146
|
+
| `fusion` | The parent Fusion instance; defaults to a fresh `mockFramework` instance serving this app's own manifest |
|
|
147
|
+
|
|
148
|
+
Any other `renderHook` option (e.g. `initialProps`) is forwarded as-is. The result carries the
|
|
149
|
+
usual `renderHook` return values (`result`, `rerender`, `unmount`) plus `fusion` — the same
|
|
150
|
+
instances the hook rendered against — for driving a module the hook itself doesn't return.
|
|
151
|
+
|
|
152
|
+
### Basic usage
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { renderAppHook } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
156
|
+
import { useAccessToken } from '@equinor/fusion-framework-react-app/msal';
|
|
157
|
+
|
|
158
|
+
test('resolves an access token', async () => {
|
|
159
|
+
const { result } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }));
|
|
160
|
+
await vi.waitFor(() => expect(result.current.pending).toBe(false));
|
|
161
|
+
expect(result.current.token).toBeDefined();
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Sign in a named user
|
|
166
|
+
|
|
167
|
+
Pass `configure` to reach the msal mock's builder before the hook renders:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { renderAppHook } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
171
|
+
import { useCurrentAccount } from '@equinor/fusion-framework-react-app/msal';
|
|
172
|
+
|
|
173
|
+
test('reads the configured account', async () => {
|
|
174
|
+
const { result } = await renderAppHook(() => useCurrentAccount(), {
|
|
175
|
+
configure: (configurator) => configurator.msal.setAccount({ name: 'Ada Lovelace' }),
|
|
176
|
+
});
|
|
177
|
+
expect(result.current).toMatchObject({ name: 'Ada Lovelace' });
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Reuse a pre-built parent Fusion instance
|
|
182
|
+
|
|
183
|
+
Build the `fusion` instance yourself when a test needs to pre-configure parent-level modules
|
|
184
|
+
(`http`, `context`, `serviceDiscovery`) or share one instance across multiple render calls:
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
import { mockFramework } from '@equinor/fusion-framework/mock';
|
|
188
|
+
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
|
|
189
|
+
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
190
|
+
import { renderAppHook } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
191
|
+
import { useAccessToken, useCurrentAccount } from '@equinor/fusion-framework-react-app/msal';
|
|
192
|
+
|
|
193
|
+
const env = { manifest: { appKey: 'test-app', displayName: 'Test App', description: '', type: 'standalone' as const } };
|
|
194
|
+
|
|
195
|
+
test('shares one fusion instance across two renders', async () => {
|
|
196
|
+
const fusion = await mockFramework<[AppModule]>((configurator) =>
|
|
197
|
+
enableAppManifestMock(configurator, env),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const { result: a } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }), { fusion });
|
|
201
|
+
const { result: b } = await renderAppHook(() => useCurrentAccount(), { fusion });
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## renderAppComponent
|
|
206
|
+
|
|
207
|
+
Renders a component inside the same real, mock-backed application module scope as
|
|
208
|
+
`renderAppHook`. Use this when you need to assert on rendered output rather than a hook's
|
|
209
|
+
return value — e.g. a component with its own loading/error states.
|
|
210
|
+
|
|
211
|
+
**Signature:**
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
function renderAppComponent<TModules = unknown, TEnv extends AppEnv = AppEnv>(
|
|
215
|
+
ui: ReactElement,
|
|
216
|
+
options?: {
|
|
217
|
+
configure?: AppMockConfigureFn<TModules, TEnv>;
|
|
218
|
+
env?: TEnv;
|
|
219
|
+
fusion?: Fusion;
|
|
220
|
+
} & Omit<RenderOptions, 'wrapper'>,
|
|
221
|
+
): Promise<RenderResult & { fusion: { framework: Fusion; app: AppModulesInstance<TModules> } }>;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Options are the same shape as `renderAppHook`'s — `configure`, `env`, `fusion` — plus any other
|
|
225
|
+
`vitest-browser-react` `render` option. The result carries the usual `render` return values
|
|
226
|
+
(`getByText`, `container`, `unmount`, ...) plus `fusion` — nested rather than spread directly
|
|
227
|
+
onto the result, so `vitest-browser-react`'s own return shape stays free to evolve without ever
|
|
228
|
+
colliding with it. `fusion.app` is the same application module instance the rendered component
|
|
229
|
+
reads through `useAppModule`/`useAppModules`, and `fusion.framework` is the parent Fusion
|
|
230
|
+
instance. Drive a module directly through `fusion.app` to exercise a state change after the
|
|
231
|
+
initial render, without hand-wiring `mockAppModules`/`ModuleProvider`:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
|
|
235
|
+
import type { ContextModule } from '@equinor/fusion-framework-module-context';
|
|
236
|
+
import { act } from 'react';
|
|
237
|
+
import { renderAppComponent } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
238
|
+
|
|
239
|
+
test('reacts when the current context switches', async () => {
|
|
240
|
+
const { getByText, fusion } = await renderAppComponent<[ContextModule]>(<App />, {
|
|
241
|
+
configure: (configurator) =>
|
|
242
|
+
enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await act(() => fusion.app.context.setCurrentContextByIdAsync(projectB.id));
|
|
246
|
+
await expect.element(getByText(/project-b/)).toBeVisible();
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Example: asserting loading and error states
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
import { mockFramework } from '@equinor/fusion-framework/mock';
|
|
254
|
+
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
|
|
255
|
+
import type { AppManifest, AppModule } from '@equinor/fusion-framework-module-app';
|
|
256
|
+
import { renderAppComponent } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
257
|
+
import { Apploader } from '@equinor/fusion-framework-react-app/apploader';
|
|
258
|
+
|
|
259
|
+
test('mounts the child app once its script loads', async () => {
|
|
260
|
+
const manifest: AppManifest = {
|
|
261
|
+
appKey: 'child-app',
|
|
262
|
+
displayName: 'Child App',
|
|
263
|
+
description: 'A child application',
|
|
264
|
+
type: 'standalone',
|
|
265
|
+
build: { version: '1.0.0', entryPoint: 'child-script.ts', assetPath: '' },
|
|
266
|
+
};
|
|
267
|
+
const fusion = await mockFramework<[AppModule]>((configurator) =>
|
|
268
|
+
enableAppManifestMock(configurator, { manifest }, someFixturesUri),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const { container } = await renderAppComponent(<Apploader appKey="child-app" />, { fusion });
|
|
272
|
+
|
|
273
|
+
// the loading state renders synchronously, before the script's dynamic import resolves
|
|
274
|
+
expect(container.textContent).toContain('Loading child-app');
|
|
275
|
+
await vi.waitFor(() => expect(container.textContent).toContain('mounted: child-app'));
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## testApp
|
|
280
|
+
|
|
281
|
+
`vitest`'s `test`, extended with an application module scope fixture — an alternative to
|
|
282
|
+
`renderAppComponent`/`renderAppHook` for a test file whose cases share one mocked scope:
|
|
283
|
+
`env`/`configure` become suite-level concerns, overridden once per file (or per `describe`
|
|
284
|
+
block) with `testApp.extend(...)`, rather than an options object repeated on every call.
|
|
285
|
+
`fusion`/`app` resolve lazily — a test that only destructures `app` never pays for rendering
|
|
286
|
+
anything, and one that only destructures `render`/`renderHook` gets the same mocked scope
|
|
287
|
+
wired in automatically.
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
import { testApp } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
291
|
+
|
|
292
|
+
testApp('resolves current context', async ({ app, render }) => {
|
|
293
|
+
const screen = await render(<App />);
|
|
294
|
+
expect(app.context).toBeDefined();
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Seed a module for every test in a suite:
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
describe('with a seeded context module', () => {
|
|
302
|
+
const test = testApp.extend('configure', { injected: true }, () =>
|
|
303
|
+
(configurator) => enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
test('starts on the seeded context', async ({ render }) => {
|
|
307
|
+
const screen = await render(<App />);
|
|
308
|
+
await expect.element(screen.getByText(projectA.title)).toBeVisible();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## appTestVitePlugin
|
|
314
|
+
|
|
315
|
+
A plain Vite plugin — Vitest configs are Vite configs, so this registers directly in your own
|
|
316
|
+
`vitest.config.ts`, no CLI command required. It serves an application's manifest/config
|
|
317
|
+
(resolved the same way `ffc app build`/`ffc app dev` do) and its own module-configurator
|
|
318
|
+
export as virtual modules, so the `/test` entry-point's `test`/`render` need no per-test
|
|
319
|
+
`env`/`configure` wiring.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
appTestVitePlugin({
|
|
323
|
+
entrypoint?: string; // defaults to the resolved Vite/Vitest project root
|
|
324
|
+
manifest?: string | AppManifestFn;
|
|
325
|
+
config?: string | AppConfigFn;
|
|
326
|
+
configure?: string; // defaults to the first of src/config.ts, src/config.tsx, src/config.js
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`entrypoint`, `manifest`, and `config` are forwarded to the same manifest/config resolution
|
|
331
|
+
`ffc app build`/`ffc app dev` use (a base manifest generated from `package.json`, merged with a
|
|
332
|
+
local `app.manifest.ts` if one exists, plus `app.config.ts` for endpoints). `configure` points
|
|
333
|
+
at the application's module-configurator file — unlike `manifest`/`config`, it must be a real
|
|
334
|
+
file on disk, since it's re-exported as live application code into the test bundle rather than
|
|
335
|
+
JSON-serialized data. An explicitly requested `manifest`/`config`/`configure` file that doesn't
|
|
336
|
+
exist throws `FileNotFoundError`; the convention-based lookups fail silently and fall back to
|
|
337
|
+
defaults instead.
|
|
338
|
+
|
|
339
|
+
## Notes
|
|
340
|
+
|
|
341
|
+
- Every render/hook helper is `async` — always `await` it, since the mocked application module
|
|
342
|
+
scope (app manifest, module initialization) resolves asynchronously before the first render.
|
|
343
|
+
- Seeding a module beyond the default set (bookmark, context, feature-flag) requires its own
|
|
344
|
+
mock enabler passed through `configure` — e.g. `enableBookmarkMock`, `enableContextMock`,
|
|
345
|
+
`enableFeatureFlagMock` from that module's own `/mock` sub-path.
|
|
346
|
+
- `msal` ships enabled by default with a signed-in "Test User" — no `configure` needed unless
|
|
347
|
+
a test cares about a specific account or a signed-out state.
|
|
348
|
+
- Prefer these helpers over hand-wiring `mockFramework`, `mockAppModules`, `FrameworkProvider`,
|
|
349
|
+
and `ModuleProvider` directly in every test.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { renderAppHook, } from './render-app-hook';
|
|
2
|
+
export { renderAppComponent, } from './render-app-component';
|
|
3
|
+
export { testApp } from './test-app';
|
|
4
|
+
// `test`/`render` import virtual modules only served once `appTestVitePlugin`
|
|
5
|
+
// (@equinor/fusion-framework-vitest-plugin-react-app) is registered — using any export from
|
|
6
|
+
// this module requires the plugin registered in your `vitest.config.ts` `plugins`.
|
|
7
|
+
export { test } from './test';
|
|
8
|
+
export { render } from './render';
|
|
9
|
+
//# sourceMappingURL=app-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-test.js","sourceRoot":"","sources":["../../src/app-test.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAGd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,GAGnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrC,8EAA8E;AAC9E,4FAA4F;AAC5F,mFAAmF;AACnF,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { playwright } from '@vitest/browser-playwright';
|
|
2
|
+
import { defineProject as defineVitestProject, mergeConfig, } from 'vitest/config';
|
|
3
|
+
import { appTestVitePlugin } from './index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds a ready-to-export Vitest project config for testing a Fusion Framework React app:
|
|
6
|
+
* registers {@link appTestVitePlugin} (with its own defaults) and wires up Vitest browser mode
|
|
7
|
+
* with the Playwright/`chromium` provider, so a consuming app's own `vitest.config.ts` needs no
|
|
8
|
+
* browser-provider boilerplate of its own.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Drop-in replacement for Vitest's own `defineProject` — same `export default`, just pre-wired.
|
|
12
|
+
* Playwright/`chromium` is only the *default* — pass `override` to change or replace anything
|
|
13
|
+
* (e.g. `test.name`, or swap `test.browser.provider` for a different `@vitest/browser-*`
|
|
14
|
+
* provider) without hand-rolling `appTestVitePlugin`'s own wiring.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
19
|
+
* import { name, version } from './package.json' with { type: 'json' };
|
|
20
|
+
*
|
|
21
|
+
* export default defineProject({ test: { name: `${name}@${version}` } });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* Overriding the browser provider:
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { webdriverio } from '@vitest/browser-webdriverio';
|
|
28
|
+
*
|
|
29
|
+
* export default defineProject({ test: { browser: { provider: webdriverio() } } });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @param override - Deep-merged onto the default config (a plain object), or applied to it
|
|
33
|
+
* outright (a function receiving the default config); see {@link AppTestConfigOverride}.
|
|
34
|
+
* @returns A Vitest project config, ready to `export default`.
|
|
35
|
+
*/
|
|
36
|
+
export const defineProject = (override) => {
|
|
37
|
+
const defaults = {
|
|
38
|
+
plugins: [appTestVitePlugin()],
|
|
39
|
+
test: {
|
|
40
|
+
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
|
41
|
+
browser: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
provider: playwright(),
|
|
44
|
+
headless: true,
|
|
45
|
+
instances: [{ browser: 'chromium' }],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
// pre-transforms all source up front so deps only reached via lazy/code-split imports
|
|
49
|
+
// (e.g. route components) are discovered before the first test request, not mid-run —
|
|
50
|
+
// the latter forces Vite to reload the page and fails the in-flight test file import
|
|
51
|
+
server: { warmup: { clientFiles: ['src/**/*.{ts,tsx}'] } },
|
|
52
|
+
// same reasoning as `server.warmup` above, but for the esbuild dep scanner: without this,
|
|
53
|
+
// its default entry detection can miss code-split route files entirely, so a package only
|
|
54
|
+
// ever imported from one of those (e.g. react-router's own deps) is discovered mid-run
|
|
55
|
+
// instead of up front — statically crawling every source file (which esbuild's scanner
|
|
56
|
+
// follows through dynamic imports too) avoids that with no per-package name needed
|
|
57
|
+
optimizeDeps: { entries: ['src/**/*.{ts,tsx}'] },
|
|
58
|
+
};
|
|
59
|
+
// a function replaces the config outright; a plain object deep-merges onto it via Vite's own mergeConfig
|
|
60
|
+
const resolved = typeof override === 'function'
|
|
61
|
+
? override(defaults)
|
|
62
|
+
: override
|
|
63
|
+
? mergeConfig(defaults, override)
|
|
64
|
+
: defaults;
|
|
65
|
+
return defineVitestProject(resolved);
|
|
66
|
+
};
|
|
67
|
+
export default defineProject;
|
|
68
|
+
//# sourceMappingURL=define-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-project.js","sourceRoot":"","sources":["../../src/define-project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EACL,aAAa,IAAI,mBAAmB,EACpC,WAAW,GAEZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAW/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAgC,EAAuB,EAAE;IACrF,MAAM,QAAQ,GAAwB;QACpC,OAAO,EAAE,CAAC,iBAAiB,EAAE,CAAC;QAC9B,IAAI,EAAE;YACJ,OAAO,EAAE,CAAC,+BAA+B,CAAC;YAC1C,OAAO,EAAE;gBACP,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,UAAU,EAAE;gBACtB,QAAQ,EAAE,IAAI;gBACd,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;aACrC;SACF;QACD,sFAAsF;QACtF,sFAAsF;QACtF,qFAAqF;QACrF,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,mBAAmB,CAAC,EAAE,EAAE;QAC1D,0FAA0F;QAC1F,0FAA0F;QAC1F,uFAAuF;QACvF,uFAAuF;QACvF,mFAAmF;QACnF,YAAY,EAAE,EAAE,OAAO,EAAE,CAAC,mBAAmB,CAAC,EAAE;KACjD,CAAC;IACF,yGAAyG;IACzG,MAAM,QAAQ,GACZ,OAAO,QAAQ,KAAK,UAAU;QAC5B,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACpB,CAAC,CAAC,QAAQ;YACR,CAAC,CAAE,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAyB;YAC1D,CAAC,CAAC,QAAQ,CAAC;IACjB,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AACvC,CAAC,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { FileNotFoundError } from '@equinor/fusion-imports';
|
|
4
|
+
import { resolveAppTestEnv } from './resolve-app-test-env.js';
|
|
5
|
+
const ENV_MODULE_ID = 'virtual:fusion-app-test-env';
|
|
6
|
+
const CONFIGURE_MODULE_ID = 'virtual:fusion-app-test-configure';
|
|
7
|
+
const RESOLVED_ENV_MODULE_ID = `\0${ENV_MODULE_ID}`;
|
|
8
|
+
const RESOLVED_CONFIGURE_MODULE_ID = `\0${CONFIGURE_MODULE_ID}`;
|
|
9
|
+
const DEFAULT_CONFIGURE_CANDIDATES = ['src/config.ts', 'src/config.tsx', 'src/config.js'];
|
|
10
|
+
/**
|
|
11
|
+
* Vite plugin serving an application's manifest/config (resolved the same way `ffc app build`/
|
|
12
|
+
* `ffc app dev` do) and its own module-configurator export as virtual modules, so
|
|
13
|
+
* `@equinor/fusion-framework-vitest-plugin-react-app/test`'s `test`/`render` need no per-test
|
|
14
|
+
* `env`/`configure` wiring.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* A plain Vite plugin — Vitest configs are Vite configs, so this registers directly in your
|
|
18
|
+
* own `vitest.config.ts`, no CLI command required:
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { defineConfig } from 'vitest/config';
|
|
21
|
+
* import { appTestVitePlugin } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
22
|
+
*
|
|
23
|
+
* export default defineConfig({
|
|
24
|
+
* plugins: [appTestVitePlugin()],
|
|
25
|
+
* // ...your own browser-mode config
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
* Exposes two virtual modules: `virtual:fusion-app-test-env` (`manifest`/`config`, as JSON) and
|
|
29
|
+
* `virtual:fusion-app-test-configure` (a re-export of the resolved `configure` module, or
|
|
30
|
+
* `undefined` if none exists). Not intended to be imported directly by application code.
|
|
31
|
+
*
|
|
32
|
+
* @param options - Resolution options; `entrypoint` defaults to the current working directory.
|
|
33
|
+
* @returns A Vite plugin instance.
|
|
34
|
+
*/
|
|
35
|
+
export const appTestVitePlugin = (options) => {
|
|
36
|
+
const explicitEntrypoint = options?.entrypoint;
|
|
37
|
+
let entrypoint = explicitEntrypoint;
|
|
38
|
+
let configureModulePath = explicitEntrypoint
|
|
39
|
+
? resolveConfigureModulePath(explicitEntrypoint, options?.configure)
|
|
40
|
+
: undefined;
|
|
41
|
+
return {
|
|
42
|
+
name: 'fusion:app-test',
|
|
43
|
+
configResolved(config) {
|
|
44
|
+
// Vitest resolves each workspace project's root from its config file, which identifies the app containing src/index.ts.
|
|
45
|
+
entrypoint ??= config.root;
|
|
46
|
+
configureModulePath ??= resolveConfigureModulePath(entrypoint, options?.configure);
|
|
47
|
+
},
|
|
48
|
+
resolveId(id) {
|
|
49
|
+
// claim only our two virtual specifiers, leave everything else to the normal resolvers
|
|
50
|
+
if (id === ENV_MODULE_ID)
|
|
51
|
+
return RESOLVED_ENV_MODULE_ID;
|
|
52
|
+
// second virtual specifier, same rule as above
|
|
53
|
+
if (id === CONFIGURE_MODULE_ID)
|
|
54
|
+
return RESOLVED_CONFIGURE_MODULE_ID;
|
|
55
|
+
return null;
|
|
56
|
+
},
|
|
57
|
+
async load(id) {
|
|
58
|
+
// serves manifest/config resolved lazily here, not at plugin-creation time, so options.entrypoint changes between test runs are respected
|
|
59
|
+
if (id === RESOLVED_ENV_MODULE_ID) {
|
|
60
|
+
const { manifest, config } = await resolveAppTestEnv({ ...options, entrypoint });
|
|
61
|
+
return [
|
|
62
|
+
`export const manifest = ${JSON.stringify(manifest)};`,
|
|
63
|
+
`export const config = ${JSON.stringify(config)};`,
|
|
64
|
+
].join('\n');
|
|
65
|
+
}
|
|
66
|
+
// no conventional module means the app registers no extra modules, same as omitting `configure` from `makeComponent`
|
|
67
|
+
if (id === RESOLVED_CONFIGURE_MODULE_ID) {
|
|
68
|
+
return configureModulePath
|
|
69
|
+
? `export { default as configure } from ${JSON.stringify(configureModulePath)};`
|
|
70
|
+
: 'export const configure = undefined;';
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Resolves the app's module-configurator file: the explicit `file` if given, otherwise the
|
|
78
|
+
* first existing candidate in {@link DEFAULT_CONFIGURE_CANDIDATES}.
|
|
79
|
+
*
|
|
80
|
+
* @throws {@link FileNotFoundError} If an explicitly requested `file` does not exist — unlike
|
|
81
|
+
* the convention-based lookup, a typo here should fail loudly instead of silently running the
|
|
82
|
+
* test suite without the application's modules.
|
|
83
|
+
*/
|
|
84
|
+
const resolveConfigureModulePath = (cwd, file) => {
|
|
85
|
+
// an explicit path is a user request, not a convention lookup, so a typo must fail loudly
|
|
86
|
+
if (file) {
|
|
87
|
+
const resolved = resolve(cwd, file);
|
|
88
|
+
// fail fast rather than silently falling back to "no configurator"
|
|
89
|
+
if (!existsSync(resolved)) {
|
|
90
|
+
throw new FileNotFoundError(`Configure module not found: ${resolved}`);
|
|
91
|
+
}
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
// first candidate that exists wins; none existing is a valid "no configurator" state
|
|
95
|
+
const found = DEFAULT_CONFIGURE_CANDIDATES.find((candidate) => existsSync(resolve(cwd, candidate)));
|
|
96
|
+
return found ? resolve(cwd, found) : undefined;
|
|
97
|
+
};
|
|
98
|
+
export default appTestVitePlugin;
|
|
99
|
+
//# sourceMappingURL=index.js.map
|