@equinor/fusion-framework-app 13.1.0 → 13.1.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/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +15 -12
- package/CHANGELOG.md +0 -2034
- package/docs/bookmarks.md +0 -18
- package/docs/http-clients.md +0 -121
- package/docs/testing.md +0 -105
- package/src/AppConfigurator.ts +0 -280
- package/src/AppConfiguratorError.ts +0 -34
- package/src/AppModulesConfiguredEvent.ts +0 -43
- package/src/AppModulesInitializedEvent.ts +0 -40
- package/src/__tests__/AppConfigurator.test.ts +0 -49
- package/src/__tests__/mock/AppMockConfigurator.test.ts +0 -96
- package/src/__tests__/mock/mock-app.test.ts +0 -112
- package/src/__tests__/mock/msal-hoisting.test.ts +0 -54
- package/src/configure-modules.ts +0 -71
- package/src/enable-bookmark.ts +0 -95
- package/src/enable-state.ts +0 -49
- package/src/index.ts +0 -34
- package/src/initialize-app-modules.ts +0 -98
- package/src/mock/AppMockConfigurator.ts +0 -218
- package/src/mock/enable-app-manifest-mock.ts +0 -62
- package/src/mock/index.ts +0 -21
- package/src/mock/mock-app-modules.ts +0 -109
- package/src/types.ts +0 -145
- package/src/utils.ts +0 -50
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -42
- package/vitest.config.ts +0 -10
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import type { FusionModulesInstance } from '@equinor/fusion-framework';
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
AnyModule,
|
|
5
|
-
IModuleConfigurator,
|
|
6
|
-
ModuleConfigType,
|
|
7
|
-
} from '@equinor/fusion-framework-module';
|
|
8
|
-
|
|
9
|
-
import http, { type IHttpClientConfigurator } from '@equinor/fusion-framework-module-http';
|
|
10
|
-
import {
|
|
11
|
-
msalMockModule,
|
|
12
|
-
type MsalMockConfigurator,
|
|
13
|
-
} from '@equinor/fusion-framework-module-msal/mock';
|
|
14
|
-
|
|
15
|
-
import { AppConfigurator } from '../AppConfigurator.js';
|
|
16
|
-
import type { AppEnv } from '../types.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The real `AppConfigurator`, with the `msal` module it registers backed by
|
|
20
|
-
* the same test double `FrameworkMockConfigurator` uses. `http` is the real
|
|
21
|
-
* module — fake a response by registering a short-circuiting middleware
|
|
22
|
-
* through `.http.addMiddleware(...)` instead of swapping the module out.
|
|
23
|
-
*
|
|
24
|
-
* @remarks
|
|
25
|
-
* Nothing else changes: the same module set (`event`, `http`, `msal`), the same
|
|
26
|
-
* configuration pipeline and the same lifecycle are used. `configureHttpClient`,
|
|
27
|
-
* `useFrameworkServiceClient` and any callback written for a real
|
|
28
|
-
* `AppConfigurator` work against this unchanged.
|
|
29
|
-
*
|
|
30
|
-
* `http` and `msal` are pinned early — mirroring `FrameworkMockConfigurator`,
|
|
31
|
-
* one level down — so `.http` and `.msal` are reachable synchronously, before
|
|
32
|
-
* `useFrameworkServiceClient` or a `configureModules` callback ever runs.
|
|
33
|
-
* `event` is deliberately not pinned, for the same reason it isn't in
|
|
34
|
-
* `FrameworkMockConfigurator`: its `configure` factory reads `ref` to wire
|
|
35
|
-
* bubbling to a parent event provider, and pinning would freeze that decision
|
|
36
|
-
* before a `ref` could ever be known.
|
|
37
|
-
*
|
|
38
|
-
* @typeParam TModules - Module descriptors beyond the default set. Supply this
|
|
39
|
-
* when a test registers application modules, so they are typed on the result.
|
|
40
|
-
* @typeParam TRef - The resolved Fusion modules instance used as a reference during initialization.
|
|
41
|
-
* @typeParam TEnv - The application environment descriptor.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```typescript
|
|
45
|
-
* const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
|
|
46
|
-
* const configurator = new AppMockConfigurator({ manifest });
|
|
47
|
-
*
|
|
48
|
-
* configurator.configureHttpClient('catalog', { baseUri: 'https://api.example.com' });
|
|
49
|
-
* configurator.http.addMiddleware(async (uri, init, next) =>
|
|
50
|
-
* uri === 'https://api.example.com/items' ? Response.json([{ id: 1 }]) : next(uri, init),
|
|
51
|
-
* );
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export class AppMockConfigurator<
|
|
55
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
56
|
-
TRef extends FusionModulesInstance = FusionModulesInstance,
|
|
57
|
-
TEnv extends AppEnv = AppEnv,
|
|
58
|
-
> extends AppConfigurator<TModules, TRef, TEnv> {
|
|
59
|
-
static override readonly className: string = 'AppMockConfigurator';
|
|
60
|
-
|
|
61
|
-
// Keyed by module name, so `_getConfig` can look a pinned configurator up
|
|
62
|
-
// without needing the module descriptor again.
|
|
63
|
-
#configurators = new Map<string, unknown>();
|
|
64
|
-
|
|
65
|
-
// Keyed by module name, so `addConfig` can redirect a registration at the
|
|
66
|
-
// pinned descriptor instead of the unpinned one it was given.
|
|
67
|
-
#pinnedModules = new Map<string, AnyModule>();
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Creates an app configurator backed by the built-in mock modules.
|
|
71
|
-
*
|
|
72
|
-
* @param env - The application environment containing manifest, config, and optional basename.
|
|
73
|
-
*/
|
|
74
|
-
constructor(env: TEnv) {
|
|
75
|
-
super(env);
|
|
76
|
-
|
|
77
|
-
// Pinning up front replaces the modules AppConfigurator's own constructor
|
|
78
|
-
// already registered, whether or not a test ever touches the accessor.
|
|
79
|
-
this._pin(http);
|
|
80
|
-
this._pin(msalMockModule);
|
|
81
|
-
|
|
82
|
-
// deferred from AppConfigurator's own constructor (see the override below) until
|
|
83
|
-
// after pinning, so endpoint-derived clients register against the pinned http module
|
|
84
|
-
super._configureHttpClientsFromAppConfig();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* No-ops the base constructor's own call to this, since it would otherwise run
|
|
89
|
-
* before {@link _pin} has anything to redirect `addConfig` at; this class calls
|
|
90
|
-
* {@link AppConfigurator._configureHttpClientsFromAppConfig} itself once pinned.
|
|
91
|
-
*/
|
|
92
|
-
protected override _configureHttpClientsFromAppConfig(): void {}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Registers a module configurator, redirecting registrations for a pinned module
|
|
96
|
-
* at its pinned descriptor.
|
|
97
|
-
*
|
|
98
|
-
* @remarks
|
|
99
|
-
* `configureHttpClient`, `useFrameworkServiceClient` and similar helpers always
|
|
100
|
-
* pass the real, unpinned module descriptor — the base `addConfig` replaces a
|
|
101
|
-
* module's descriptor whenever it doesn't recognize the object it's given, even
|
|
102
|
-
* under the same name, which would otherwise silently un-pin it.
|
|
103
|
-
*
|
|
104
|
-
* @param config - The module configurator descriptor to register.
|
|
105
|
-
* @template T - The module type being configured.
|
|
106
|
-
* @template TConfig - The resolved configuration type for the module.
|
|
107
|
-
*/
|
|
108
|
-
public override addConfig<T extends AnyModule, TConfig = ModuleConfigType<T>>(
|
|
109
|
-
config: IModuleConfigurator<T, TRef, TConfig>,
|
|
110
|
-
): void {
|
|
111
|
-
const pinnedModule = this.#pinnedModules.get(config.module.name) as T | undefined;
|
|
112
|
-
super.addConfig(pinnedModule ? { ...config, module: pinnedModule } : config);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Pins a module to a single configurator instance for the lifetime of this
|
|
117
|
-
* configurator, so it can be reached by name through {@link _getConfig}.
|
|
118
|
-
*
|
|
119
|
-
* @remarks
|
|
120
|
-
* The module system otherwise builds a fresh configurator from its own
|
|
121
|
-
* `configure` factory during the configure phase — too late for a test to
|
|
122
|
-
* reach, and a new instance on every call besides. This replaces that factory
|
|
123
|
-
* with one that always returns the same instance, and registers the result
|
|
124
|
-
* under the module's own name.
|
|
125
|
-
*
|
|
126
|
-
* An application module supplied through {@link TModules} uses this the same
|
|
127
|
-
* way `.http` and `.msal` do, to expose its own named accessor:
|
|
128
|
-
*
|
|
129
|
-
* ```typescript
|
|
130
|
-
* class MyAppMockConfigurator extends AppMockConfigurator<[WidgetsModule]> {
|
|
131
|
-
* constructor(env: AppEnv) {
|
|
132
|
-
* super(env);
|
|
133
|
-
* this._pin(widgetsMockModule);
|
|
134
|
-
* }
|
|
135
|
-
*
|
|
136
|
-
* public get widgets(): WidgetsMockConfigurator {
|
|
137
|
-
* return this._getConfig('widgets');
|
|
138
|
-
* }
|
|
139
|
-
* }
|
|
140
|
-
* ```
|
|
141
|
-
*
|
|
142
|
-
* @param module - The module descriptor to pin a configurator for.
|
|
143
|
-
* @template TModule - The specific module descriptor type being pinned.
|
|
144
|
-
* @throws {Error} If the module declares no `configure` factory to pin, or
|
|
145
|
-
* the factory returns a promise instead of a configurator — pinning is
|
|
146
|
-
* synchronous, so a test can reach the accessor immediately.
|
|
147
|
-
*/
|
|
148
|
-
protected _pin<TModule extends AnyModule>(module: TModule): void {
|
|
149
|
-
// A module without a configure factory has nothing this method could pin
|
|
150
|
-
if (!module.configure) {
|
|
151
|
-
throw new Error(`Cannot pin "${module.name}": it declares no configure factory.`);
|
|
152
|
-
}
|
|
153
|
-
const instance = module.configure();
|
|
154
|
-
// Async factories would make the pinned instance unavailable until the module system
|
|
155
|
-
// resolves it later, defeating the point of pinning it for immediate synchronous access
|
|
156
|
-
if (instance instanceof Promise) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
`Cannot pin "${module.name}": its configure factory returns a promise, so it cannot be resolved synchronously.`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
this.#configurators.set(module.name, instance);
|
|
162
|
-
const pinnedModule = { ...module, configure: () => instance } as TModule;
|
|
163
|
-
this.#pinnedModules.set(module.name, pinnedModule);
|
|
164
|
-
this.addConfig({ module: pinnedModule });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Returns the configurator pinned for a module by name.
|
|
169
|
-
*
|
|
170
|
-
* @param name - The module's name, as passed to {@link _pin}.
|
|
171
|
-
* @template TConfig - The specific configurator type expected for this module.
|
|
172
|
-
* @returns The configurator pinned under `name`.
|
|
173
|
-
* @throws {Error} If no configurator has been pinned for that name.
|
|
174
|
-
*/
|
|
175
|
-
protected _getConfig<TConfig>(name: string): TConfig {
|
|
176
|
-
const config = this.#configurators.get(name);
|
|
177
|
-
// A missing entry means _pin was never called for this module name
|
|
178
|
-
if (config === undefined) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`No configurator is pinned for module "${name}" — call this._pin(module) before this._getConfig("${name}").`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
return config as TConfig;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Configures the app's named HTTP clients.
|
|
188
|
-
*
|
|
189
|
-
* @remarks
|
|
190
|
-
* The same {@link IHttpClientConfigurator} the `http` module is configured
|
|
191
|
-
* from — the real one, not a test double. Every client it builds —
|
|
192
|
-
* including ones registered through
|
|
193
|
-
* {@link AppConfigurator.configureHttpClient} or
|
|
194
|
-
* {@link AppConfigurator.useFrameworkServiceClient} — is reachable here to
|
|
195
|
-
* register a short-circuiting {@link HttpMiddleware} through
|
|
196
|
-
* `addMiddleware`, so it answers from that instead of the network.
|
|
197
|
-
*
|
|
198
|
-
* @returns The real HTTP configurator.
|
|
199
|
-
*/
|
|
200
|
-
public get http(): IHttpClientConfigurator {
|
|
201
|
-
return this._getConfig<IHttpClientConfigurator>(http.name);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Configures the user the app's `msal` module signs in.
|
|
206
|
-
*
|
|
207
|
-
* @remarks
|
|
208
|
-
* The same {@link MsalMockConfigurator} the `msal` module is configured from,
|
|
209
|
-
* so a change made here is what the module sees.
|
|
210
|
-
*
|
|
211
|
-
* @returns The MSAL mock configurator.
|
|
212
|
-
*/
|
|
213
|
-
public get msal(): MsalMockConfigurator {
|
|
214
|
-
return this._getConfig<MsalMockConfigurator>(msalMockModule.name);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export default AppMockConfigurator;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import type { FrameworkMockConfigurator } from '@equinor/fusion-framework/mock';
|
|
2
|
-
import { AppConfig, enableAppModule, type AppModule } from '@equinor/fusion-framework-module-app';
|
|
3
|
-
import { MockAppClient } from '@equinor/fusion-framework-module-app/mock';
|
|
4
|
-
import type { ConfigEnvironment } from '@equinor/fusion-framework-module-app';
|
|
5
|
-
|
|
6
|
-
import type { AppEnv } from '../types.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Enables the `app` module on a parent framework configurator, wrapping its
|
|
10
|
-
* client in a {@link MockAppClient} so this app's manifest and config resolve
|
|
11
|
-
* locally.
|
|
12
|
-
*
|
|
13
|
-
* @remarks
|
|
14
|
-
* The underlying http client is still resolved the same way `AppConfigurator`
|
|
15
|
-
* resolves it (a pre-configured client, falling back to service discovery), so
|
|
16
|
-
* a caller pointing `serviceDiscovery` at something else — e.g. a real local
|
|
17
|
-
* mock server — is honored for every request `MockAppClient` doesn't answer
|
|
18
|
-
* itself. {@link mockAppModules} uses this to wire its zero-config default
|
|
19
|
-
* parent; call it directly when building a custom parent (via
|
|
20
|
-
* {@link mockFramework}) that also needs this app's own manifest servable.
|
|
21
|
-
*
|
|
22
|
-
* @param configurator - The parent framework's mock configurator, with `app` in its module set.
|
|
23
|
-
* @param env - The application environment whose manifest (and optional config) should be served.
|
|
24
|
-
* @param assetUri - Overrides the base URI a loaded app's script is imported from.
|
|
25
|
-
* @template TEnv - The application environment descriptor.
|
|
26
|
-
*
|
|
27
|
-
* @example Custom service discovery, same manifest resolution
|
|
28
|
-
* ```typescript
|
|
29
|
-
* const fusion = await mockFramework<[AppModule]>((configurator) => {
|
|
30
|
-
* configurator.serviceDiscovery.setBaseUri('http://localhost:9999');
|
|
31
|
-
* enableAppManifestMock(configurator, env);
|
|
32
|
-
* });
|
|
33
|
-
* const modules = await mockAppModules(undefined, env, fusion);
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export function enableAppManifestMock<TEnv extends AppEnv>(
|
|
37
|
-
configurator: FrameworkMockConfigurator<[AppModule]>,
|
|
38
|
-
env: TEnv,
|
|
39
|
-
assetUri?: string,
|
|
40
|
-
): void {
|
|
41
|
-
enableAppModule(configurator, (builder) => {
|
|
42
|
-
// only override the default asset base when the caller supplies one; an
|
|
43
|
-
// explicit empty string is meaningful (selects a root-relative script path)
|
|
44
|
-
if (assetUri !== undefined) {
|
|
45
|
-
builder.setAssetUri(assetUri);
|
|
46
|
-
}
|
|
47
|
-
builder.setClient(async ({ requireInstance }) => {
|
|
48
|
-
const http = await requireInstance('http');
|
|
49
|
-
const client = http.hasClient('apps')
|
|
50
|
-
? http.createClient('apps')
|
|
51
|
-
: await (await requireInstance('serviceDiscovery')).createClient('apps');
|
|
52
|
-
// fall back to a trivial config so `App.initialize()` can resolve without a caller-supplied one
|
|
53
|
-
return new MockAppClient(
|
|
54
|
-
client,
|
|
55
|
-
env.manifest,
|
|
56
|
-
env.config ?? new AppConfig<ConfigEnvironment>({ environment: {} }),
|
|
57
|
-
);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export default enableAppManifestMock;
|
package/src/mock/index.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
|
|
19
|
-
export { mockAppModules, type AppMockConfigureFn } from './mock-app-modules.js';
|
|
20
|
-
export { AppMockConfigurator } from './AppMockConfigurator.js';
|
|
21
|
-
export { enableAppManifestMock } from './enable-app-manifest-mock.js';
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import type { Fusion } from '@equinor/fusion-framework';
|
|
2
|
-
import { mockFramework } from '@equinor/fusion-framework/mock';
|
|
3
|
-
|
|
4
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
5
|
-
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
6
|
-
|
|
7
|
-
import { initializeAppModules } from '../initialize-app-modules.js';
|
|
8
|
-
import type { AppEnv, AppModulesInstance } from '../types.js';
|
|
9
|
-
|
|
10
|
-
import { AppMockConfigurator } from './AppMockConfigurator.js';
|
|
11
|
-
import { enableAppManifestMock } from './enable-app-manifest-mock.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Configuration callback for {@link mockAppModules}.
|
|
15
|
-
*/
|
|
16
|
-
export type AppMockConfigureFn<
|
|
17
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
18
|
-
TEnv extends AppEnv = AppEnv,
|
|
19
|
-
> = (
|
|
20
|
-
configurator: AppMockConfigurator<TModules, Fusion['modules'], TEnv>,
|
|
21
|
-
args: { fusion: Fusion; env: TEnv },
|
|
22
|
-
) => void | Promise<void>;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Runs an application's module pipeline with no real credentials required,
|
|
26
|
-
* from either the parent framework or the app's own `http`/`msal`
|
|
27
|
-
* registrations.
|
|
28
|
-
*
|
|
29
|
-
* @remarks
|
|
30
|
-
* The real `AppConfigurator` pipeline is run — the real module set, the real
|
|
31
|
-
* configuration pipeline and the real lifecycle. Only the boundaries that
|
|
32
|
-
* would need credentials are substituted by default; the `http` module is
|
|
33
|
-
* the real `HttpClientConfigurator`, so a registered client only avoids the
|
|
34
|
-
* network for requests a middleware short-circuits with a response — a
|
|
35
|
-
* client with no matching middleware, or a middleware that calls `next`,
|
|
36
|
-
* still reaches the real network. A test exercises the wiring an
|
|
37
|
-
* application actually depends on rather than a reimplementation of it.
|
|
38
|
-
*
|
|
39
|
-
* The configurator passed to `cb` is an {@link AppMockConfigurator}, which *is*
|
|
40
|
-
* an `AppConfigurator`. `useFrameworkServiceClient`, `configureHttpClient` and
|
|
41
|
-
* any callback written for a real app work against it unchanged.
|
|
42
|
-
*
|
|
43
|
-
* `fusion` defaults to a fresh {@link mockFramework} instance with a real `app`
|
|
44
|
-
* module already enabled and this app's own manifest served at whatever URI
|
|
45
|
-
* service discovery resolves `'apps'` to, so a test needs no parent Fusion
|
|
46
|
-
* instance of its own — but an already-mocked (or real) instance can be passed
|
|
47
|
-
* to compose with other framework-level setup. To point the parent's service
|
|
48
|
-
* discovery at something else (e.g. a real local mock server) while keeping
|
|
49
|
-
* the manifest served consistently, build that `fusion` with
|
|
50
|
-
* {@link mockFramework} and call {@link enableAppManifestMock} yourself,
|
|
51
|
-
* after customizing `serviceDiscovery`.
|
|
52
|
-
*
|
|
53
|
-
* @template TModules - Module descriptors beyond the default set. Supply this
|
|
54
|
-
* when a test registers application modules, so they are typed on the result.
|
|
55
|
-
* @template TEnv - The application environment descriptor.
|
|
56
|
-
* @param cb - Configuration callback invoked before module initialization, or `undefined` to skip it.
|
|
57
|
-
* @param env - The application environment (manifest, config, basename).
|
|
58
|
-
* @param fusion - The parent Fusion instance; defaults to a fresh {@link mockFramework} instance.
|
|
59
|
-
* @returns The initialized application module instance.
|
|
60
|
-
*
|
|
61
|
-
* @example Zero configuration
|
|
62
|
-
* ```typescript
|
|
63
|
-
* const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
|
|
64
|
-
* const modules = await mockAppModules(undefined, { manifest });
|
|
65
|
-
* ```
|
|
66
|
-
*
|
|
67
|
-
* @example Register a client answered by the app's own mocked HTTP module
|
|
68
|
-
* ```typescript
|
|
69
|
-
* const manifest = { appKey: 'my-app', displayName: 'My App', description: 'My app', type: 'standalone' } as const;
|
|
70
|
-
* const modules = await mockAppModules(
|
|
71
|
-
* (configurator) => {
|
|
72
|
-
* configurator.useFrameworkServiceClient('portal-api');
|
|
73
|
-
* configurator.http.addMiddleware(async (uri, init, next) =>
|
|
74
|
-
* uri === 'https://portal-api.fusion.test/items' ? Response.json([{ id: 1 }]) : next(uri, init),
|
|
75
|
-
* );
|
|
76
|
-
* },
|
|
77
|
-
* { manifest },
|
|
78
|
-
* );
|
|
79
|
-
*
|
|
80
|
-
* const items = await modules.http.createClient('portal-api').json('/items');
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
export async function mockAppModules<
|
|
84
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
85
|
-
TEnv extends AppEnv = AppEnv,
|
|
86
|
-
>(
|
|
87
|
-
cb: AppMockConfigureFn<TModules, TEnv> | undefined,
|
|
88
|
-
env: TEnv,
|
|
89
|
-
fusion?: Fusion,
|
|
90
|
-
): Promise<AppModulesInstance<TModules>> {
|
|
91
|
-
// `await` is illegal in a parameter default, so an omitted fusion is resolved here instead.
|
|
92
|
-
// The default parent also carries a real `app` module, serving this app's own manifest at
|
|
93
|
-
// whatever URI service discovery is currently configured to resolve `'apps'` to, so a test
|
|
94
|
-
// exercises the same portal wiring a real parent framework would provide.
|
|
95
|
-
const resolvedFusion: Fusion =
|
|
96
|
-
fusion ??
|
|
97
|
-
(await mockFramework<[AppModule]>((configurator) => enableAppManifestMock(configurator, env)));
|
|
98
|
-
const configurator = new AppMockConfigurator<TModules, Fusion['modules'], TEnv>(env);
|
|
99
|
-
// Cast is safe: `initializeAppModules` returns the exact module instance produced by
|
|
100
|
-
// `configurator`, which was constructed with this same `TModules`. TypeScript widens
|
|
101
|
-
// `TModules` to its constraint (`AnyModule[]`) when inferring through the generic
|
|
102
|
-
// `TConfigurator` parameter, so the assignment needs an explicit assertion here.
|
|
103
|
-
return initializeAppModules(configurator, cb, {
|
|
104
|
-
fusion: resolvedFusion,
|
|
105
|
-
env,
|
|
106
|
-
}) as Promise<AppModulesInstance<TModules>>;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export default mockAppModules;
|
package/src/types.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import type { Fusion } from '@equinor/fusion-framework';
|
|
2
|
-
|
|
3
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
AppConfig,
|
|
7
|
-
AppManifest,
|
|
8
|
-
AppModulesInstance,
|
|
9
|
-
ComponentRenderArgs,
|
|
10
|
-
} from '@equinor/fusion-framework-module-app';
|
|
11
|
-
|
|
12
|
-
import type { IAppConfigurator } from './AppConfigurator';
|
|
13
|
-
import type { ConfigEnvironment } from '@equinor/fusion-framework-module-app';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Re-exported application module types from `@equinor/fusion-framework-module-app`.
|
|
17
|
-
*
|
|
18
|
-
* - `AppModules` — union of default application modules
|
|
19
|
-
* - `AppManifest` — application manifest metadata (app key, version, etc.)
|
|
20
|
-
* - `AppConfig` — environment-specific application configuration
|
|
21
|
-
* - `AppModulesInstance` — resolved module instances after initialization
|
|
22
|
-
*/
|
|
23
|
-
export type {
|
|
24
|
-
AppModules,
|
|
25
|
-
AppManifest,
|
|
26
|
-
AppConfig,
|
|
27
|
-
AppModulesInstance,
|
|
28
|
-
} from '@equinor/fusion-framework-module-app';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Environment descriptor passed to the application during module initialization.
|
|
32
|
-
*
|
|
33
|
-
* Contains the application manifest, optional config (with endpoint definitions),
|
|
34
|
-
* an optional base path for routing, and optional component props.
|
|
35
|
-
*
|
|
36
|
-
* @template TConfig - Shape of the environment-specific configuration object.
|
|
37
|
-
* @template TProps - Additional properties forwarded to the application component (currently unused).
|
|
38
|
-
*/
|
|
39
|
-
export type AppEnv<TConfig extends ConfigEnvironment = ConfigEnvironment, TProps = unknown> = {
|
|
40
|
-
/** Base routing path of the application (e.g. `/apps/my-app`). */
|
|
41
|
-
basename?: string;
|
|
42
|
-
/** Application manifest describing the app key, version, and build metadata. */
|
|
43
|
-
manifest: AppManifest;
|
|
44
|
-
/** Environment-specific configuration with optional endpoint definitions. */
|
|
45
|
-
config?: AppConfig<TConfig>;
|
|
46
|
-
/** Optional properties forwarded to the application component. */
|
|
47
|
-
props?: TProps;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Configuration callback for setting up application modules.
|
|
52
|
-
*
|
|
53
|
-
* This is the function signature accepted by {@link configureModules}. Implement
|
|
54
|
-
* this callback to register HTTP clients, enable bookmarks, and add custom modules
|
|
55
|
-
* to the application’s module pipeline.
|
|
56
|
-
*
|
|
57
|
-
* @template TModules - Additional modules registered by the application.
|
|
58
|
-
* @template TRef - The Fusion instance type used as a configuration reference.
|
|
59
|
-
* @template TEnv - The application environment descriptor.
|
|
60
|
-
*
|
|
61
|
-
* @param configurator - The application configurator with HTTP and module helpers.
|
|
62
|
-
* @param args - Object containing the Fusion instance and the application environment.
|
|
63
|
-
* @returns `void` or a `Promise<void>` for async configuration steps.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```ts
|
|
67
|
-
* import type { AppModuleInitiator } from '@equinor/fusion-framework-app';
|
|
68
|
-
*
|
|
69
|
-
* const configure: AppModuleInitiator = (configurator, { fusion, env }) => {
|
|
70
|
-
* configurator.useFrameworkServiceClient('portal-api');
|
|
71
|
-
* };
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export type AppModuleInitiator<
|
|
75
|
-
TModules extends Array<AnyModule> | unknown = unknown,
|
|
76
|
-
TRef extends Fusion = Fusion,
|
|
77
|
-
TEnv = AppEnv,
|
|
78
|
-
> = (
|
|
79
|
-
configurator: IAppConfigurator<TModules, TRef['modules']>,
|
|
80
|
-
args: { fusion: TRef; env: TEnv },
|
|
81
|
-
) => void | Promise<void>;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Factory type that wraps {@link AppModuleInitiator} into a complete initializer.
|
|
85
|
-
*
|
|
86
|
-
* Accepts a configuration callback and returns an async function that, given the
|
|
87
|
-
* Fusion instance and environment, produces the initialized module instance.
|
|
88
|
-
* This is the signature of the {@link configureModules} function itself.
|
|
89
|
-
*
|
|
90
|
-
* @template TModules - Additional modules registered by the application.
|
|
91
|
-
* @template TRef - The Fusion instance type used as a configuration reference.
|
|
92
|
-
* @template TEnv - The application environment descriptor.
|
|
93
|
-
*/
|
|
94
|
-
export type AppModuleInit<
|
|
95
|
-
TModules extends Array<AnyModule> | unknown = [],
|
|
96
|
-
TRef extends Fusion = Fusion,
|
|
97
|
-
TEnv = AppEnv,
|
|
98
|
-
> = (
|
|
99
|
-
cb: AppModuleInitiator<TModules, TRef, TEnv>,
|
|
100
|
-
) => (args: AppModuleInitArgs<TRef, TEnv>) => Promise<AppModulesInstance<TModules>>;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Arguments passed to the async initializer returned by {@link configureModules}.
|
|
104
|
-
*
|
|
105
|
-
* @template TRef - The Fusion instance type.
|
|
106
|
-
* @template TEnv - The application environment descriptor.
|
|
107
|
-
*/
|
|
108
|
-
export type AppModuleInitArgs<TRef extends Fusion = Fusion, TEnv = AppEnv> = {
|
|
109
|
-
fusion: TRef;
|
|
110
|
-
env: TEnv;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Render function signature for mounting a Fusion application into the DOM.
|
|
115
|
-
*
|
|
116
|
-
* Called by the Fusion portal or dev-server to render the application. The
|
|
117
|
-
* function receives the root element and render arguments (Fusion instance,
|
|
118
|
-
* environment, and modules) and optionally returns a cleanup function that
|
|
119
|
-
* is invoked when the application is unmounted.
|
|
120
|
-
*
|
|
121
|
-
* @template TFusion - The Fusion instance type providing framework modules.
|
|
122
|
-
* @template TEnv - The application environment descriptor.
|
|
123
|
-
*
|
|
124
|
-
* @param el - The root HTML element where the application will be rendered.
|
|
125
|
-
* @param args - Render arguments including the Fusion instance, environment,
|
|
126
|
-
* and resolved modules.
|
|
127
|
-
* @returns A cleanup / teardown function, or `void` if no cleanup is needed.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* ```ts
|
|
131
|
-
* import type { AppRenderFn } from '@equinor/fusion-framework-app';
|
|
132
|
-
* import { createRoot } from 'react-dom/client';
|
|
133
|
-
*
|
|
134
|
-
* export const renderApp: AppRenderFn = (el, args) => {
|
|
135
|
-
* const root = createRoot(el);
|
|
136
|
-
* root.render(<App />);
|
|
137
|
-
* return () => root.unmount();
|
|
138
|
-
* };
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
export type AppRenderFn<TFusion extends Fusion = Fusion, TEnv = AppEnv> = (
|
|
142
|
-
el: HTMLHtmlElement,
|
|
143
|
-
args: ComponentRenderArgs<TFusion, TEnv>,
|
|
144
|
-
// biome-ignore lint/suspicious/noConfusingVoidType: `void` here relies on TypeScript's special-cased "void-returning callback accepts any return value" behavior \u2014 `undefined` would break assignability of render functions that return a cleanup function
|
|
145
|
-
) => VoidFunction | void;
|
package/src/utils.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export { default as deepClone } from 'lodash.clonedeep';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Utility type that makes all properties of an object deeply readonly.
|
|
5
|
-
*
|
|
6
|
-
* @typeParam T - The type to make deeply readonly.
|
|
7
|
-
*/
|
|
8
|
-
export type DeepImmutable<T> = {
|
|
9
|
-
readonly [P in keyof T]: T[P] extends object ? DeepImmutable<T[P]> : T[P];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Determines if the provided object is eligible to be frozen.
|
|
14
|
-
*
|
|
15
|
-
* Checks whether the input is a non-null object or array that is not already frozen.
|
|
16
|
-
*
|
|
17
|
-
* @param obj - The value to check for isMutable.
|
|
18
|
-
* @returns True if the object is a non-null object or array and is not already frozen; otherwise, false.
|
|
19
|
-
*/
|
|
20
|
-
function isMutable(obj: unknown): obj is Record<string, unknown> | Array<unknown> {
|
|
21
|
-
return typeof obj === 'object' && obj !== null && !Object.isFrozen(obj);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Recursively applies Object.freeze to an object and all nested properties, making them immutable.
|
|
26
|
-
*
|
|
27
|
-
* @remarks
|
|
28
|
-
* - Plain objects and arrays are deeply frozen.
|
|
29
|
-
* - Does not handle circular references. Use with caution on complex object graphs.
|
|
30
|
-
* - Symbol properties are not frozen.
|
|
31
|
-
*
|
|
32
|
-
* @template T - The type of the object to freeze.
|
|
33
|
-
* @param obj - The object to deeply freeze.
|
|
34
|
-
* @returns The deeply frozen (read-only) object.
|
|
35
|
-
*/
|
|
36
|
-
export function deepFreeze<T>(source: T): DeepImmutable<T> {
|
|
37
|
-
// Skip primitives and objects already frozen to avoid unnecessary recursion.
|
|
38
|
-
if (isMutable(source)) {
|
|
39
|
-
// Arrays require traversing their values directly before freezing the container.
|
|
40
|
-
if (Array.isArray(source)) {
|
|
41
|
-
// Freeze every nested array value so the result is immutable at every depth.
|
|
42
|
-
source.forEach(deepFreeze);
|
|
43
|
-
} else {
|
|
44
|
-
// Freeze every nested object value so the result is immutable at every depth.
|
|
45
|
-
Object.values(source).forEach(deepFreeze);
|
|
46
|
-
}
|
|
47
|
-
Object.freeze(source);
|
|
48
|
-
}
|
|
49
|
-
return source;
|
|
50
|
-
}
|
package/src/version.ts
DELETED
package/tsconfig.json
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "dist/esm",
|
|
5
|
-
"rootDir": "src",
|
|
6
|
-
"declarationDir": "./dist/types"
|
|
7
|
-
},
|
|
8
|
-
"references": [
|
|
9
|
-
{
|
|
10
|
-
"path": "../modules/module"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"path": "../modules/http"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"path": "../modules/msal"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"path": "../modules/event"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"path": "../modules/telemetry"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"path": "../modules/bookmark"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"path": "../modules/state"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"path": "../modules/app"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"path": "../modules/feature-flag"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"path": "../framework"
|
|
38
|
-
}
|
|
39
|
-
],
|
|
40
|
-
"include": ["src/**/*"],
|
|
41
|
-
"exclude": ["node_modules", "lib"]
|
|
42
|
-
}
|
package/vitest.config.ts
DELETED