@equinor/fusion-framework-vitest-plugin-react-app 1.0.0 → 1.0.2

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 (50) hide show
  1. package/dist/esm/index.js +9 -4
  2. package/dist/esm/index.js.map +1 -1
  3. package/dist/esm/resolve-app-test-env.js +8 -2
  4. package/dist/esm/resolve-app-test-env.js.map +1 -1
  5. package/dist/esm/scope/resolve-fusion.js +22 -24
  6. package/dist/esm/scope/resolve-fusion.js.map +1 -1
  7. package/dist/esm/test-app.js +5 -8
  8. package/dist/esm/test-app.js.map +1 -1
  9. package/dist/esm/test.js +2 -1
  10. package/dist/esm/test.js.map +1 -1
  11. package/dist/esm/version.js +1 -1
  12. package/dist/tsconfig.tsbuildinfo +1 -1
  13. package/dist/types/index.d.ts +4 -3
  14. package/dist/types/resolve-app-test-env.d.ts +3 -1
  15. package/dist/types/scope/resolve-fusion.d.ts +6 -7
  16. package/dist/types/test-app.d.ts +12 -11
  17. package/dist/types/test.d.ts +20 -9
  18. package/dist/types/version.d.ts +1 -1
  19. package/package.json +17 -19
  20. package/CHANGELOG.md +0 -81
  21. package/docs/advanced.md +0 -209
  22. package/docs/configuration.md +0 -112
  23. package/docs/getting-started.md +0 -66
  24. package/docs/migrating-an-existing-app.md +0 -202
  25. package/docs/module-mocks.md +0 -120
  26. package/docs/overview.md +0 -49
  27. package/docs/troubleshooting.md +0 -67
  28. package/docs/why-browser-mode.md +0 -128
  29. package/src/__tests__/app-test-vite-plugin.test.ts +0 -109
  30. package/src/__tests__/merge-env-config.test.ts +0 -67
  31. package/src/__tests__/resolve-app-test-env.test.ts +0 -83
  32. package/src/app-test.ts +0 -19
  33. package/src/define-project.ts +0 -91
  34. package/src/index.ts +0 -124
  35. package/src/merge-env-config.ts +0 -58
  36. package/src/render-app-component.tsx +0 -116
  37. package/src/render-app-hook.tsx +0 -135
  38. package/src/render.tsx +0 -64
  39. package/src/resolve-app-test-env.ts +0 -132
  40. package/src/scope/create-app-scope-wrapper.tsx +0 -26
  41. package/src/scope/default-app-env.ts +0 -13
  42. package/src/scope/index.ts +0 -4
  43. package/src/scope/resolve-app-scope.ts +0 -46
  44. package/src/scope/resolve-fusion.ts +0 -74
  45. package/src/test-app.tsx +0 -148
  46. package/src/test.tsx +0 -42
  47. package/src/version.ts +0 -2
  48. package/src/virtual-modules.d.ts +0 -12
  49. package/tsconfig.json +0 -26
  50. package/vitest.config.ts +0 -10
