@equinor/fusion-framework-app 13.0.1 → 14.0.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 (47) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +35 -65
  3. package/dist/esm/__tests__/mock/AppMockConfigurator.test.js +70 -0
  4. package/dist/esm/__tests__/mock/AppMockConfigurator.test.js.map +1 -0
  5. package/dist/esm/__tests__/mock/mock-app.test.js +86 -0
  6. package/dist/esm/__tests__/mock/mock-app.test.js.map +1 -0
  7. package/dist/esm/__tests__/mock/msal-hoisting.test.js +36 -0
  8. package/dist/esm/__tests__/mock/msal-hoisting.test.js.map +1 -0
  9. package/dist/esm/configure-modules.js +2 -42
  10. package/dist/esm/configure-modules.js.map +1 -1
  11. package/dist/esm/initialize-app-modules.js +65 -0
  12. package/dist/esm/initialize-app-modules.js.map +1 -0
  13. package/dist/esm/mock/AppMockConfigurator.js +183 -0
  14. package/dist/esm/mock/AppMockConfigurator.js.map +1 -0
  15. package/dist/esm/mock/enable-app-manifest-mock.js +49 -0
  16. package/dist/esm/mock/enable-app-manifest-mock.js.map +1 -0
  17. package/dist/esm/mock/index.js +21 -0
  18. package/dist/esm/mock/index.js.map +1 -0
  19. package/dist/esm/mock/mock-app-modules.js +82 -0
  20. package/dist/esm/mock/mock-app-modules.js.map +1 -0
  21. package/dist/esm/version.js +1 -1
  22. package/dist/esm/version.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/dist/types/__tests__/mock/AppMockConfigurator.test.d.ts +1 -0
  25. package/dist/types/__tests__/mock/mock-app.test.d.ts +1 -0
  26. package/dist/types/__tests__/mock/msal-hoisting.test.d.ts +1 -0
  27. package/dist/types/initialize-app-modules.d.ts +31 -0
  28. package/dist/types/mock/AppMockConfigurator.d.ts +142 -0
  29. package/dist/types/mock/enable-app-manifest-mock.d.ts +33 -0
  30. package/dist/types/mock/index.d.ts +20 -0
  31. package/dist/types/mock/mock-app-modules.d.ts +72 -0
  32. package/dist/types/version.d.ts +1 -1
  33. package/docs/bookmarks.md +18 -0
  34. package/docs/http-clients.md +71 -0
  35. package/docs/testing.md +105 -0
  36. package/package.json +22 -13
  37. package/src/__tests__/mock/AppMockConfigurator.test.ts +96 -0
  38. package/src/__tests__/mock/mock-app.test.ts +112 -0
  39. package/src/__tests__/mock/msal-hoisting.test.ts +54 -0
  40. package/src/configure-modules.ts +2 -52
  41. package/src/initialize-app-modules.ts +98 -0
  42. package/src/mock/AppMockConfigurator.ts +218 -0
  43. package/src/mock/enable-app-manifest-mock.ts +62 -0
  44. package/src/mock/index.ts +21 -0
  45. package/src/mock/mock-app-modules.ts +109 -0
  46. package/src/version.ts +1 -1
  47. package/vitest.config.ts +1 -1
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import type { Fusion } from '@equinor/fusion-framework';
2
+ import type { AnyModule } from '@equinor/fusion-framework-module';
3
+ import type { AppConfigurator } from './AppConfigurator';
4
+ import type { AppModulesInstance, AppEnv } from './types';
5
+ /**
6
+ * Runs the telemetry wiring, the caller's configuration callback and module
7
+ * initialization against an already constructed configurator.
8
+ *
9
+ * @remarks
10
+ * Extracted so `mockAppModules` (`@equinor/fusion-framework-app/mock`) can drive the
11
+ * exact same pipeline against an `AppMockConfigurator` instead of reimplementing
12
+ * it — the same way `FrameworkConfigurator` and `FrameworkMockConfigurator`
13
+ * share the framework's `init`.
14
+ *
15
+ * @param configurator - The (real or mock) app configurator to run the pipeline on.
16
+ * @param cb - Configuration callback invoked before module initialization, or `undefined` to skip it.
17
+ * @param args - Object containing the Fusion instance and the application environment.
18
+ * @returns The fully initialized application module instance.
19
+ * @template TModules - Application module descriptors beyond the default set.
20
+ * @template TRef - The parent Fusion instance type.
21
+ * @template TEnv - The application environment descriptor.
22
+ * @template TConfigurator - The (real or mock) `AppConfigurator` subclass driving the pipeline.
23
+ */
24
+ export declare function initializeAppModules<TModules extends Array<AnyModule> | never, TRef extends Fusion = Fusion, TEnv extends AppEnv = AppEnv, TConfigurator extends AppConfigurator<TModules, TRef['modules'], TEnv> = AppConfigurator<TModules, TRef['modules'], TEnv>>(configurator: TConfigurator, cb: ((configurator: TConfigurator, args: {
25
+ fusion: TRef;
26
+ env: TEnv;
27
+ }) => void | Promise<void>) | undefined, args: {
28
+ fusion: TRef;
29
+ env: TEnv;
30
+ }): Promise<AppModulesInstance<TModules>>;
31
+ export default initializeAppModules;
@@ -0,0 +1,142 @@
1
+ import type { FusionModulesInstance } from '@equinor/fusion-framework';
2
+ import type { AnyModule, IModuleConfigurator, ModuleConfigType } from '@equinor/fusion-framework-module';
3
+ import { type IHttpClientConfigurator } from '@equinor/fusion-framework-module-http';
4
+ import { type MsalMockConfigurator } from '@equinor/fusion-framework-module-msal/mock';
5
+ import { AppConfigurator } from '../AppConfigurator.js';
6
+ import type { AppEnv } from '../types.js';
7
+ /**
8
+ * The real `AppConfigurator`, with the `msal` module it registers backed by
9
+ * the same test double `FrameworkMockConfigurator` uses. `http` is the real
10
+ * module — fake a response by registering a short-circuiting middleware
11
+ * through `.http.addMiddleware(...)` instead of swapping the module out.
12
+ *
13
+ * @remarks
14
+ * Nothing else changes: the same module set (`event`, `http`, `msal`), the same
15
+ * configuration pipeline and the same lifecycle are used. `configureHttpClient`,
16
+ * `useFrameworkServiceClient` and any callback written for a real
17
+ * `AppConfigurator` work against this unchanged.
18
+ *
19
+ * `http` and `msal` are pinned early — mirroring `FrameworkMockConfigurator`,
20
+ * one level down — so `.http` and `.msal` are reachable synchronously, before
21
+ * `useFrameworkServiceClient` or a `configureModules` callback ever runs.
22
+ * `event` is deliberately not pinned, for the same reason it isn't in
23
+ * `FrameworkMockConfigurator`: its `configure` factory reads `ref` to wire
24
+ * bubbling to a parent event provider, and pinning would freeze that decision
25
+ * before a `ref` could ever be known.
26
+ *
27
+ * @typeParam TModules - Module descriptors beyond the default set. Supply this
28
+ * when a test registers application modules, so they are typed on the result.
29
+ * @typeParam TRef - The resolved Fusion modules instance used as a reference during initialization.
30
+ * @typeParam TEnv - The application environment descriptor.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
35
+ * const configurator = new AppMockConfigurator({ manifest });
36
+ *
37
+ * configurator.configureHttpClient('catalog', { baseUri: 'https://api.example.com' });
38
+ * configurator.http.addMiddleware(async (uri, init, next) =>
39
+ * uri === 'https://api.example.com/items' ? Response.json([{ id: 1 }]) : next(uri, init),
40
+ * );
41
+ * ```
42
+ */
43
+ export declare class AppMockConfigurator<TModules extends Array<AnyModule> | unknown = unknown, TRef extends FusionModulesInstance = FusionModulesInstance, TEnv extends AppEnv = AppEnv> extends AppConfigurator<TModules, TRef, TEnv> {
44
+ #private;
45
+ static readonly className: string;
46
+ /**
47
+ * Creates an app configurator backed by the built-in mock modules.
48
+ *
49
+ * @param env - The application environment containing manifest, config, and optional basename.
50
+ */
51
+ constructor(env: TEnv);
52
+ /**
53
+ * No-ops the base constructor's own call to this, since it would otherwise run
54
+ * before {@link _pin} has anything to redirect `addConfig` at; this class calls
55
+ * {@link AppConfigurator._configureHttpClientsFromAppConfig} itself once pinned.
56
+ */
57
+ protected _configureHttpClientsFromAppConfig(): void;
58
+ /**
59
+ * Registers a module configurator, redirecting registrations for a pinned module
60
+ * at its pinned descriptor.
61
+ *
62
+ * @remarks
63
+ * `configureHttpClient`, `useFrameworkServiceClient` and similar helpers always
64
+ * pass the real, unpinned module descriptor — the base `addConfig` replaces a
65
+ * module's descriptor whenever it doesn't recognize the object it's given, even
66
+ * under the same name, which would otherwise silently un-pin it.
67
+ *
68
+ * @param config - The module configurator descriptor to register.
69
+ * @template T - The module type being configured.
70
+ * @template TConfig - The resolved configuration type for the module.
71
+ */
72
+ addConfig<T extends AnyModule, TConfig = ModuleConfigType<T>>(config: IModuleConfigurator<T, TRef, TConfig>): void;
73
+ /**
74
+ * Pins a module to a single configurator instance for the lifetime of this
75
+ * configurator, so it can be reached by name through {@link _getConfig}.
76
+ *
77
+ * @remarks
78
+ * The module system otherwise builds a fresh configurator from its own
79
+ * `configure` factory during the configure phase — too late for a test to
80
+ * reach, and a new instance on every call besides. This replaces that factory
81
+ * with one that always returns the same instance, and registers the result
82
+ * under the module's own name.
83
+ *
84
+ * An application module supplied through {@link TModules} uses this the same
85
+ * way `.http` and `.msal` do, to expose its own named accessor:
86
+ *
87
+ * ```typescript
88
+ * class MyAppMockConfigurator extends AppMockConfigurator<[WidgetsModule]> {
89
+ * constructor(env: AppEnv) {
90
+ * super(env);
91
+ * this._pin(widgetsMockModule);
92
+ * }
93
+ *
94
+ * public get widgets(): WidgetsMockConfigurator {
95
+ * return this._getConfig('widgets');
96
+ * }
97
+ * }
98
+ * ```
99
+ *
100
+ * @param module - The module descriptor to pin a configurator for.
101
+ * @template TModule - The specific module descriptor type being pinned.
102
+ * @throws {Error} If the module declares no `configure` factory to pin, or
103
+ * the factory returns a promise instead of a configurator — pinning is
104
+ * synchronous, so a test can reach the accessor immediately.
105
+ */
106
+ protected _pin<TModule extends AnyModule>(module: TModule): void;
107
+ /**
108
+ * Returns the configurator pinned for a module by name.
109
+ *
110
+ * @param name - The module's name, as passed to {@link _pin}.
111
+ * @template TConfig - The specific configurator type expected for this module.
112
+ * @returns The configurator pinned under `name`.
113
+ * @throws {Error} If no configurator has been pinned for that name.
114
+ */
115
+ protected _getConfig<TConfig>(name: string): TConfig;
116
+ /**
117
+ * Configures the app's named HTTP clients.
118
+ *
119
+ * @remarks
120
+ * The same {@link IHttpClientConfigurator} the `http` module is configured
121
+ * from — the real one, not a test double. Every client it builds —
122
+ * including ones registered through
123
+ * {@link AppConfigurator.configureHttpClient} or
124
+ * {@link AppConfigurator.useFrameworkServiceClient} — is reachable here to
125
+ * register a short-circuiting {@link HttpMiddleware} through
126
+ * `addMiddleware`, so it answers from that instead of the network.
127
+ *
128
+ * @returns The real HTTP configurator.
129
+ */
130
+ get http(): IHttpClientConfigurator;
131
+ /**
132
+ * Configures the user the app's `msal` module signs in.
133
+ *
134
+ * @remarks
135
+ * The same {@link MsalMockConfigurator} the `msal` module is configured from,
136
+ * so a change made here is what the module sees.
137
+ *
138
+ * @returns The MSAL mock configurator.
139
+ */
140
+ get msal(): MsalMockConfigurator;
141
+ }
142
+ export default AppMockConfigurator;
@@ -0,0 +1,33 @@
1
+ import type { FrameworkMockConfigurator } from '@equinor/fusion-framework/mock';
2
+ import { type AppModule } from '@equinor/fusion-framework-module-app';
3
+ import type { AppEnv } from '../types.js';
4
+ /**
5
+ * Enables the `app` module on a parent framework configurator, wrapping its
6
+ * client in a {@link MockAppClient} so this app's manifest and config resolve
7
+ * locally.
8
+ *
9
+ * @remarks
10
+ * The underlying http client is still resolved the same way `AppConfigurator`
11
+ * resolves it (a pre-configured client, falling back to service discovery), so
12
+ * a caller pointing `serviceDiscovery` at something else — e.g. a real local
13
+ * mock server — is honored for every request `MockAppClient` doesn't answer
14
+ * itself. {@link mockAppModules} uses this to wire its zero-config default
15
+ * parent; call it directly when building a custom parent (via
16
+ * {@link mockFramework}) that also needs this app's own manifest servable.
17
+ *
18
+ * @param configurator - The parent framework's mock configurator, with `app` in its module set.
19
+ * @param env - The application environment whose manifest (and optional config) should be served.
20
+ * @param assetUri - Overrides the base URI a loaded app's script is imported from.
21
+ * @template TEnv - The application environment descriptor.
22
+ *
23
+ * @example Custom service discovery, same manifest resolution
24
+ * ```typescript
25
+ * const fusion = await mockFramework<[AppModule]>((configurator) => {
26
+ * configurator.serviceDiscovery.setBaseUri('http://localhost:9999');
27
+ * enableAppManifestMock(configurator, env);
28
+ * });
29
+ * const modules = await mockAppModules(undefined, env, fusion);
30
+ * ```
31
+ */
32
+ export declare function enableAppManifestMock<TEnv extends AppEnv>(configurator: FrameworkMockConfigurator<[AppModule]>, env: TEnv, assetUri?: string): void;
33
+ export default enableAppManifestMock;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Zero-configuration application module pipelines for tests.
3
+ *
4
+ * @remarks
5
+ * Lets a test initialize an application's real module pipeline — real
6
+ * `event`/`http`/`msal` modules, real configuration pipeline, real lifecycle —
7
+ * while the boundaries that reach outside the process are substituted with the
8
+ * same deterministic fakes {@link https://www.npmjs.com/package/@equinor/fusion-framework | @equinor/fusion-framework}'s
9
+ * own `/mock` entry point uses. No credentials, no network access and no
10
+ * running parent portal are required.
11
+ *
12
+ * This entry point is test-runner agnostic: it contains no dependency on
13
+ * Vitest or any other test framework, so the same helpers work under any
14
+ * runner.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ export { mockAppModules, type AppMockConfigureFn } from './mock-app-modules.js';
19
+ export { AppMockConfigurator } from './AppMockConfigurator.js';
20
+ export { enableAppManifestMock } from './enable-app-manifest-mock.js';
@@ -0,0 +1,72 @@
1
+ import type { Fusion } from '@equinor/fusion-framework';
2
+ import type { AnyModule } from '@equinor/fusion-framework-module';
3
+ import type { AppEnv, AppModulesInstance } from '../types.js';
4
+ import { AppMockConfigurator } from './AppMockConfigurator.js';
5
+ /**
6
+ * Configuration callback for {@link mockAppModules}.
7
+ */
8
+ export type AppMockConfigureFn<TModules extends Array<AnyModule> | unknown = unknown, TEnv extends AppEnv = AppEnv> = (configurator: AppMockConfigurator<TModules, Fusion['modules'], TEnv>, args: {
9
+ fusion: Fusion;
10
+ env: TEnv;
11
+ }) => void | Promise<void>;
12
+ /**
13
+ * Runs an application's module pipeline with no real credentials required,
14
+ * from either the parent framework or the app's own `http`/`msal`
15
+ * registrations.
16
+ *
17
+ * @remarks
18
+ * The real `AppConfigurator` pipeline is run — the real module set, the real
19
+ * configuration pipeline and the real lifecycle. Only the boundaries that
20
+ * would need credentials are substituted by default; the `http` module is
21
+ * the real `HttpClientConfigurator`, so a registered client only avoids the
22
+ * network for requests a middleware short-circuits with a response — a
23
+ * client with no matching middleware, or a middleware that calls `next`,
24
+ * still reaches the real network. A test exercises the wiring an
25
+ * application actually depends on rather than a reimplementation of it.
26
+ *
27
+ * The configurator passed to `cb` is an {@link AppMockConfigurator}, which *is*
28
+ * an `AppConfigurator`. `useFrameworkServiceClient`, `configureHttpClient` and
29
+ * any callback written for a real app work against it unchanged.
30
+ *
31
+ * `fusion` defaults to a fresh {@link mockFramework} instance with a real `app`
32
+ * module already enabled and this app's own manifest served at whatever URI
33
+ * service discovery resolves `'apps'` to, so a test needs no parent Fusion
34
+ * instance of its own — but an already-mocked (or real) instance can be passed
35
+ * to compose with other framework-level setup. To point the parent's service
36
+ * discovery at something else (e.g. a real local mock server) while keeping
37
+ * the manifest served consistently, build that `fusion` with
38
+ * {@link mockFramework} and call {@link enableAppManifestMock} yourself,
39
+ * after customizing `serviceDiscovery`.
40
+ *
41
+ * @template TModules - Module descriptors beyond the default set. Supply this
42
+ * when a test registers application modules, so they are typed on the result.
43
+ * @template TEnv - The application environment descriptor.
44
+ * @param cb - Configuration callback invoked before module initialization, or `undefined` to skip it.
45
+ * @param env - The application environment (manifest, config, basename).
46
+ * @param fusion - The parent Fusion instance; defaults to a fresh {@link mockFramework} instance.
47
+ * @returns The initialized application module instance.
48
+ *
49
+ * @example Zero configuration
50
+ * ```typescript
51
+ * const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
52
+ * const modules = await mockAppModules(undefined, { manifest });
53
+ * ```
54
+ *
55
+ * @example Register a client answered by the app's own mocked HTTP module
56
+ * ```typescript
57
+ * const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
58
+ * const modules = await mockAppModules(
59
+ * (configurator) => {
60
+ * configurator.useFrameworkServiceClient('portal-api');
61
+ * configurator.http.addMiddleware(async (uri, init, next) =>
62
+ * uri === 'https://portal-api.fusion.test/items' ? Response.json([{ id: 1 }]) : next(uri, init),
63
+ * );
64
+ * },
65
+ * { manifest },
66
+ * );
67
+ *
68
+ * const items = await modules.http.createClient('portal-api').json('/items');
69
+ * ```
70
+ */
71
+ export declare function mockAppModules<TModules extends Array<AnyModule> | unknown = unknown, TEnv extends AppEnv = AppEnv>(cb: AppMockConfigureFn<TModules, TEnv> | undefined, env: TEnv, fusion?: Fusion): Promise<AppModulesInstance<TModules>>;
72
+ export default mockAppModules;
@@ -1 +1 @@
1
- export declare const version = "13.0.1";
1
+ export declare const version = "14.0.0-next.0";
@@ -0,0 +1,18 @@
1
+ # Enable Bookmarks
2
+
3
+ The bookmark module allows applications to save and restore application state.
4
+
5
+ > **Important:** Import `enableBookmark` from the app-level package, not from
6
+ > `@equinor/fusion-framework-module-bookmark` directly.
7
+
8
+ ```ts
9
+ import { configureModules } from '@equinor/fusion-framework-app';
10
+ import { enableBookmark } from '@equinor/fusion-framework-app/enable-bookmark';
11
+
12
+ const initialize = configureModules((configurator) => {
13
+ enableBookmark(configurator);
14
+ });
15
+ ```
16
+
17
+ Payload generators registered through the bookmark module are automatically
18
+ cleaned up when the module is disposed.
@@ -0,0 +1,71 @@
1
+ # Configure HTTP Clients
2
+
3
+ The `AppConfigurator` can register named HTTP clients from several sources.
4
+ You retrieve a client at runtime with `framework.modules.http.createClient(name)`.
5
+
6
+ ## From Application Config (auto-registration)
7
+
8
+ Endpoints defined in `app.config.<env>.ts` are **automatically registered as
9
+ named HTTP clients** when the `AppConfigurator` is created — no extra code
10
+ needed in `config.ts`.
11
+
12
+ ```ts
13
+ // app.config.ts
14
+ import { defineAppConfig } from '@equinor/fusion-framework-cli/app';
15
+
16
+ export default defineAppConfig(() => ({
17
+ endpoints: {
18
+ schedule: {
19
+ url: 'https://schedule-api.example.com',
20
+ scopes: ['api://schedule-id/.default'],
21
+ },
22
+ },
23
+ }));
24
+ ```
25
+
26
+ After initialization, use the client directly:
27
+
28
+ ```ts
29
+ const client = framework.modules.http.createClient('schedule');
30
+ const data = await client.json('/items');
31
+ ```
32
+
33
+ ## Via Service Discovery
34
+
35
+ ```ts
36
+ const initialize = configureModules((configurator) => {
37
+ configurator.useFrameworkServiceClient('people');
38
+ });
39
+ ```
40
+
41
+ ## Explicit Registration
42
+
43
+ Use `configureHttpClient` in `config.ts` when the endpoint is **not** in
44
+ `app.config.ts`, or when you need custom transport behavior such as headers,
45
+ response guards, or a custom client class.
46
+
47
+ ```ts
48
+ configurator.configureHttpClient('custom-api', {
49
+ baseUri: 'https://custom.api.example.com',
50
+ defaultScopes: ['api://custom-id/.default'],
51
+ onCreate: (client) => {
52
+ client.requestHandler.setHeader('X-Source', 'portal');
53
+ },
54
+ });
55
+ ```
56
+
57
+ ## Resolution Priority
58
+
59
+ When the same client name is configured in more than one place, the
60
+ highest-priority source wins:
61
+
62
+ | Priority | Source | Example |
63
+ |----------|--------|---------|
64
+ | 1 (highest) | **Session overrides** | User-specific URL / scopes set at runtime via `sessionStorage` |
65
+ | 2 | **Application config endpoints** | `endpoints` in `app.config.ts` |
66
+ | 3 | **Service-discovery registry** | Resolved via `useFrameworkServiceClient` |
67
+ | 4 (lowest) | **Explicit registration** | `configureHttpClient(name, options)` in `config.ts` |
68
+
69
+ This means an endpoint defined in `app.config.ts` will override a
70
+ `configureHttpClient` call for the same name, and a session override will
71
+ override both.
@@ -0,0 +1,105 @@
1
+ # Testing
2
+
3
+ `@equinor/fusion-framework-app/mock` runs an application's real module pipeline in
4
+ tests — the real `event`/`http`/`msal` modules, the real `AppConfigurator`
5
+ configuration pipeline, and real lifecycle — while only the boundaries that reach
6
+ outside the process (network access, credentials, a running parent portal) are
7
+ substituted with deterministic fakes. This entry point has no dependency on
8
+ Vitest or any other test runner.
9
+
10
+ ```ts
11
+ import { mockAppModules } from '@equinor/fusion-framework-app/mock';
12
+
13
+ const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
14
+ const modules = await mockAppModules(undefined, { manifest });
15
+ ```
16
+
17
+ ## `mockAppModules(cb, env, fusion?)`
18
+
19
+ Runs the same pipeline `configureModules` produces, against a mocked parent.
20
+ `cb` receives an `AppMockConfigurator` — which *is* an `AppConfigurator`, so
21
+ `useFrameworkServiceClient`, `configureHttpClient`, and any callback written for
22
+ a real app work against it unchanged.
23
+
24
+ - `cb` — configuration callback, or `undefined` to skip it.
25
+ - `env` — the application environment (`manifest`, `config`, `basename`).
26
+ - `fusion` — optional parent Fusion instance. Defaults to a fresh `mockFramework`
27
+ instance with `app` already enabled and this app's own manifest and config
28
+ served (see `enableAppManifestMock` below).
29
+
30
+ ```ts
31
+ const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
32
+
33
+ const modules = await mockAppModules(
34
+ (configurator) => {
35
+ configurator.useFrameworkServiceClient('portal-api');
36
+ configurator.http.addMiddleware(async (uri, init, next) =>
37
+ uri === 'https://portal-api.fusion.test/items' ? Response.json([{ id: 1 }]) : next(uri, init),
38
+ );
39
+ },
40
+ { manifest },
41
+ );
42
+
43
+ const items = await modules.http.createClient('portal-api').json('/items');
44
+ ```
45
+
46
+ The default parent's own `app` module (not `mockAppModules`'s returned `modules`)
47
+ only resolves `env.manifest`/`env.config` locally for `env.manifest.appKey` —
48
+ setting the current app to any other key falls through to whatever the parent's
49
+ real `app` module would do (a real service-discovery-resolved request, or
50
+ nothing if `serviceDiscovery` was never pointed anywhere):
51
+
52
+ ```ts
53
+ import type { Fusion } from '@equinor/fusion-framework';
54
+ import type { AppModule } from '@equinor/fusion-framework-module-app';
55
+
56
+ await mockAppModules(async (_configurator, { fusion }) => {
57
+ // the default parent always has `app` enabled; cast narrows the module set
58
+ // for callers that pass in a parent without it
59
+ const { app } = (fusion as Fusion<[AppModule]>).modules;
60
+
61
+ app.setCurrentApp(env.manifest.appKey);
62
+ await app.current?.getManifestAsync(); // resolves with env.manifest
63
+
64
+ app.setCurrentApp('some-other-app');
65
+ await app.current?.getManifestAsync(); // rejects — not this app's own manifest
66
+ }, env);
67
+ ```
68
+
69
+ ## `enableAppManifestMock(configurator, env)`
70
+
71
+ Registers the `app` module on a parent `mockFramework` configurator, serving
72
+ `env.manifest` and `env.config` for this app's own `appKey` while delegating
73
+ every other request — other app keys, tagged requests, builds, settings — to
74
+ whatever client service discovery (or a pre-configured http client) would really
75
+ resolve. `mockAppModules` uses this to build its zero-config default parent;
76
+ call it directly when a test needs to customize `serviceDiscovery` (e.g. point
77
+ it at a real local mock server) while keeping this app's own manifest and
78
+ config servable.
79
+
80
+ ```ts
81
+ import { mockFramework } from '@equinor/fusion-framework/mock';
82
+ import type { AppModule } from '@equinor/fusion-framework-module-app';
83
+ import { enableAppManifestMock, mockAppModules } from '@equinor/fusion-framework-app/mock';
84
+
85
+ const env = { manifest: { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const };
86
+
87
+ const fusion = await mockFramework<[AppModule]>((configurator) => {
88
+ configurator.serviceDiscovery.setBaseUri('http://localhost:9999');
89
+ enableAppManifestMock(configurator, env);
90
+ });
91
+
92
+ const modules = await mockAppModules(undefined, env, fusion);
93
+ ```
94
+
95
+ ## `AppMockConfigurator`
96
+
97
+ The configurator type passed to `mockAppModules`'s `cb`. It extends the real
98
+ `AppConfigurator`, so any configuration code written against a real app — named
99
+ HTTP clients, service-discovery clients, bookmark setup — works unchanged
100
+ against it in a test.
101
+
102
+ ## Related
103
+
104
+ - [`@equinor/fusion-framework/mock`](../../framework/docs/testing.md) — mock every framework boundary at once, for building a custom parent `fusion` instance.
105
+ - [`@equinor/fusion-framework-module-app/mock`](../../modules/app/README.md) — the lower-level `MockAppClient` this package's mock wiring is built on.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-app",
3
- "version": "13.0.1",
3
+ "version": "14.0.0-next.0",
4
4
  "description": "",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -16,6 +16,10 @@
