@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,6 @@
1
+ export { renderAppHook, type RenderAppHookOptions, type RenderAppHookResult, } from './render-app-hook';
2
+ export { renderAppComponent, type RenderAppComponentOptions, type RenderAppComponentResult, } from './render-app-component';
3
+ export { testApp } from './test-app';
4
+ export type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
5
+ export { test } from './test';
6
+ export { render } from './render';
@@ -0,0 +1,41 @@
1
+ import { type UserWorkspaceConfig } from 'vitest/config';
2
+ /**
3
+ * A config override for {@link defineProject}: an object is deep-merged onto the default
4
+ * config via Vite's own `mergeConfig`; a function receives the default config and returns the
5
+ * config used outright, for changes `mergeConfig` can't express (e.g. removing a field).
6
+ */
7
+ export type AppTestConfigOverride = Partial<UserWorkspaceConfig> | ((config: UserWorkspaceConfig) => UserWorkspaceConfig);
8
+ /**
9
+ * Builds a ready-to-export Vitest project config for testing a Fusion Framework React app:
10
+ * registers {@link appTestVitePlugin} (with its own defaults) and wires up Vitest browser mode
11
+ * with the Playwright/`chromium` provider, so a consuming app's own `vitest.config.ts` needs no
12
+ * browser-provider boilerplate of its own.
13
+ *
14
+ * @remarks
15
+ * Drop-in replacement for Vitest's own `defineProject` — same `export default`, just pre-wired.
16
+ * Playwright/`chromium` is only the *default* — pass `override` to change or replace anything
17
+ * (e.g. `test.name`, or swap `test.browser.provider` for a different `@vitest/browser-*`
18
+ * provider) without hand-rolling `appTestVitePlugin`'s own wiring.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
23
+ * import { name, version } from './package.json' with { type: 'json' };
24
+ *
25
+ * export default defineProject({ test: { name: `${name}@${version}` } });
26
+ * ```
27
+ *
28
+ * @example
29
+ * Overriding the browser provider:
30
+ * ```ts
31
+ * import { webdriverio } from '@vitest/browser-webdriverio';
32
+ *
33
+ * export default defineProject({ test: { browser: { provider: webdriverio() } } });
34
+ * ```
35
+ *
36
+ * @param override - Deep-merged onto the default config (a plain object), or applied to it
37
+ * outright (a function receiving the default config); see {@link AppTestConfigOverride}.
38
+ * @returns A Vitest project config, ready to `export default`.
39
+ */
40
+ export declare const defineProject: (override?: AppTestConfigOverride) => UserWorkspaceConfig;
41
+ export default defineProject;
@@ -0,0 +1,45 @@
1
+ import type { Plugin } from 'vite';
2
+ import { type ResolveAppTestEnvOptions } from './resolve-app-test-env.js';
3
+ /**
4
+ * Options for {@link appTestVitePlugin}.
5
+ */
6
+ export type AppTestVitePluginOptions = ResolveAppTestEnvOptions & {
7
+ /**
8
+ * Path (relative to `entrypoint`) to the app's module-configurator export, mirroring the
9
+ * `configure` argument passed to `makeComponent` in the app's own entry point. Defaults to the
10
+ * first of `src/config.ts`, `src/config.tsx`, `src/config.js` that exists.
11
+ *
12
+ * @remarks
13
+ * Unlike `manifest`/`config`, this can't be an inline function: it's live application code
14
+ * (with its own imports and closures) re-exported as-is into the test bundle rather than
15
+ * JSON-serialized data, so Vite needs a real file on disk to resolve and transform.
16
+ */
17
+ configure?: string;
18
+ };
19
+ /**
20
+ * Vite plugin serving an application's manifest/config (resolved the same way `ffc app build`/
21
+ * `ffc app dev` do) and its own module-configurator export as virtual modules, so
22
+ * `@equinor/fusion-framework-vitest-plugin-react-app/test`'s `test`/`render` need no per-test
23
+ * `env`/`configure` wiring.
24
+ *
25
+ * @remarks
26
+ * A plain Vite plugin — Vitest configs are Vite configs, so this registers directly in your
27
+ * own `vitest.config.ts`, no CLI command required:
28
+ * ```ts
29
+ * import { defineConfig } from 'vitest/config';
30
+ * import { appTestVitePlugin } from '@equinor/fusion-framework-vitest-plugin-react-app';
31
+ *
32
+ * export default defineConfig({
33
+ * plugins: [appTestVitePlugin()],
34
+ * // ...your own browser-mode config
35
+ * });
36
+ * ```
37
+ * Exposes two virtual modules: `virtual:fusion-app-test-env` (`manifest`/`config`, as JSON) and
38
+ * `virtual:fusion-app-test-configure` (a re-export of the resolved `configure` module, or
39
+ * `undefined` if none exists). Not intended to be imported directly by application code.
40
+ *
41
+ * @param options - Resolution options; `entrypoint` defaults to the current working directory.
42
+ * @returns A Vite plugin instance.
43
+ */
44
+ export declare const appTestVitePlugin: (options?: AppTestVitePluginOptions) => Plugin;
45
+ export default appTestVitePlugin;
@@ -0,0 +1,86 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { RenderOptions, RenderResult } from 'vitest-browser-react';
3
+ import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
4
+ import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
5
+ import type { Fusion } from '@equinor/fusion-framework';
6
+ import type { AnyModule } from '@equinor/fusion-framework-module';
7
+ /**
8
+ * Options for {@link renderAppComponent}.
9
+ *
10
+ * @template TModules - Module descriptors beyond the default set.
11
+ * @template TEnv - The application environment descriptor.
12
+ */
13
+ export interface RenderAppComponentOptions<TModules extends Array<AnyModule> | unknown = unknown, TEnv extends AppEnv = AppEnv> extends Omit<RenderOptions, 'wrapper'> {
14
+ /** Configuration callback forwarded to {@link mockAppModules}. */
15
+ configure?: AppMockConfigureFn<TModules, TEnv>;
16
+ /** The application environment; defaults to a generic standalone test app. */
17
+ env?: TEnv;
18
+ /**
19
+ * The parent Fusion instance; defaults to a fresh {@link mockFramework} instance with
20
+ * this app's own manifest served. Pass one built beforehand to reuse a single instance
21
+ * across multiple render calls, or to pre-configure parent-level modules (e.g. `http`,
22
+ * `context`, `serviceDiscovery`, or `app` itself for a component that loads another app).
23
+ */
24
+ fusion?: Fusion;
25
+ }
26
+ /**
27
+ * The result of {@link renderAppComponent}: the `vitest-browser-react` render result,
28
+ * plus the resolved application module scope and its parent Fusion instance.
29
+ *
30
+ * @template TModules - Module descriptors beyond the default set.
31
+ */
32
+ export interface RenderAppComponentResult<TModules extends Array<AnyModule> | unknown = unknown> extends RenderResult {
33
+ /**
34
+ * The Fusion instances backing the rendered component, nested under this single key so
35
+ * `vitest-browser-react`'s own `RenderResult` fields stay free to evolve without ever
36
+ * colliding with it.
37
+ */
38
+ fusion: {
39
+ /** The parent Fusion instance the component's `FrameworkProvider` was given. */
40
+ framework: Fusion;
41
+ /**
42
+ * The resolved application module instance backing the rendered component — the same
43
+ * instance a real app would read via `useAppModule`/`useAppModules`. Drive a module
44
+ * directly (e.g. `fusion.app.context.setCurrentContextByIdAsync(id)`) to exercise a
45
+ * state change after the initial render, then assert the component re-rendered accordingly.
46
+ */
47
+ app: AppModulesInstance<TModules>;
48
+ };
49
+ }
50
+ /**
51
+ * Renders a component inside a real, mock-backed application module scope.
52
+ *
53
+ * @remarks
54
+ * Wraps `vitest-browser-react`'s `render` with the same provider nesting
55
+ * `createComponent` uses in production — a `FrameworkProvider` (the parent Fusion
56
+ * instance, from `mockFramework`) around a `ModuleProvider` (this app's own modules,
57
+ * from `mockAppModules`, `@equinor/fusion-framework-app/mock`). See {@link renderAppHook}
58
+ * for the equivalent helper when testing a hook in isolation, rather than a component.
59
+ *
60
+ * @template TModules - Module descriptors beyond the default set.
61
+ * @template TEnv - The application environment descriptor.
62
+ * @param ui - The component to render.
63
+ * @param options - A `configure` callback and `env` for `mockAppModules`, plus any other `render` option.
64
+ * @returns The `render` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * const { getByText } = await renderAppComponent(<Apploader appKey="child-app" />, {
69
+ * fusion: await mockFramework<[AppModule]>((configurator) => {
70
+ * // register the child app's own manifest against the `app` module
71
+ * }),
72
+ * });
73
+ * await expect.element(getByText(/mounted/)).toBeInTheDocument();
74
+ * ```
75
+ *
76
+ * @example Drive a module directly and assert the re-render
77
+ * ```tsx
78
+ * const { getByText, fusion } = await renderAppComponent<[ContextModule]>(<App />, {
79
+ * configure: (configurator) => enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
80
+ * });
81
+ * await fusion.app.context.setCurrentContextByIdAsync(projectB.id);
82
+ * await expect.element(getByText(/project-b/)).toBeInTheDocument();
83
+ * ```
84
+ */
85
+ export declare function renderAppComponent<TModules extends Array<AnyModule> | unknown = unknown, TEnv extends AppEnv = AppEnv>(ui: ReactElement, options?: RenderAppComponentOptions<TModules, TEnv>): Promise<RenderAppComponentResult<TModules>>;
86
+ export default renderAppComponent;
@@ -0,0 +1,99 @@
1
+ import type { RenderHookOptions, RenderHookResult } from 'vitest-browser-react';
2
+ import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
3
+ import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
4
+ import type { Fusion } from '@equinor/fusion-framework';
5
+ import type { AnyModule } from '@equinor/fusion-framework-module';
6
+ /**
7
+ * Options for {@link renderAppHook}.
8
+ *
9
+ * @template TModules - Module descriptors beyond the default set.
10
+ * @template TEnv - The application environment descriptor.
11
+ * @template Props - The props type accepted by the rendered hook.
12
+ */
13
+ export interface RenderAppHookOptions<TModules extends Array<AnyModule> | unknown = unknown, TEnv extends AppEnv = AppEnv, Props = undefined> extends Omit<RenderHookOptions<Props>, 'wrapper'> {
14
+ /** Configuration callback forwarded to {@link mockAppModules}. */
15
+ configure?: AppMockConfigureFn<TModules, TEnv>;
16
+ /** The application environment; defaults to a generic standalone test app. */
17
+ env?: TEnv;
18
+ /**
19
+ * The parent Fusion instance; defaults to a fresh {@link mockFramework} instance with
20
+ * this app's own manifest served. Pass one built beforehand to reuse a single instance
21
+ * across multiple `renderAppHook` calls, or to pre-configure parent-level modules (e.g.
22
+ * `http`, `context`, `serviceDiscovery`) the app reads through `useFramework`.
23
+ */
24
+ fusion?: Fusion;
25
+ }
26
+ /**
27
+ * The result of {@link renderAppHook}: the `vitest-browser-react` `renderHook` result,
28
+ * plus the resolved application module scope and its parent Fusion instance.
29
+ *
30
+ * @template Result - The value returned by the rendered hook.
31
+ * @template Props - The props accepted by the rendered hook.
32
+ * @template TModules - Module descriptors beyond the default set.
33
+ */
34
+ export interface RenderAppHookResult<Result, Props, TModules extends Array<AnyModule> | unknown = unknown> extends RenderHookResult<Result, Props> {
35
+ /**
36
+ * The Fusion instances backing the rendered hook, nested under this single key so
37
+ * `vitest-browser-react`'s own `RenderHookResult` fields stay free to evolve without
38
+ * ever colliding with it.
39
+ */
40
+ fusion: {
41
+ /** The parent Fusion instance the hook's `FrameworkProvider` was given. */
42
+ framework: Fusion;
43
+ /**
44
+ * The resolved application module instance backing the rendered hook — the same
45
+ * instance a real app would read via `useAppModule`/`useAppModules`. Drive a module
46
+ * not returned by the hook itself (e.g. `fusion.app.context.setCurrentContextByIdAsync(id)`)
47
+ * to exercise a state change after the initial render.
48
+ */
49
+ app: AppModulesInstance<TModules>;
50
+ };
51
+ }
52
+ /**
53
+ * Renders a hook inside a real, mock-backed application module scope.
54
+ *
55
+ * @remarks
56
+ * Wraps `vitest-browser-react`'s `renderHook` with the same provider nesting
57
+ * `createComponent` uses in production — a `FrameworkProvider` (the parent Fusion
58
+ * instance, from `mockFramework`) around a `ModuleProvider` (this app's own modules,
59
+ * from `mockAppModules`, `@equinor/fusion-framework-app/mock`) — the real
60
+ * `event`/`http`/`msal` module pipeline. Only requests a seeded middleware answers are
61
+ * faked; a request with no matching middleware still reaches the real network. Use this
62
+ * for any hook that reads from the application module scope or the parent framework
63
+ * (e.g. `useAppModule`, `useAccessToken`, `useFramework`), instead of hand-wiring
64
+ * `mockFramework`, `mockAppModules`, `FrameworkProvider` and `ModuleProvider` in every test.
65
+ *
66
+ * @template Result - The value returned by the rendered hook.
67
+ * @template Props - The props accepted by the rendered hook.
68
+ * @template TModules - Module descriptors beyond the default set.
69
+ * @template TEnv - The application environment descriptor.
70
+ * @param render - The hook to render, receiving `initialProps`.
71
+ * @param options - A `configure` callback and `env` for `mockAppModules`, plus any other
72
+ * `renderHook` option.
73
+ * @returns The `renderHook` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
74
+ *
75
+ * @example
76
+ * ```tsx
77
+ * const { result } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }));
78
+ * await vi.waitFor(() => expect(result.current.pending).toBe(false));
79
+ * ```
80
+ *
81
+ * @example Sign in a named user
82
+ * ```tsx
83
+ * const { result } = await renderAppHook(() => useCurrentAccount(), {
84
+ * configure: (configurator) => configurator.msal.setAccount({ name: 'Ada Lovelace' }),
85
+ * });
86
+ * ```
87
+ *
88
+ * @example Reuse a pre-built parent Fusion instance
89
+ * ```tsx
90
+ * const fusion = await mockFramework<[AppModule]>((configurator) =>
91
+ * enableAppManifestMock(configurator, env),
92
+ * );
93
+ *
94
+ * const { result: a } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }), { fusion });
95
+ * const { result: b } = await renderAppHook(() => useCurrentAccount(), { fusion });
96
+ * ```
97
+ */
98
+ export declare function renderAppHook<Result, Props = undefined, TModules extends Array<AnyModule> | unknown = unknown, TEnv extends AppEnv = AppEnv>(render: (initialProps?: Props) => Result, options?: RenderAppHookOptions<TModules, TEnv, Props>): Promise<RenderAppHookResult<Result, Props, TModules>>;
99
+ export default renderAppHook;
@@ -0,0 +1,37 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { AnyModule } from '@equinor/fusion-framework-module';
3
+ import type { AppEnv } from '@equinor/fusion-framework-app';
4
+ import { type RenderAppComponentOptions, type RenderAppComponentResult } from './render-app-component';
5
+ /**
6
+ * Renders a component inside the application's own module scope for use in plain `describe`/`it`
7
+ * tests, using the manifest, config, and module-configurator resolved for this application — no
8
+ * per-test wiring, and no custom `test` fixture required.
9
+ *
10
+ * @remarks
11
+ * Requires `appTestVitePlugin` (`@equinor/fusion-framework-vitest-plugin-react-app`) registered in
12
+ * your `vitest.config.ts` `plugins`, which serves the virtual modules backing the resolved
13
+ * `env`/`configure`. Running the same test file without the plugin registered fails to resolve
14
+ * those imports.
15
+ *
16
+ * Pass `env` or `configure` in `options` to override the resolved values for a single render.
17
+ *
18
+ * @template TModules - Module descriptors beyond the default set.
19
+ * @param ui - The component to render.
20
+ * @param options - Overrides for `env`/`configure`, plus any other `renderAppComponent` option.
21
+ * @returns The `render` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
22
+ * @example
23
+ * ```tsx
24
+ * import { describe, expect, it } from 'vitest';
25
+ * import { render } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
26
+ * import { App } from '../App';
27
+ *
28
+ * describe('App', () => {
29
+ * it('renders the app', async () => {
30
+ * const { getByRole } = await render(<App />);
31
+ * await expect.element(getByRole('heading')).toBeVisible();
32
+ * });
33
+ * });
34
+ * ```
35
+ */
36
+ export declare function render<TModules extends Array<AnyModule> | unknown = unknown>(ui: ReactElement, options?: RenderAppComponentOptions<TModules, AppEnv>): Promise<RenderAppComponentResult<TModules>>;
37
+ export default render;
@@ -0,0 +1,56 @@
1
+ import type { AppManifest } from '@equinor/fusion-framework-module-app';
2
+ import { type AppManifestFn, type AppConfigFn, type ApiAppConfig } from '@equinor/fusion-framework-cli/app';
3
+ /**
4
+ * The application manifest and config resolved for a test run.
5
+ */
6
+ export type AppTestEnv = {
7
+ manifest: AppManifest;
8
+ config: ApiAppConfig;
9
+ };
10
+ /**
11
+ * Options for {@link resolveAppTestEnv}.
12
+ */
13
+ export type ResolveAppTestEnvOptions = {
14
+ /** Directory to resolve the package, manifest, and config from. Defaults to `process.cwd()`. */
15
+ entrypoint?: string;
16
+ /**
17
+ * An explicit manifest file to load instead of the default `app.manifest(.*)?` lookup, or a
18
+ * manifest function (same shape as `defineAppManifest`'s argument) applied directly.
19
+ */
20
+ manifest?: string | AppManifestFn;
21
+ /**
22
+ * An explicit config file to load instead of the default `app.config(.*)?` lookup, or a
23
+ * config function (same shape as `defineAppConfig`'s argument) applied directly.
24
+ */
25
+ config?: string | AppConfigFn;
26
+ };
27
+ /**
28
+ * Resolves an application's manifest and config using the same pipeline `ffc app build`/
29
+ * `ffc app dev` use: a base manifest generated from `package.json`, merged with a local
30
+ * `app.manifest.ts` if one exists; and `app.config.ts` for endpoints/environment, falling
31
+ * back to an empty config if none exists.
32
+ *
33
+ * @remarks
34
+ * Intended to seed `@equinor/fusion-framework-vitest-plugin-react-app/test`'s `testApp` `env` fixture, so
35
+ * a test suite exercises the application's real manifest/config instead of a hand-maintained
36
+ * duplicate. Anything a specific test still needs faked (a missing endpoint, a different
37
+ * `appKey`) can be layered on top with `testApp.extend('env', ...)` or a per-test
38
+ * `test.override('env', ...)`.
39
+ *
40
+ * @param options - Resolution options; `entrypoint` defaults to the current working directory.
41
+ * @returns The resolved application manifest and config.
42
+ * @throws If no `package.json` can be found from `entrypoint` upward, or an explicitly requested
43
+ * `manifest`/`config` file does not exist.
44
+ * @example
45
+ * ```ts
46
+ * import { resolveAppTestEnv } from '@equinor/fusion-framework-vitest-plugin-react-app';
47
+ * import { testApp } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
48
+ * import { configure } from '../config';
49
+ *
50
+ * const test = testApp
51
+ * .extend('env', { injected: true }, () => resolveAppTestEnv())
52
+ * .extend('configure', { injected: true }, () => configure);
53
+ * ```
54
+ */
55
+ export declare const resolveAppTestEnv: (options?: ResolveAppTestEnvOptions) => Promise<AppTestEnv>;
56
+ export default resolveAppTestEnv;
@@ -0,0 +1,14 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import type { AnyModule } from '@equinor/fusion-framework-module';
3
+ import type { AppScope } from './resolve-app-scope';
4
+ /**
5
+ * The provider nesting `createComponent` uses in production, for wrapping a component or
6
+ * hook under test in its resolved {@link AppScope}.
7
+ *
8
+ * @template TModules - Module descriptors beyond the default set.
9
+ * @param scope - The resolved parent Fusion instance and application module scope.
10
+ * @returns A wrapper component nesting a `FrameworkProvider` around a `ModuleProvider`.
11
+ */
12
+ export declare function createAppScopeWrapper<TModules extends Array<AnyModule> | unknown = unknown>({ framework, app, }: AppScope<TModules>): (props: {
13
+ children: ReactNode;
14
+ }) => ReactElement;
@@ -0,0 +1,5 @@
1
+ import type { AppEnv } from '@equinor/fusion-framework-app';
2
+ /**
3
+ * The application environment used when a test does not care about its own app identity.
4
+ */
5
+ export declare const defaultAppEnv: AppEnv;
@@ -0,0 +1,4 @@
1
+ export { defaultAppEnv } from './default-app-env';
2
+ export { resolveFusion } from './resolve-fusion';
3
+ export { resolveAppScope, type AppScope } from './resolve-app-scope';
4
+ export { createAppScopeWrapper } from './create-app-scope-wrapper';
@@ -0,0 +1,31 @@
1
+ import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
2
+ import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
3
+ import type { Fusion } from '@equinor/fusion-framework';
4
+ import type { AnyModule } from '@equinor/fusion-framework-module';
5
+ /**
6
+ * The parent Fusion instance and resolved application module scope shared by every
7
+ * `packages/react/app` testing helper.
8
+ *
9
+ * @template TModules - Module descriptors beyond the default set.
10
+ */
11
+ export interface AppScope<TModules extends Array<AnyModule> | unknown = unknown> {
12
+ /** The parent Fusion instance the application scope was resolved against. */
13
+ framework: Fusion;
14
+ /** The resolved application module instance. */
15
+ app: AppModulesInstance<TModules>;
16
+ }
17
+ /**
18
+ * Resolves the parent Fusion instance and application module scope shared by every
19
+ * `packages/react/app` testing helper.
20
+ *
21
+ * @template TModules - Module descriptors beyond the default set.
22
+ * @template TEnv - The application environment descriptor.
23
+ * @param options - A `configure` callback and `env` for {@link mockAppModules}, plus an
24
+ * already-built `fusion` instance to reuse instead of a fresh one.
25
+ * @returns The resolved {@link AppScope}.
26
+ */
27
+ export declare function resolveAppScope<TModules extends Array<AnyModule> | unknown = unknown, TEnv extends AppEnv = AppEnv>(options?: {
28
+ configure?: AppMockConfigureFn<TModules, TEnv>;
29
+ env?: TEnv;
30
+ fusion?: Fusion;
31
+ }): Promise<AppScope<TModules>>;
@@ -0,0 +1,13 @@
1
+ import type { AppEnv } from '@equinor/fusion-framework-app';
2
+ import type { Fusion } from '@equinor/fusion-framework';
3
+ /**
4
+ * Resolves the parent Fusion instance backing an application module scope, building a
5
+ * fresh {@link mockFramework} instance with this app's own manifest served when none is
6
+ * given.
7
+ *
8
+ * @template TEnv - The application environment descriptor.
9
+ * @param env - The application environment; defaults to {@link defaultAppEnv}.
10
+ * @param fusion - An already-built parent Fusion instance to reuse instead.
11
+ * @returns The given `fusion`, or a fresh mocked parent Fusion instance.
12
+ */
13
+ export declare function resolveFusion<TEnv extends AppEnv = AppEnv>(env?: TEnv, fusion?: Fusion): Promise<Fusion>;
@@ -0,0 +1,68 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { RenderOptions, RenderHookOptions } from 'vitest-browser-react';
3
+ import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
4
+ import type { AppEnv } from '@equinor/fusion-framework-app';
5
+ /**
6
+ * `vitest`'s `test`, extended with an application module scope fixture.
7
+ *
8
+ * @remarks
9
+ * An alternative to {@link renderAppComponent}/{@link renderAppHook} for a test file whose
10
+ * cases share seeded fixture defaults: `env`/`configure` become suite-level concerns,
11
+ * overridden once per file (or per `describe` block) with `testApp.extend(...)`, rather than
12
+ * an options object repeated on every call. `fusion`/`app` are still instantiated fresh per
13
+ * test — only the seeded defaults are shared, not state between tests. They also resolve
14
+ * lazily: a test that only destructures `app` never pays for rendering anything, and one
15
+ * that only destructures `render`/`renderHook` gets the same fixture wiring automatically.
16
+ *
17
+ * Both entry points stay supported: reach for `renderAppComponent`/`renderAppHook` for a
18
+ * one-off test whose configuration is not shared by the rest of the file; reach for
19
+ * `testApp` when several cases in a file share one set of seeded fixture defaults.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * testApp('resolves current context', async ({ app, render }) => {
24
+ * const screen = await render(<App />);
25
+ * expect(app.context).toBeDefined();
26
+ * });
27
+ * ```
28
+ *
29
+ * @example Seed a module for every test in a suite
30
+ * ```tsx
31
+ * describe('with a seeded context module', () => {
32
+ * const test = testApp.extend('configure', { injected: true }, () =>
33
+ * (configurator) => enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
34
+ * );
35
+ *
36
+ * test('starts on the seeded context', async ({ render }) => {
37
+ * const screen = await render(<App />);
38
+ * await expect.element(screen.getByText(projectA.title)).toBeVisible();
39
+ * });
40
+ * });
41
+ * ```
42
+ */
43
+ export declare const testApp: import("vitest").TestAPI<Omit<Omit<Omit<Omit<Omit<Omit<object, "$__test"> & Record<"env", AppEnv> & {
44
+ readonly $__worker?: object | undefined;
45
+ readonly $__file?: object | undefined;
46
+ readonly $__test?: (object & Record<"env", AppEnv>) | undefined;
47
+ }, "$__test"> & Record<"configure", AppMockConfigureFn | undefined> & {
48
+ readonly $__worker?: object | undefined;
49
+ readonly $__file?: object | undefined;
50
+ readonly $__test?: (object & Record<"env", AppEnv> & Record<"configure", AppMockConfigureFn | undefined>) | undefined;
51
+ }, "$__test"> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & {
52
+ readonly $__worker?: object | undefined;
53
+ readonly $__file?: object | undefined;
54
+ readonly $__test?: (object & Record<"env", AppEnv> & Record<"configure", AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>>) | undefined;
55
+ }, "$__test"> & Record<"app", import("@equinor/fusion-framework-app").AppModulesInstance<unknown>> & {
56
+ readonly $__worker?: object | undefined;
57
+ readonly $__file?: object | undefined;
58
+ readonly $__test?: (object & Record<"env", AppEnv> & Record<"configure", AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("@equinor/fusion-framework-app").AppModulesInstance<unknown>>) | undefined;
59
+ }, "$__test"> & Record<"render", (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>> & {
60
+ readonly $__worker?: object | undefined;
61
+ readonly $__file?: object | undefined;
62
+ readonly $__test?: (object & Record<"env", AppEnv> & Record<"configure", AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("@equinor/fusion-framework-app").AppModulesInstance<unknown>> & Record<"render", (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>>) | undefined;
63
+ }, "$__test"> & Record<"renderHook", <Result, Props = undefined>(cb: (initialProps?: Props) => Result, options?: Omit<RenderHookOptions<Props>, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderHookResult<Result, Props>>> & {
64
+ readonly $__worker?: object | undefined;
65
+ readonly $__file?: object | undefined;
66
+ readonly $__test?: (object & Record<"env", AppEnv> & Record<"configure", AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("@equinor/fusion-framework-app").AppModulesInstance<unknown>> & Record<"render", (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>> & Record<"renderHook", <Result, Props = undefined>(cb: (initialProps?: Props) => Result, options?: Omit<RenderHookOptions<Props>, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderHookResult<Result, Props>>>) | undefined;
67
+ }>;
68
+ export default testApp;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `vitest`'s `test`, pre-seeded with the application's own manifest, config, and
3
+ * module-configurator, resolved the same way `ffc app build`/`ffc app dev` resolve them.
4
+ *
5
+ * @remarks
6
+ * Requires `appTestVitePlugin` (`@equinor/fusion-framework-vitest-plugin-react-app`) registered in
7
+ * your `vitest.config.ts` `plugins`, which serves the virtual modules backing this fixture.
8
+ * Running the same test file without the plugin registered fails to resolve those imports.
9
+ *
10
+ * Per-test mocking still works exactly like the base `testApp`: `.extend('configure', ...)` or
11
+ * a per-case `test.override('env', ...)` layers on top of the resolved values.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { test } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
16
+ * import { App } from '../App';
17
+ *
18
+ * test('renders the app', async ({ render }) => {
19
+ * const screen = await render(<App />);
20
+ * await expect.element(screen.getByRole('heading')).toBeVisible();
21
+ * });
22
+ * ```
23
+ */
24
+ export declare const test: import("vitest").TestAPI<Omit<Omit<Omit<Omit<Omit<Omit<Omit<Omit<object, "$__test"> & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & {
25
+ readonly $__worker?: object | undefined;
26
+ readonly $__file?: object | undefined;
27
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv>) | undefined;
28
+ }, "$__test"> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & {
29
+ readonly $__worker?: object | undefined;
30
+ readonly $__file?: object | undefined;
31
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined>) | undefined;
32
+ }, "$__test"> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & {
33
+ readonly $__worker?: object | undefined;
34
+ readonly $__file?: object | undefined;
35
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>>) | undefined;
36
+ }, "$__test"> & Record<"app", import("node_modules/@equinor/fusion-framework-module-app/src/types").AppModulesInstance<unknown>> & {
37
+ readonly $__worker?: object | undefined;
38
+ readonly $__file?: object | undefined;
39
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("node_modules/@equinor/fusion-framework-module-app/src/types").AppModulesInstance<unknown>>) | undefined;
40
+ }, "$__test"> & Record<"render", (ui: import("node_modules/@types/react").ReactElement, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>> & {
41
+ readonly $__worker?: object | undefined;
42
+ readonly $__file?: object | undefined;
43
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("node_modules/@equinor/fusion-framework-module-app/src/types").AppModulesInstance<unknown>> & Record<"render", (ui: import("node_modules/@types/react").ReactElement, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>>) | undefined;
44
+ }, "$__test"> & Record<"renderHook", <Result, Props = undefined>(cb: (initialProps?: Props) => Result, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderHookOptions<Props>, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderHookResult<Result, Props>>> & {
45
+ readonly $__worker?: object | undefined;
46
+ readonly $__file?: object | undefined;
47
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("node_modules/@equinor/fusion-framework-module-app/src/types").AppModulesInstance<unknown>> & Record<"render", (ui: import("node_modules/@types/react").ReactElement, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>> & Record<"renderHook", <Result, Props = undefined>(cb: (initialProps?: Props) => Result, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderHookOptions<Props>, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderHookResult<Result, Props>>>) | undefined;
48
+ }, "$__test"> & Record<"env", {
49
+ manifest: import("@equinor/fusion-framework-cli/app").AppManifest;
50
+ config: import("node_modules/@equinor/fusion-framework-module-app/src/AppConfig").AppConfig<import("node_modules/@equinor/fusion-framework-module-app/src/types").ConfigEnvironment> | undefined;
51
+ }> & {
52
+ readonly $__worker?: object | undefined;
53
+ readonly $__file?: object | undefined;
54
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("node_modules/@equinor/fusion-framework-module-app/src/types").AppModulesInstance<unknown>> & Record<"render", (ui: import("node_modules/@types/react").ReactElement, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>> & Record<"renderHook", <Result, Props = undefined>(cb: (initialProps?: Props) => Result, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderHookOptions<Props>, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderHookResult<Result, Props>>> & Record<"env", {
55
+ manifest: import("@equinor/fusion-framework-cli/app").AppManifest;
56
+ config: import("node_modules/@equinor/fusion-framework-module-app/src/AppConfig").AppConfig<import("node_modules/@equinor/fusion-framework-module-app/src/types").ConfigEnvironment> | undefined;
57
+ }>) | undefined;
58
+ }, "$__test"> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & {
59
+ readonly $__worker?: object | undefined;
60
+ readonly $__file?: object | undefined;
61
+ readonly $__test?: (object & Record<"env", import("node_modules/@equinor/fusion-framework-app/src/types").AppEnv> & Record<"configure", import("node_modules/@equinor/fusion-framework-app/src/mock/mock-app-modules").AppMockConfigureFn | undefined> & Record<"fusion", import("node_modules/@equinor/fusion-framework/src/types").Fusion<unknown>> & Record<"app", import("node_modules/@equinor/fusion-framework-module-app/src/types").AppModulesInstance<unknown>> & Record<"render", (ui: import("node_modules/@types/react").ReactElement, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderOptions, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderResult>> & Record<"renderHook", <Result, Props = undefined>(cb: (initialProps?: Props) => Result, options?: Omit<import("node_modules/vitest-browser-react/dist").RenderHookOptions<Props>, 'wrapper'>) => Promise<import("node_modules/vitest-browser-react/dist").RenderHookResult<Result, Props>>> & Record<"env", {
62
+ manifest: import("@equinor/fusion-framework-cli/app").AppManifest;
63
+ config: import("node_modules/@equinor/fusion-framework-module-app/src/AppConfig").AppConfig<import("node_modules/@equinor/fusion-framework-module-app/src/types").ConfigEnvironment> | undefined;
64
+ }>) | undefined;
65
+ }>;
66
+ export default test;
@@ -0,0 +1 @@
1
+ export declare const version = "0.2.0-next.0";