@equinor/fusion-framework-vitest-plugin-react-app 1.0.0-next.6 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.js +9 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/resolve-app-test-env.js +8 -2
- package/dist/esm/resolve-app-test-env.js.map +1 -1
- package/dist/esm/scope/resolve-fusion.js +22 -24
- package/dist/esm/scope/resolve-fusion.js.map +1 -1
- package/dist/esm/test-app.js +5 -8
- package/dist/esm/test-app.js.map +1 -1
- package/dist/esm/test.js +2 -1
- package/dist/esm/test.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/resolve-app-test-env.d.ts +3 -1
- package/dist/types/scope/resolve-fusion.d.ts +6 -7
- package/dist/types/test-app.d.ts +12 -11
- package/dist/types/test.d.ts +20 -9
- package/dist/types/version.d.ts +1 -1
- package/package.json +17 -19
- package/CHANGELOG.md +0 -196
- package/docs/advanced.md +0 -209
- package/docs/configuration.md +0 -112
- package/docs/getting-started.md +0 -66
- package/docs/migrating-an-existing-app.md +0 -202
- package/docs/module-mocks.md +0 -120
- package/docs/overview.md +0 -49
- package/docs/troubleshooting.md +0 -67
- package/docs/why-browser-mode.md +0 -128
- package/src/__tests__/app-test-vite-plugin.test.ts +0 -109
- package/src/__tests__/merge-env-config.test.ts +0 -67
- package/src/__tests__/resolve-app-test-env.test.ts +0 -83
- package/src/app-test.ts +0 -19
- package/src/define-project.ts +0 -91
- package/src/index.ts +0 -124
- package/src/merge-env-config.ts +0 -58
- package/src/render-app-component.tsx +0 -116
- package/src/render-app-hook.tsx +0 -135
- package/src/render.tsx +0 -64
- package/src/resolve-app-test-env.ts +0 -132
- package/src/scope/create-app-scope-wrapper.tsx +0 -26
- package/src/scope/default-app-env.ts +0 -13
- package/src/scope/index.ts +0 -4
- package/src/scope/resolve-app-scope.ts +0 -46
- package/src/scope/resolve-fusion.ts +0 -74
- package/src/test-app.tsx +0 -148
- package/src/test.tsx +0 -42
- package/src/version.ts +0 -2
- package/src/virtual-modules.d.ts +0 -12
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -10
package/src/define-project.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { playwright } from '@vitest/browser-playwright';
|
|
2
|
-
import {
|
|
3
|
-
defineProject as defineVitestProject,
|
|
4
|
-
mergeConfig,
|
|
5
|
-
type UserWorkspaceConfig,
|
|
6
|
-
} from 'vitest/config';
|
|
7
|
-
|
|
8
|
-
import { appTestVitePlugin } from './index.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A config override for {@link defineProject}: an object is deep-merged onto the default
|
|
12
|
-
* config via Vite's own `mergeConfig`; a function receives the default config and returns the
|
|
13
|
-
* config used outright, for changes `mergeConfig` can't express (e.g. removing a field).
|
|
14
|
-
*/
|
|
15
|
-
export type AppTestConfigOverride =
|
|
16
|
-
| Partial<UserWorkspaceConfig>
|
|
17
|
-
| ((config: UserWorkspaceConfig) => UserWorkspaceConfig);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Builds a ready-to-export Vitest project config for testing a Fusion Framework React app:
|
|
21
|
-
* registers {@link appTestVitePlugin} (with its own defaults) and wires up Vitest browser mode
|
|
22
|
-
* with the Playwright/`chromium` provider, so a consuming app's own `vitest.config.ts` needs no
|
|
23
|
-
* browser-provider boilerplate of its own.
|
|
24
|
-
*
|
|
25
|
-
* @remarks
|
|
26
|
-
* Drop-in replacement for Vitest's own `defineProject` — same `export default`, just pre-wired.
|
|
27
|
-
* Playwright/`chromium` is only the *default* — pass `override` to change or replace anything
|
|
28
|
-
* (e.g. `test.name`, or swap `test.browser.provider` for a different `@vitest/browser-*`
|
|
29
|
-
* provider) without hand-rolling `appTestVitePlugin`'s own wiring. The default `1024x768`
|
|
30
|
-
* viewport matches a typical low-resolution Citrix session rather than Vitest's own
|
|
31
|
-
* mobile-sized default; pass `test.browser.viewport` to use a different size.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
|
|
36
|
-
* import { name, version } from './package.json' with { type: 'json' };
|
|
37
|
-
*
|
|
38
|
-
* export default defineProject({ test: { name: `${name}@${version}` } });
|
|
39
|
-
* ```
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* Overriding the browser provider:
|
|
43
|
-
* ```ts
|
|
44
|
-
* import { webdriverio } from '@vitest/browser-webdriverio';
|
|
45
|
-
*
|
|
46
|
-
* export default defineProject({ test: { browser: { provider: webdriverio() } } });
|
|
47
|
-
* ```
|
|
48
|
-
*
|
|
49
|
-
* @param override - Deep-merged onto the default config (a plain object), or applied to it
|
|
50
|
-
* outright (a function receiving the default config); see {@link AppTestConfigOverride}.
|
|
51
|
-
* @returns A Vitest project config, ready to `export default`.
|
|
52
|
-
*/
|
|
53
|
-
export const defineProject = (override?: AppTestConfigOverride): UserWorkspaceConfig => {
|
|
54
|
-
const defaults: UserWorkspaceConfig = {
|
|
55
|
-
plugins: [appTestVitePlugin()],
|
|
56
|
-
test: {
|
|
57
|
-
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
|
58
|
-
browser: {
|
|
59
|
-
enabled: true,
|
|
60
|
-
provider: playwright(),
|
|
61
|
-
headless: true,
|
|
62
|
-
// most users run the app through Citrix at a low, fixed resolution rather than a
|
|
63
|
-
// resizable desktop window — default to that instead of Vitest's own mobile-sized viewport
|
|
64
|
-
viewport: { width: 1024, height: 768 },
|
|
65
|
-
instances: [{ browser: 'chromium' }],
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
// pre-transforms all source up front so deps only reached via lazy/code-split imports
|
|
69
|
-
// (e.g. route components) are discovered before the first test request, not mid-run —
|
|
70
|
-
// the latter forces Vite to reload the page and fails the in-flight test file import
|
|
71
|
-
// `.d.ts` files are excluded: a CJS-style declaration file (e.g. one using `export =`)
|
|
72
|
-
// fails the warmup scan when loaded as ESM
|
|
73
|
-
server: { warmup: { clientFiles: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'] } },
|
|
74
|
-
// same reasoning as `server.warmup` above, but for the esbuild dep scanner: without this,
|
|
75
|
-
// its default entry detection can miss code-split route files entirely, so a package only
|
|
76
|
-
// ever imported from one of those (e.g. react-router's own deps) is discovered mid-run
|
|
77
|
-
// instead of up front — statically crawling every source file (which esbuild's scanner
|
|
78
|
-
// follows through dynamic imports too) avoids that with no per-package name needed
|
|
79
|
-
optimizeDeps: { entries: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'] },
|
|
80
|
-
};
|
|
81
|
-
// a function replaces the config outright; a plain object deep-merges onto it via Vite's own mergeConfig
|
|
82
|
-
const resolved =
|
|
83
|
-
typeof override === 'function'
|
|
84
|
-
? override(defaults)
|
|
85
|
-
: override
|
|
86
|
-
? (mergeConfig(defaults, override) as UserWorkspaceConfig)
|
|
87
|
-
: defaults;
|
|
88
|
-
return defineVitestProject(resolved);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export default defineProject;
|
package/src/index.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { FileNotFoundError } from '@equinor/fusion-imports';
|
|
5
|
-
import type { Plugin } from 'vite';
|
|
6
|
-
|
|
7
|
-
import { resolveAppTestEnv, type ResolveAppTestEnvOptions } from './resolve-app-test-env.js';
|
|
8
|
-
|
|
9
|
-
const ENV_MODULE_ID = 'virtual:fusion-app-test-env';
|
|
10
|
-
const CONFIGURE_MODULE_ID = 'virtual:fusion-app-test-configure';
|
|
11
|
-
const RESOLVED_ENV_MODULE_ID = `\0${ENV_MODULE_ID}`;
|
|
12
|
-
const RESOLVED_CONFIGURE_MODULE_ID = `\0${CONFIGURE_MODULE_ID}`;
|
|
13
|
-
|
|
14
|
-
const DEFAULT_CONFIGURE_CANDIDATES = ['src/config.ts', 'src/config.tsx', 'src/config.js'];
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Options for {@link appTestVitePlugin}.
|
|
18
|
-
*/
|
|
19
|
-
export type AppTestVitePluginOptions = ResolveAppTestEnvOptions & {
|
|
20
|
-
/**
|
|
21
|
-
* Path (relative to `entrypoint`) to the app's module-configurator export, mirroring the
|
|
22
|
-
* `configure` argument passed to `makeComponent` in the app's own entry point. Defaults to the
|
|
23
|
-
* first of `src/config.ts`, `src/config.tsx`, `src/config.js` that exists.
|
|
24
|
-
*
|
|
25
|
-
* @remarks
|
|
26
|
-
* Unlike `manifest`/`config`, this can't be an inline function: it's live application code
|
|
27
|
-
* (with its own imports and closures) re-exported as-is into the test bundle rather than
|
|
28
|
-
* JSON-serialized data, so Vite needs a real file on disk to resolve and transform.
|
|
29
|
-
*/
|
|
30
|
-
configure?: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Vite plugin serving an application's manifest/config (resolved the same way `ffc app build`/
|
|
35
|
-
* `ffc app dev` do) and its own module-configurator export as virtual modules, so
|
|
36
|
-
* `@equinor/fusion-framework-vitest-plugin-react-app/test`'s `test`/`render` need no per-test
|
|
37
|
-
* `env`/`configure` wiring.
|
|
38
|
-
*
|
|
39
|
-
* @remarks
|
|
40
|
-
* A plain Vite plugin — Vitest configs are Vite configs, so this registers directly in your
|
|
41
|
-
* own `vitest.config.ts`, no CLI command required:
|
|
42
|
-
* ```ts
|
|
43
|
-
* import { defineConfig } from 'vitest/config';
|
|
44
|
-
* import { appTestVitePlugin } from '@equinor/fusion-framework-vitest-plugin-react-app';
|
|
45
|
-
*
|
|
46
|
-
* export default defineConfig({
|
|
47
|
-
* plugins: [appTestVitePlugin()],
|
|
48
|
-
* // ...your own browser-mode config
|
|
49
|
-
* });
|
|
50
|
-
* ```
|
|
51
|
-
* Exposes two virtual modules: `virtual:fusion-app-test-env` (`manifest`/`config`, as JSON) and
|
|
52
|
-
* `virtual:fusion-app-test-configure` (a re-export of the resolved `configure` module, or
|
|
53
|
-
* `undefined` if none exists). Not intended to be imported directly by application code.
|
|
54
|
-
*
|
|
55
|
-
* @param options - Resolution options; `entrypoint` defaults to the current working directory.
|
|
56
|
-
* @returns A Vite plugin instance.
|
|
57
|
-
*/
|
|
58
|
-
export const appTestVitePlugin = (options?: AppTestVitePluginOptions): Plugin => {
|
|
59
|
-
const explicitEntrypoint = options?.entrypoint;
|
|
60
|
-
let entrypoint = explicitEntrypoint;
|
|
61
|
-
let configureModulePath = explicitEntrypoint
|
|
62
|
-
? resolveConfigureModulePath(explicitEntrypoint, options?.configure)
|
|
63
|
-
: undefined;
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
name: 'fusion:app-test',
|
|
67
|
-
configResolved(config) {
|
|
68
|
-
// Vitest resolves each workspace project's root from its config file, which identifies the app containing src/index.ts.
|
|
69
|
-
entrypoint ??= config.root;
|
|
70
|
-
configureModulePath ??= resolveConfigureModulePath(entrypoint, options?.configure);
|
|
71
|
-
},
|
|
72
|
-
resolveId(id) {
|
|
73
|
-
// claim only our two virtual specifiers, leave everything else to the normal resolvers
|
|
74
|
-
if (id === ENV_MODULE_ID) return RESOLVED_ENV_MODULE_ID;
|
|
75
|
-
// second virtual specifier, same rule as above
|
|
76
|
-
if (id === CONFIGURE_MODULE_ID) return RESOLVED_CONFIGURE_MODULE_ID;
|
|
77
|
-
return null;
|
|
78
|
-
},
|
|
79
|
-
async load(id) {
|
|
80
|
-
// serves manifest/config resolved lazily here, not at plugin-creation time, so options.entrypoint changes between test runs are respected
|
|
81
|
-
if (id === RESOLVED_ENV_MODULE_ID) {
|
|
82
|
-
const { manifest, config } = await resolveAppTestEnv({ ...options, entrypoint });
|
|
83
|
-
return [
|
|
84
|
-
`export const manifest = ${JSON.stringify(manifest)};`,
|
|
85
|
-
`export const config = ${JSON.stringify(config)};`,
|
|
86
|
-
].join('\n');
|
|
87
|
-
}
|
|
88
|
-
// no conventional module means the app registers no extra modules, same as omitting `configure` from `makeComponent`
|
|
89
|
-
if (id === RESOLVED_CONFIGURE_MODULE_ID) {
|
|
90
|
-
return configureModulePath
|
|
91
|
-
? `export { default as configure } from ${JSON.stringify(configureModulePath)};`
|
|
92
|
-
: 'export const configure = undefined;';
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Resolves the app's module-configurator file: the explicit `file` if given, otherwise the
|
|
101
|
-
* first existing candidate in {@link DEFAULT_CONFIGURE_CANDIDATES}.
|
|
102
|
-
*
|
|
103
|
-
* @throws {@link FileNotFoundError} If an explicitly requested `file` does not exist — unlike
|
|
104
|
-
* the convention-based lookup, a typo here should fail loudly instead of silently running the
|
|
105
|
-
* test suite without the application's modules.
|
|
106
|
-
*/
|
|
107
|
-
const resolveConfigureModulePath = (cwd: string, file?: string): string | undefined => {
|
|
108
|
-
// an explicit path is a user request, not a convention lookup, so a typo must fail loudly
|
|
109
|
-
if (file) {
|
|
110
|
-
const resolved = resolve(cwd, file);
|
|
111
|
-
// fail fast rather than silently falling back to "no configurator"
|
|
112
|
-
if (!existsSync(resolved)) {
|
|
113
|
-
throw new FileNotFoundError(`Configure module not found: ${resolved}`);
|
|
114
|
-
}
|
|
115
|
-
return resolved;
|
|
116
|
-
}
|
|
117
|
-
// first candidate that exists wins; none existing is a valid "no configurator" state
|
|
118
|
-
const found = DEFAULT_CONFIGURE_CANDIDATES.find((candidate) =>
|
|
119
|
-
existsSync(resolve(cwd, candidate)),
|
|
120
|
-
);
|
|
121
|
-
return found ? resolve(cwd, found) : undefined;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export default appTestVitePlugin;
|
package/src/merge-env-config.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { AppConfig } from '@equinor/fusion-framework-module-app';
|
|
2
|
-
import type { ConfigEnvironment } from '@equinor/fusion-framework-module-app';
|
|
3
|
-
import type { AppEnv } from '@equinor/fusion-framework-app';
|
|
4
|
-
|
|
5
|
-
type EndpointOverride = Partial<NonNullable<AppEnv['config']>['endpoints'][string]>;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Overrides accepted by {@link mergeEnvConfig}.
|
|
9
|
-
*/
|
|
10
|
-
export type MergeEnvConfigOverrides<TConfig extends ConfigEnvironment = ConfigEnvironment> = {
|
|
11
|
-
environment?: Partial<TConfig>;
|
|
12
|
-
endpoints?: Record<string, EndpointOverride>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Merges `environment`/`endpoints` overrides into an `AppEnv`'s `config`.
|
|
17
|
-
*
|
|
18
|
-
* @remarks
|
|
19
|
-
* `AppConfig` stores both behind private fields exposed only through getters, so
|
|
20
|
-
* `{ ...env.config, endpoints: {...} }` silently drops everything it doesn't explicitly
|
|
21
|
-
* restate — a plain object spread copies no own enumerable properties off an `AppConfig`
|
|
22
|
-
* instance. Reach for this instead of hand-rolling that merge in a test fixture.
|
|
23
|
-
*
|
|
24
|
-
* @template TEnv - The `AppEnv` shape being merged into.
|
|
25
|
-
* @param env - The `AppEnv` to merge overrides into; left untouched, `config` may be omitted.
|
|
26
|
-
* @param overrides - Partial `environment`/`endpoints` values, merged over any existing config.
|
|
27
|
-
* @returns A new `AppEnv` with a new `AppConfig` reflecting the merge.
|
|
28
|
-
* @example
|
|
29
|
-
* ```ts
|
|
30
|
-
* const test = testApp.extend('appEnv', ({ appEnv }) =>
|
|
31
|
-
* mergeEnvConfig(appEnv, { endpoints: { 'cpr-api': { url: backendBaseUrl } } }),
|
|
32
|
-
* );
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
export function mergeEnvConfig<TEnv extends AppEnv = AppEnv>(
|
|
36
|
-
env: TEnv,
|
|
37
|
-
overrides: MergeEnvConfigOverrides<
|
|
38
|
-
TEnv['config'] extends AppConfig<infer TConfig> ? TConfig : ConfigEnvironment
|
|
39
|
-
>,
|
|
40
|
-
): TEnv {
|
|
41
|
-
// each overridden endpoint keeps any field the caller didn't explicitly override
|
|
42
|
-
const endpoints = Object.entries(overrides.endpoints ?? {}).reduce(
|
|
43
|
-
// defaults, then any existing endpoint, then the override (override wins)
|
|
44
|
-
(acc, [key, override]) =>
|
|
45
|
-
Object.assign(acc, { [key]: Object.assign({ url: '', scopes: [] }, acc[key], override) }),
|
|
46
|
-
{ ...env.config?.endpoints },
|
|
47
|
-
);
|
|
48
|
-
// caller-supplied environment values win over the existing ones
|
|
49
|
-
return {
|
|
50
|
-
...env,
|
|
51
|
-
config: new AppConfig({
|
|
52
|
-
environment: { ...env.config?.environment, ...overrides.environment },
|
|
53
|
-
endpoints,
|
|
54
|
-
}),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export default mergeEnvConfig;
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from 'react';
|
|
2
|
-
import { render } from 'vitest-browser-react';
|
|
3
|
-
import type { RenderOptions, RenderResult } from 'vitest-browser-react';
|
|
4
|
-
|
|
5
|
-
import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
|
|
6
|
-
import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
|
|
7
|
-
import type { Fusion } from '@equinor/fusion-framework';
|
|
8
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
9
|
-
|
|
10
|
-
import { resolveAppScope, createAppScopeWrapper } from './scope';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Options for {@link renderAppComponent}.
|
|
14
|
-
*
|
|
15
|
-
* @template TModules - Module descriptors beyond the default set.
|
|
16
|
-
* @template TEnv - The application environment descriptor.
|
|
17
|
-
*/
|
|
18
|
-
export interface RenderAppComponentOptions<
|
|
19
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
20
|
-
TEnv extends AppEnv = AppEnv,
|
|
21
|
-
> extends Omit<RenderOptions, 'wrapper'> {
|
|
22
|
-
/** Configuration callback forwarded to {@link mockAppModules}. */
|
|
23
|
-
configure?: AppMockConfigureFn<TModules, TEnv>;
|
|
24
|
-
/** The application environment; defaults to a generic standalone test app. */
|
|
25
|
-
env?: TEnv;
|
|
26
|
-
/**
|
|
27
|
-
* The parent Fusion instance; defaults to a fresh {@link mockFramework} instance with
|
|
28
|
-
* this app's own manifest served. Pass one built beforehand to reuse a single instance
|
|
29
|
-
* across multiple render calls, or to pre-configure parent-level modules (e.g. `http`,
|
|
30
|
-
* `context`, `serviceDiscovery`, or `app` itself for a component that loads another app).
|
|
31
|
-
*/
|
|
32
|
-
fusion?: Fusion;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The result of {@link renderAppComponent}: the `vitest-browser-react` render result,
|
|
37
|
-
* plus the resolved application module scope and its parent Fusion instance.
|
|
38
|
-
*
|
|
39
|
-
* @template TModules - Module descriptors beyond the default set.
|
|
40
|
-
*/
|
|
41
|
-
export interface RenderAppComponentResult<TModules extends Array<AnyModule> | unknown = unknown>
|
|
42
|
-
extends RenderResult {
|
|
43
|
-
/**
|
|
44
|
-
* The Fusion instances backing the rendered component, nested under this single key so
|
|
45
|
-
* `vitest-browser-react`'s own `RenderResult` fields stay free to evolve without ever
|
|
46
|
-
* colliding with it.
|
|
47
|
-
*/
|
|
48
|
-
fusion: {
|
|
49
|
-
/** The parent Fusion instance the component's `FrameworkProvider` was given. */
|
|
50
|
-
framework: Fusion;
|
|
51
|
-
/**
|
|
52
|
-
* The resolved application module instance backing the rendered component — the same
|
|
53
|
-
* instance a real app would read via `useAppModule`/`useAppModules`. Drive a module
|
|
54
|
-
* directly (e.g. `fusion.app.context.setCurrentContextByIdAsync(id)`) to exercise a
|
|
55
|
-
* state change after the initial render, then assert the component re-rendered accordingly.
|
|
56
|
-
*/
|
|
57
|
-
app: AppModulesInstance<TModules>;
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Renders a component inside a real, mock-backed application module scope.
|
|
63
|
-
*
|
|
64
|
-
* @remarks
|
|
65
|
-
* Wraps `vitest-browser-react`'s `render` with the same provider nesting
|
|
66
|
-
* `createComponent` uses in production — a `FrameworkProvider` (the parent Fusion
|
|
67
|
-
* instance, from `mockFramework`) around a `ModuleProvider` (this app's own modules,
|
|
68
|
-
* from `mockAppModules`, `@equinor/fusion-framework-app/mock`). See {@link renderAppHook}
|
|
69
|
-
* for the equivalent helper when testing a hook in isolation, rather than a component.
|
|
70
|
-
*
|
|
71
|
-
* @template TModules - Module descriptors beyond the default set.
|
|
72
|
-
* @template TEnv - The application environment descriptor.
|
|
73
|
-
* @param ui - The component to render.
|
|
74
|
-
* @param options - A `configure` callback and `env` for `mockAppModules`, plus any other `render` option.
|
|
75
|
-
* @returns The `render` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* ```tsx
|
|
79
|
-
* const { getByText } = await renderAppComponent(<Apploader appKey="child-app" />, {
|
|
80
|
-
* fusion: await mockFramework<[AppModule]>((configurator) => {
|
|
81
|
-
* // register the child app's own manifest against the `app` module
|
|
82
|
-
* }),
|
|
83
|
-
* });
|
|
84
|
-
* await expect.element(getByText(/mounted/)).toBeInTheDocument();
|
|
85
|
-
* ```
|
|
86
|
-
*
|
|
87
|
-
* @example Drive a module directly and assert the re-render
|
|
88
|
-
* ```tsx
|
|
89
|
-
* const { getByText, fusion } = await renderAppComponent<[ContextModule]>(<App />, {
|
|
90
|
-
* configure: (configurator) => enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
|
|
91
|
-
* });
|
|
92
|
-
* await fusion.app.context.setCurrentContextByIdAsync(projectB.id);
|
|
93
|
-
* await expect.element(getByText(/project-b/)).toBeInTheDocument();
|
|
94
|
-
* ```
|
|
95
|
-
*/
|
|
96
|
-
export async function renderAppComponent<
|
|
97
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
98
|
-
TEnv extends AppEnv = AppEnv,
|
|
99
|
-
>(
|
|
100
|
-
ui: ReactElement,
|
|
101
|
-
options?: RenderAppComponentOptions<TModules, TEnv>,
|
|
102
|
-
): Promise<RenderAppComponentResult<TModules>> {
|
|
103
|
-
const { configure, env, fusion: providedFusion, ...renderOptions } = options ?? {};
|
|
104
|
-
const { framework, app } = await resolveAppScope<TModules, TEnv>({
|
|
105
|
-
configure,
|
|
106
|
-
env,
|
|
107
|
-
fusion: providedFusion,
|
|
108
|
-
});
|
|
109
|
-
const result = await render(ui, {
|
|
110
|
-
...renderOptions,
|
|
111
|
-
wrapper: createAppScopeWrapper<TModules>({ framework, app }),
|
|
112
|
-
});
|
|
113
|
-
return { ...result, fusion: { framework, app } };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export default renderAppComponent;
|
package/src/render-app-hook.tsx
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { renderHook } from 'vitest-browser-react';
|
|
2
|
-
import type { RenderHookOptions, RenderHookResult } from 'vitest-browser-react';
|
|
3
|
-
|
|
4
|
-
import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
|
|
5
|
-
import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
|
|
6
|
-
import type { Fusion } from '@equinor/fusion-framework';
|
|
7
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
8
|
-
|
|
9
|
-
import { resolveAppScope, createAppScopeWrapper } from './scope';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Options for {@link renderAppHook}.
|
|
13
|
-
*
|
|
14
|
-
* @template TModules - Module descriptors beyond the default set.
|
|
15
|
-
* @template TEnv - The application environment descriptor.
|
|
16
|
-
* @template Props - The props type accepted by the rendered hook.
|
|
17
|
-
*/
|
|
18
|
-
export interface RenderAppHookOptions<
|
|
19
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
20
|
-
TEnv extends AppEnv = AppEnv,
|
|
21
|
-
Props = undefined,
|
|
22
|
-
> extends Omit<RenderHookOptions<Props>, 'wrapper'> {
|
|
23
|
-
/** Configuration callback forwarded to {@link mockAppModules}. */
|
|
24
|
-
configure?: AppMockConfigureFn<TModules, TEnv>;
|
|
25
|
-
/** The application environment; defaults to a generic standalone test app. */
|
|
26
|
-
env?: TEnv;
|
|
27
|
-
/**
|
|
28
|
-
* The parent Fusion instance; defaults to a fresh {@link mockFramework} instance with
|
|
29
|
-
* this app's own manifest served. Pass one built beforehand to reuse a single instance
|
|
30
|
-
* across multiple `renderAppHook` calls, or to pre-configure parent-level modules (e.g.
|
|
31
|
-
* `http`, `context`, `serviceDiscovery`) the app reads through `useFramework`.
|
|
32
|
-
*/
|
|
33
|
-
fusion?: Fusion;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The result of {@link renderAppHook}: the `vitest-browser-react` `renderHook` result,
|
|
38
|
-
* plus the resolved application module scope and its parent Fusion instance.
|
|
39
|
-
*
|
|
40
|
-
* @template Result - The value returned by the rendered hook.
|
|
41
|
-
* @template Props - The props accepted by the rendered hook.
|
|
42
|
-
* @template TModules - Module descriptors beyond the default set.
|
|
43
|
-
*/
|
|
44
|
-
export interface RenderAppHookResult<
|
|
45
|
-
Result,
|
|
46
|
-
Props,
|
|
47
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
48
|
-
> extends RenderHookResult<Result, Props> {
|
|
49
|
-
/**
|
|
50
|
-
* The Fusion instances backing the rendered hook, nested under this single key so
|
|
51
|
-
* `vitest-browser-react`'s own `RenderHookResult` fields stay free to evolve without
|
|
52
|
-
* ever colliding with it.
|
|
53
|
-
*/
|
|
54
|
-
fusion: {
|
|
55
|
-
/** The parent Fusion instance the hook's `FrameworkProvider` was given. */
|
|
56
|
-
framework: Fusion;
|
|
57
|
-
/**
|
|
58
|
-
* The resolved application module instance backing the rendered hook — the same
|
|
59
|
-
* instance a real app would read via `useAppModule`/`useAppModules`. Drive a module
|
|
60
|
-
* not returned by the hook itself (e.g. `fusion.app.context.setCurrentContextByIdAsync(id)`)
|
|
61
|
-
* to exercise a state change after the initial render.
|
|
62
|
-
*/
|
|
63
|
-
app: AppModulesInstance<TModules>;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Renders a hook inside a real, mock-backed application module scope.
|
|
69
|
-
*
|
|
70
|
-
* @remarks
|
|
71
|
-
* Wraps `vitest-browser-react`'s `renderHook` with the same provider nesting
|
|
72
|
-
* `createComponent` uses in production — a `FrameworkProvider` (the parent Fusion
|
|
73
|
-
* instance, from `mockFramework`) around a `ModuleProvider` (this app's own modules,
|
|
74
|
-
* from `mockAppModules`, `@equinor/fusion-framework-app/mock`) — the real
|
|
75
|
-
* `event`/`http`/`msal` module pipeline. Only requests a seeded middleware answers are
|
|
76
|
-
* faked; a request with no matching middleware still reaches the real network. Use this
|
|
77
|
-
* for any hook that reads from the application module scope or the parent framework
|
|
78
|
-
* (e.g. `useAppModule`, `useAccessToken`, `useFramework`), instead of hand-wiring
|
|
79
|
-
* `mockFramework`, `mockAppModules`, `FrameworkProvider` and `ModuleProvider` in every test.
|
|
80
|
-
*
|
|
81
|
-
* @template Result - The value returned by the rendered hook.
|
|
82
|
-
* @template Props - The props accepted by the rendered hook.
|
|
83
|
-
* @template TModules - Module descriptors beyond the default set.
|
|
84
|
-
* @template TEnv - The application environment descriptor.
|
|
85
|
-
* @param render - The hook to render, receiving `initialProps`.
|
|
86
|
-
* @param options - A `configure` callback and `env` for `mockAppModules`, plus any other
|
|
87
|
-
* `renderHook` option.
|
|
88
|
-
* @returns The `renderHook` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```tsx
|
|
92
|
-
* const { result } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }));
|
|
93
|
-
* await vi.waitFor(() => expect(result.current.pending).toBe(false));
|
|
94
|
-
* ```
|
|
95
|
-
*
|
|
96
|
-
* @example Sign in a named user
|
|
97
|
-
* ```tsx
|
|
98
|
-
* const { result } = await renderAppHook(() => useCurrentAccount(), {
|
|
99
|
-
* configure: (configurator) => configurator.msal.setAccount({ name: 'Ada Lovelace' }),
|
|
100
|
-
* });
|
|
101
|
-
* ```
|
|
102
|
-
*
|
|
103
|
-
* @example Reuse a pre-built parent Fusion instance
|
|
104
|
-
* ```tsx
|
|
105
|
-
* const fusion = await mockFramework<[AppModule]>((configurator) =>
|
|
106
|
-
* enableAppManifestMock(configurator, env),
|
|
107
|
-
* );
|
|
108
|
-
*
|
|
109
|
-
* const { result: a } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }), { fusion });
|
|
110
|
-
* const { result: b } = await renderAppHook(() => useCurrentAccount(), { fusion });
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
export async function renderAppHook<
|
|
114
|
-
Result,
|
|
115
|
-
Props = undefined,
|
|
116
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
117
|
-
TEnv extends AppEnv = AppEnv,
|
|
118
|
-
>(
|
|
119
|
-
render: (initialProps?: Props) => Result,
|
|
120
|
-
options?: RenderAppHookOptions<TModules, TEnv, Props>,
|
|
121
|
-
): Promise<RenderAppHookResult<Result, Props, TModules>> {
|
|
122
|
-
const { configure, env, fusion: providedFusion, ...renderHookOptions } = options ?? {};
|
|
123
|
-
const { framework, app } = await resolveAppScope<TModules, TEnv>({
|
|
124
|
-
configure,
|
|
125
|
-
env,
|
|
126
|
-
fusion: providedFusion,
|
|
127
|
-
});
|
|
128
|
-
const result = await renderHook(render, {
|
|
129
|
-
...renderHookOptions,
|
|
130
|
-
wrapper: createAppScopeWrapper<TModules>({ framework, app }),
|
|
131
|
-
});
|
|
132
|
-
return { ...result, fusion: { framework, app } };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export default renderAppHook;
|
package/src/render.tsx
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from 'react';
|
|
2
|
-
|
|
3
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
4
|
-
import type { AppEnv } from '@equinor/fusion-framework-app';
|
|
5
|
-
import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
renderAppComponent,
|
|
9
|
-
type RenderAppComponentOptions,
|
|
10
|
-
type RenderAppComponentResult,
|
|
11
|
-
} from './render-app-component';
|
|
12
|
-
|
|
13
|
-
// resolved at test-time by `appTestVitePlugin` (@equinor/fusion-framework-vitest-plugin-react-app);
|
|
14
|
-
// see virtual-modules.d.ts for the ambient module declarations
|
|
15
|
-
import { manifest, config } from 'virtual:fusion-app-test-env';
|
|
16
|
-
import { configure } from 'virtual:fusion-app-test-configure';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Renders a component inside the application's own module scope for use in plain `describe`/`it`
|
|
20
|
-
* tests, using the manifest, config, and module-configurator resolved for this application — no
|
|
21
|
-
* per-test wiring, and no custom `test` fixture required.
|
|
22
|
-
*
|
|
23
|
-
* @remarks
|
|
24
|
-
* Requires `appTestVitePlugin` (`@equinor/fusion-framework-vitest-plugin-react-app`) registered in
|
|
25
|
-
* your `vitest.config.ts` `plugins`, which serves the virtual modules backing the resolved
|
|
26
|
-
* `env`/`configure`. Running the same test file without the plugin registered fails to resolve
|
|
27
|
-
* those imports.
|
|
28
|
-
*
|
|
29
|
-
* Pass `env` or `configure` in `options` to override the resolved values for a single render.
|
|
30
|
-
*
|
|
31
|
-
* @template TModules - Module descriptors beyond the default set.
|
|
32
|
-
* @param ui - The component to render.
|
|
33
|
-
* @param options - Overrides for `env`/`configure`, plus any other `renderAppComponent` option.
|
|
34
|
-
* @returns The `render` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
|
|
35
|
-
* @example
|
|
36
|
-
* ```tsx
|
|
37
|
-
* import { describe, expect, it } from 'vitest';
|
|
38
|
-
* import { render } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
|
|
39
|
-
* import { App } from '../App';
|
|
40
|
-
*
|
|
41
|
-
* describe('App', () => {
|
|
42
|
-
* it('renders the app', async () => {
|
|
43
|
-
* const { getByRole } = await render(<App />);
|
|
44
|
-
* await expect.element(getByRole('heading')).toBeVisible();
|
|
45
|
-
* });
|
|
46
|
-
* });
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
export async function render<TModules extends Array<AnyModule> | unknown = unknown>(
|
|
50
|
-
ui: ReactElement,
|
|
51
|
-
options?: RenderAppComponentOptions<TModules, AppEnv>,
|
|
52
|
-
): Promise<RenderAppComponentResult<TModules>> {
|
|
53
|
-
const { configure: configureOverride, env: envOverride, ...renderOptions } = options ?? {};
|
|
54
|
-
return renderAppComponent<TModules, AppEnv>(ui, {
|
|
55
|
-
...renderOptions,
|
|
56
|
-
env: envOverride ?? { manifest, config },
|
|
57
|
-
// the resolved `configure` is generic over the app's own module set (`unknown`), while
|
|
58
|
-
// `TModules` here is caller-supplied *extra* modules — safe to widen for the common case
|
|
59
|
-
// where callers don't override it with a `TModules`-specific configurator
|
|
60
|
-
configure: (configureOverride ?? configure) as AppMockConfigureFn<TModules, AppEnv> | undefined,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export default render;
|