16
16
  "./enable-bookmark": {
17
17
  "types": "./dist/types/enable-bookmark.d.ts",
18
18
  "import": "./dist/esm/enable-bookmark.js"
19
+ },
20
+ "./mock": {
21
+ "types": "./dist/types/mock/index.d.ts",
22
+ "import": "./dist/esm/mock/index.js"
19
23
  }
20
24
  },
21
25
  "typesVersions": {
@@ -28,6 +32,9 @@
28
32
  ],
29
33
  "enable-bookmark": [
30
34
  "dist/types/enable-bookmark.d.ts"
35
+ ],
36
+ "mock": [
37
+ "dist/types/mock/index.d.ts"
31
38
  ]
32
39
  }
33
40
  },
@@ -54,23 +61,24 @@
54
61
  "dependencies": {
55
62
  "lodash.clonedeep": "^4.5.0",
56
63
  "rxjs": "^7.8.1",
57
- "@equinor/fusion-framework": "8.0.15",
58
- "@equinor/fusion-framework-module-app": "8.0.4",
59
- "@equinor/fusion-framework-module": "6.1.2",
60
- "@equinor/fusion-framework-module-http": "8.0.5",
61
- "@equinor/fusion-framework-module-event": "6.0.1",
62
- "@equinor/fusion-framework-module-msal": "10.0.2",
63
- "@equinor/fusion-framework-module-telemetry": "7.0.2"
64
+ "@equinor/fusion-framework": "8.1.0-next.0",
65
+ "@equinor/fusion-framework-module-app": "8.1.0-next.0",
66
+ "@equinor/fusion-framework-module-event": "6.1.0-next.0",
67
+ "@equinor/fusion-framework-module-http": "8.1.0-next.0",
68
+ "@equinor/fusion-framework-module-msal": "11.0.0-next.0",
69
+ "@equinor/fusion-framework-module": "6.1.3-next.0",
70
+ "@equinor/fusion-framework-module-telemetry": "8.0.0-next.0"
64
71
  },