@@ -1,128 +0,0 @@
1
- # Why Browser Mode is the default
2
-
3
- `@equinor/fusion-framework-vitest-plugin-react-app` runs component and hook tests in headless
4
- Chromium by default. A real browser gives these tests more accurate DOM behavior, but it is
5
- slower than `happy-dom` or `jsdom`.
6
-
7
- This guide explains that tradeoff and shows how to use a different browser or renderer when a
8
- test does not need Chromium.
9
-
10
- ## Performance
11
-
12
- A real browser is slower than DOM emulation. `happy-dom` and `jsdom` run in the same Node process
13
- as the test. Browser Mode starts Chromium through Playwright and communicates with that browser
14
- process.
15
-
16
- Choose Browser Mode for fidelity, not speed. Keep fast unit tests on Node or DOM emulation when
17
- they do not need browser behavior.
18
-
19
- ## Why use a real browser
20
-
21
- - **React compatibility:** the previous React test setup failed after its React 19 peer
22
- dependency update. The repository then moved those tests to Vitest Browser Mode. See the
23
- `packages/utils/observable` 8.4.4 changelog and migration commit `059aefae5d`.
24
- - **Real framework execution:** tests control external boundaries and seed module state through
25
- [Module mocks](module-mocks.md), while the actual module configuration, lifecycle, providers,
26
- and rendering behavior still run.
27
- - **Browser fidelity:** Chromium provides real layout, `ResizeObserver`, custom elements, and
28
- Shadow DOM — behavior `happy-dom` and `jsdom` only approximate.
29
-
30
- > [!WARNING]
31
- > A DOM-emulation gap is usually patched one of two ways: a polyfill standing in for the missing
32
- > browser API, or a component replacement that avoids exercising it at all. Both are test-only
33
- > code with no equivalent in production, and both defeat the point of the test: a polyfill can
34
- > drift from real browser behavior unnoticed, and a mocked component only proves the *mock*
35
- > renders correctly, never the real one. A passing test built on either can still fail — or
36
- > silently lie — against the real component. Treat every DOM-emulation workaround as debt to
37
- > remove, not a pattern to reach for.
38
-
39
- A real browser does not eliminate every workaround: AG Grid license messages and Lit dev-mode
40
- warnings are console noise from real Chromium runs, not DOM-emulation gaps, and suppressing them
41
- carries none of the risk above (see [Module mocks](module-mocks.md) for the supported way to
42
- seed module state instead of replacing a component). Remove a DOM-emulation-only workaround only
43
- after confirming the affected test still passes against the real component in Browser Mode.
44
-
45
- ## Choose a different runtime
46
-
47
- You can change either the browser provider or the renderer:
48
-
49
- - Stay in Browser Mode and replace Playwright with another Vitest browser provider.
50
- - Keep the Fusion module setup but render with `happy-dom` or `jsdom`.
51
-
52
- ### Use another browser provider
53
-
54
- Pass `test.browser.provider` to `defineProject` to replace Playwright while staying in Browser
55
- Mode. This example uses WebdriverIO:
56
-
57
- ```ts
58
- import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
59
- import { webdriverio } from '@vitest/browser-webdriverio';
60
-
61
- export default defineProject({
62
- test: { browser: { provider: webdriverio() } },
63
- });
64
- ```
65
-
66
- See [Configuration](configuration.md) for the complete `defineProject` behavior.
67
-
68
- ### Use happy-dom or jsdom
69
-
70
- > [!CAUTION]
71
- > Rendering with `happy-dom` or `jsdom` reintroduces the DOM-emulation tradeoff above: any gap
72
- > between the emulation and a real browser has to be closed with a polyfill or a component
73
- > replacement, not fixed. Only take this path for a component or hook that provably does not
74
- > need browser fidelity — otherwise stay in Browser Mode.
75
-
76
- The Fusion module setup does not depend on Browser Mode. Build the same provider tree and pass it
77
- to another renderer. The example below uses `@testing-library/react` on `happy-dom`:
78
-
79
- ```tsx
80
- import { render } from '@testing-library/react';
81
- import { mockFramework } from '@equinor/fusion-framework/mock';
82
- import { enableAppManifestMock, mockAppModules } from '@equinor/fusion-framework-app/mock';
83
- import { FrameworkProvider } from '@equinor/fusion-framework-react';
84
- import { ModuleProvider } from '@equinor/fusion-framework-react-module';
85
- import type { AppModule } from '@equinor/fusion-framework-module-app';
86
- import type { ReactElement } from 'react';
87
-
88
- const env = {
89
- manifest: {
90
- appKey: 'test-app',
91
- displayName: 'Test App',
92
- description: 'Test app',
93
- type: 'standalone',
94
- },
95
- } as const;
96
-
97
- async function renderWithHappyDom(ui: ReactElement) {
98
- const framework = await mockFramework<[AppModule]>((configurator) =>
99
- enableAppManifestMock(configurator, env),
100
- );
101
- const app = await mockAppModules(undefined, env, framework);
102
- return render(ui, {
103
- wrapper: ({ children }) => (
104
- <FrameworkProvider value={framework}>
105
- <ModuleProvider value={app}>{children}</ModuleProvider>
106
- </FrameworkProvider>
107
- ),
108
- });
109
- }
110
- ```
111
-
112
- Run the test in a Vitest project with `environment: 'happy-dom'` instead of
113
- `browser.enabled`. Use a separate project if the same app also has Browser Mode tests.
114
-
115
- Call the same `enable*Mock` functions from the `mockAppModules` configuration callback. These
116
- mocks configure Fusion modules and do not depend on the renderer. `renderHook` from
117
- `@testing-library/react` can use the same wrapper for hook tests.
118
-
119
- Use Browser Mode when browser behavior matters. Use `happy-dom`, `jsdom`, or no DOM for focused
120
- tests that only need application logic or simple rendering.
121
-
122
- ## Related documentation
123
-
124
- - [Overview](overview.md)
125
- - [Configuration](configuration.md)
126
- - [Module mocks](module-mocks.md)
127
- - [Migrate an existing app to Fusion Vitest](migrating-an-existing-app.md)
128
- - [Choose a Fusion testing layer](../../../framework/docs/testing-choosing-a-layer.md)
@@ -1,109 +0,0 @@
1
- import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
-
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
-
7
- import { appTestVitePlugin } from '../index.js';
8
-
9
- type ResolveIdHook = (id: string) => string | null | undefined;
10
- type LoadHook = (id: string) => string | null | undefined | Promise<string | null | undefined>;
11
- type ConfigResolvedHook = (config: { root: string }) => void;
12
-
13
- describe('appTestVitePlugin', () => {
14
- let dir: string;
15
-
16
- beforeEach(async () => {
17
- dir = await mkdtemp(join(tmpdir(), 'fusion-vitest-plugin-react-app-'));
18
- await writeFile(
19
- join(dir, 'package.json'),
20
- JSON.stringify({ name: '@equinor/my-app', version: '1.2.3' }),
21
- );
22
- });
23
-
24
- afterEach(async () => {
25
- await rm(dir, { recursive: true, force: true });
26
- });
27
-
28
- it('claims only its two virtual specifiers, leaving everything else unresolved', () => {
29
- const plugin = appTestVitePlugin({ entrypoint: dir });
30
- const resolveId = plugin.resolveId as ResolveIdHook;
31
-
32
- expect(resolveId('virtual:fusion-app-test-env')).toBe('\0virtual:fusion-app-test-env');
33
- expect(resolveId('virtual:fusion-app-test-configure')).toBe(
34
- '\0virtual:fusion-app-test-configure',
35
- );
36
- expect(resolveId('some-other-module')).toBeNull();
37
- });
38
-
39
- it('serves the resolved manifest/config as the env virtual module', async () => {
40
- const plugin = appTestVitePlugin({ entrypoint: dir });
41
- const load = plugin.load as LoadHook;
42
-
43
- const source = await load('\0virtual:fusion-app-test-env');
44
-
45
- expect(source).toContain('"appKey":"my-app"');
46
- expect(source).toContain('export const config = {"environment":{}};');
47
- });
48
-
49
- it('serves an undefined configure export when no config file exists', async () => {
50
- const plugin = appTestVitePlugin({ entrypoint: dir });
51
- const load = plugin.load as LoadHook;
52
-
53
- const source = await load('\0virtual:fusion-app-test-configure');
54
-
55
- expect(source).toBe('export const configure = undefined;');
56
- });
57
-
58
- it('resolves the app from the Vitest project root when no entrypoint is provided', async () => {
59
- await mkdir(join(dir, 'src'), { recursive: true });
60
- await writeFile(join(dir, 'src', 'index.ts'), 'export {};');
61
- await writeFile(join(dir, 'src', 'config.ts'), 'export default () => undefined;');
62
- const plugin = appTestVitePlugin();
63
- const configResolved = plugin.configResolved as ConfigResolvedHook;
64
- const load = plugin.load as LoadHook;
65
-
66
- configResolved({ root: dir });
67
-
68
- expect(await load('\0virtual:fusion-app-test-env')).toContain('"appKey":"my-app"');
69
- expect(await load('\0virtual:fusion-app-test-configure')).toBe(
70
- `export { default as configure } from ${JSON.stringify(join(dir, 'src', 'config.ts'))};`,
71
- );
72
- });
73
-
74
- it('re-exports the default candidate config file as the configure virtual module', async () => {
75
- await mkdir(join(dir, 'src'), { recursive: true });
76
- await writeFile(join(dir, 'src', 'config.ts'), 'export default () => undefined;');
77
- const plugin = appTestVitePlugin({ entrypoint: dir });
78
- const load = plugin.load as LoadHook;
79
-
80
- const source = await load('\0virtual:fusion-app-test-configure');
81
-
82
- expect(source).toBe(
83
- `export { default as configure } from ${JSON.stringify(join(dir, 'src', 'config.ts'))};`,
84
- );
85
- });
86
-
87
- it('re-exports an explicitly requested configure file', async () => {
88
- await writeFile(join(dir, 'my-config.ts'), 'export default () => undefined;');
89
- const plugin = appTestVitePlugin({ entrypoint: dir, configure: 'my-config.ts' });
90
- const load = plugin.load as LoadHook;
91
-
92
- const source = await load('\0virtual:fusion-app-test-configure');
93
-
94
- expect(source).toBe(
95
- `export { default as configure } from ${JSON.stringify(join(dir, 'my-config.ts'))};`,
96
- );
97
- });
98
-
99
- it('throws when an explicitly requested configure file does not exist', () => {
100
- expect(() => appTestVitePlugin({ entrypoint: dir, configure: 'missing.ts' })).toThrow();
101
- });
102
-
103
- it('returns null for module ids other than its own virtual specifiers', async () => {
104
- const plugin = appTestVitePlugin({ entrypoint: dir });
105
- const load = plugin.load as LoadHook;
106
-
107
- expect(await load('some-other-module')).toBeNull();
108
- });
109
- });
@@ -1,67 +0,0 @@
1
- import { AppConfig } from '@equinor/fusion-framework-module-app';
2
- import type { AppEnv } from '@equinor/fusion-framework-app';
3
- import { describe, expect, it } from 'vitest';
4
-
5
- import { mergeEnvConfig } from '../merge-env-config.js';
6
-
7
- describe('mergeEnvConfig', () => {
8
- const baseEnv: AppEnv = {
9
- manifest: {
10
- appKey: 'test-app',
11
- displayName: 'Test App',
12
- description: 'A test application',
13
- type: 'standalone',
14
- },
15
- config: new AppConfig({
16
- environment: { foo: 'bar' },
17
- endpoints: { api: { url: 'https://api.example.com', scopes: ['api.read'] } },
18
- }),
19
- };
20
-
21
- it('adds a new endpoint without dropping existing ones', () => {
22
- const merged = mergeEnvConfig(baseEnv, {
23
- endpoints: { 'cpr-api': { url: 'https://cpr.example.com' } },
24
- });
25
-
26
- expect(merged.config?.endpoints).toEqual({
27
- api: { url: 'https://api.example.com', scopes: ['api.read'] },
28
- 'cpr-api': { url: 'https://cpr.example.com', scopes: [] },
29
- });
30
- });
31
-
32
- it('overrides one field of an existing endpoint, keeping the rest', () => {
33
- const merged = mergeEnvConfig(baseEnv, {
34
- endpoints: { api: { url: 'https://fake.example.com' } },
35
- });
36
-
37
- expect(merged.config?.endpoints.api).toEqual({
38
- url: 'https://fake.example.com',
39
- scopes: ['api.read'],
40
- });
41
- });
42
-
43
- it('merges environment overrides over the existing environment', () => {
44
- const merged = mergeEnvConfig(baseEnv, { environment: { baz: 'qux' } });
45
-
46
- expect(merged.config?.environment).toEqual({ foo: 'bar', baz: 'qux' });
47
- });
48
-
49
- it('leaves the original env and its config untouched', () => {
50
- mergeEnvConfig(baseEnv, { endpoints: { api: { url: 'https://fake.example.com' } } });
51
-
52
- expect(baseEnv.config?.endpoints.api).toEqual({
53
- url: 'https://api.example.com',
54
- scopes: ['api.read'],
55
- });
56
- });
57
-
58
- it('handles an env with no existing config', () => {
59
- const env: AppEnv = { manifest: baseEnv.manifest };
60
- const merged = mergeEnvConfig(env, { endpoints: { api: { url: 'https://api.example.com' } } });
61
-
62
- expect(merged.config?.endpoints).toEqual({
63
- api: { url: 'https://api.example.com', scopes: [] },
64
- });
65
- expect(merged.config?.environment).toEqual({});
66
- });
67
- });
@@ -1,83 +0,0 @@
1
- import { mkdtemp, rm, writeFile } from 'node:fs/promises';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
-
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
-
7
- import { resolveAppTestEnv } from '../resolve-app-test-env.js';
8
-
9
- describe('resolveAppTestEnv', () => {
10
- let dir: string;
11
-
12
- beforeEach(async () => {
13
- dir = await mkdtemp(join(tmpdir(), 'fusion-vitest-plugin-react-app-'));
14
- await writeFile(
15
- join(dir, 'package.json'),
16
- JSON.stringify({ name: '@equinor/my-app', version: '1.2.3', description: 'a test app' }),
17
- );
18
- });
19
-
20
- afterEach(async () => {
21
- await rm(dir, { recursive: true, force: true });
22
- });
23
-
24
- it('falls back to a package-derived manifest and an empty config with no local files', async () => {
25
- const { manifest, config } = await resolveAppTestEnv({ entrypoint: dir });
26
-
27
- expect(manifest).toMatchObject({
28
- appKey: 'my-app',
29
- displayName: '@equinor/my-app',
30
- description: 'a test app',
31
- type: 'standalone',
32
- });
33
- expect(config).toEqual({ environment: {} });
34
- });
35
-
36
- it('merges a local app.manifest.ts and app.config.ts, same as ffc app build', async () => {
37
- await writeFile(
38
- join(dir, 'app.manifest.ts'),
39
- "export default { displayName: 'My Custom App' };",
40
- );
41
- await writeFile(
42
- join(dir, 'app.config.ts'),
43
- "export default { environment: {}, endpoints: { api: { url: 'https://example.com' } } };",
44
- );
45
-
46
- const { manifest, config } = await resolveAppTestEnv({ entrypoint: dir });
47
-
48
- expect(manifest).toMatchObject({ appKey: 'my-app', displayName: 'My Custom App' });
49
- expect(config).toEqual({
50
- environment: {},
51
- endpoints: { api: { url: 'https://example.com', scopes: [] } },
52
- });
53
- });
54
-
55
- it('applies an inline manifest/config function directly, same as a loaded file default export', async () => {
56
- const { manifest, config } = await resolveAppTestEnv({
57
- entrypoint: dir,
58
- manifest: () => ({ displayName: 'Inline App' }),
59
- config: () => ({
60
- environment: {},
61
- endpoints: { api: { url: 'https://inline.example.com' } },
62
- }),
63
- });
64
-
65
- expect(manifest).toMatchObject({ appKey: 'my-app', displayName: 'Inline App' });
66
- expect(config).toEqual({
67
- environment: {},
68
- endpoints: { api: { url: 'https://inline.example.com', scopes: [] } },
69
- });
70
- });
71
-
72
- it('propagates an error for an explicitly requested manifest file that does not exist', async () => {
73
- await expect(
74
- resolveAppTestEnv({ entrypoint: dir, manifest: 'does-not-exist.manifest.ts' }),
75
- ).rejects.toThrow();
76
- });
77
-
78
- it('propagates an error for an explicitly requested config file that does not exist', async () => {
79
- await expect(
80
- resolveAppTestEnv({ entrypoint: dir, config: 'does-not-exist.config.ts' }),
81
- ).rejects.toThrow();
82
- });
83
- });
package/src/app-test.ts DELETED
@@ -1,19 +0,0 @@
1
- export {
2
- renderAppHook,
3
- type RenderAppHookOptions,
4
- type RenderAppHookResult,
5
- } from './render-app-hook';
6
- export {
7
- renderAppComponent,
8
- type RenderAppComponentOptions,
9
- type RenderAppComponentResult,
10
- } from './render-app-component';
11
- export { testApp } from './test-app';
12
- export { mergeEnvConfig, type MergeEnvConfigOverrides } from './merge-env-config.js';
13
- export type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
14
-
15
- // `test`/`render` import virtual modules only served once `appTestVitePlugin`
16
- // (@equinor/fusion-framework-vitest-plugin-react-app) is registered — using any export from
17
- // this module requires the plugin registered in your `vitest.config.ts` `plugins`.
18
- export { test } from './test';
19
- export { render } from './render';
@@ -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;