65
72
  "devDependencies": {
66
73
  "@types/lodash.clonedeep": "^4.5.9",
67
74
  "typescript": "^7.0.2",
68
- "@equinor/fusion-framework-module-bookmark": "^4.0.4",
69
- "@equinor/fusion-framework-module-state": "^2.0.0"
75
+ "vitest": "^4.1.10",
76
+ "@equinor/fusion-framework-module-bookmark": "^4.1.0-next.0",
77
+ "@equinor/fusion-framework-module-state": "^2.0.1-next.0"
70
78
  },
71
79
  "peerDependencies": {
72
- "@equinor/fusion-framework-module-bookmark": "^4.0.4",
73
- "@equinor/fusion-framework-module-state": "^2.0.0"
80
+ "@equinor/fusion-framework-module-bookmark": "^4.1.0-next.0",
81
+ "@equinor/fusion-framework-module-state": "^2.0.1-next.0"
74
82
  },
75
83
  "peerDependenciesMeta": {
76
84
  "@equinor/fusion-framework-module-bookmark": {
@@ -81,6 +89,7 @@
81
89
  }
82
90
  },
83
91
  "scripts": {
84
- "build": "tsc -b"
92
+ "build": "tsc -b",
93
+ "test": "vitest"
85
94
  }
86
95
  }