@equinor/fusion-framework 8.1.0 → 8.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 +12 -9
- package/CHANGELOG.md +0 -1961
- package/docs/testing-api.md +0 -25
- package/docs/testing-choosing-a-layer.md +0 -90
- package/docs/testing-design.md +0 -63
- package/docs/testing-extending.md +0 -102
- package/docs/testing.md +0 -178
- package/src/FrameworkConfigurator.ts +0 -273
- package/src/__tests__/FrameworkConfigurator.test.ts +0 -35
- package/src/__tests__/mock/application-module.test.ts +0 -87
- package/src/__tests__/mock/documented-usage.test.ts +0 -98
- package/src/__tests__/mock/mock-framework.test.ts +0 -278
- package/src/index.ts +0 -44
- package/src/init.ts +0 -61
- package/src/mock/FrameworkMockConfigurator.ts +0 -270
- package/src/mock/index.ts +0 -62
- package/src/mock/mock-framework.ts +0 -72
- package/src/types.ts +0 -82
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -36
- package/vitest.config.ts +0 -10
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { Module } from '@equinor/fusion-framework-module';
|
|
4
|
-
import { enableMsalMock } from '@equinor/fusion-framework-module-msal/mock';
|
|
5
|
-
import { enableServiceDiscoveryMock } from '@equinor/fusion-framework-module-service-discovery/mock';
|
|
6
|
-
import { TelemetryLevel } from '@equinor/fusion-framework-module-telemetry';
|
|
7
|
-
|
|
8
|
-
import { FrameworkConfigurator } from '../../FrameworkConfigurator.js';
|
|
9
|
-
import { init } from '../../init.js';
|
|
10
|
-
import { FrameworkMockConfigurator, mockFramework } from '../../mock/index.js';
|
|
11
|
-
|
|
12
|
-
/** A minimal configurator, standing in for an application's own. */
|
|
13
|
-
class WidgetsConfigurator {
|
|
14
|
-
#name = 'default';
|
|
15
|
-
setName(name: string): this {
|
|
16
|
-
this.#name = name;
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
get name(): string {
|
|
20
|
-
return this.#name;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** A minimal module, standing in for one an application supplies through `TModules`. */
|
|
25
|
-
type WidgetsModule = Module<'widgets', { name: string }, WidgetsConfigurator>;
|
|
26
|
-
const widgetsModule: WidgetsModule = {
|
|
27
|
-
name: 'widgets',
|
|
28
|
-
configure: () => new WidgetsConfigurator(),
|
|
29
|
-
initialize: ({ config }) => ({ name: config.name }),
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
describe('mockFramework', () => {
|
|
33
|
-
it('initializes every built-in module without configuration', async () => {
|
|
34
|
-
const fusion = await mockFramework();
|
|
35
|
-
|
|
36
|
-
expect(fusion.modules.event).toBeDefined();
|
|
37
|
-
expect(fusion.modules.auth).toBeDefined();
|
|
38
|
-
expect(fusion.modules.http).toBeDefined();
|
|
39
|
-
expect(fusion.modules.serviceDiscovery).toBeDefined();
|
|
40
|
-
expect(fusion.modules.context).toBeDefined();
|
|
41
|
-
expect(fusion.modules.telemetry).toBeDefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('signs in a default user so an application has an identity to read', async () => {
|
|
45
|
-
const fusion = await mockFramework();
|
|
46
|
-
|
|
47
|
-
expect(fusion.modules.auth.account?.name).toBe('Test User');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('resolves the services a Fusion application needs at start-up', async () => {
|
|
51
|
-
const fusion = await mockFramework();
|
|
52
|
-
|
|
53
|
-
await expect(fusion.modules.serviceDiscovery.resolveService('apps')).resolves.toMatchObject({
|
|
54
|
-
key: 'apps',
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('passes a real FrameworkConfigurator to the callback', async () => {
|
|
59
|
-
expect.assertions(2);
|
|
60
|
-
|
|
61
|
-
await mockFramework((configurator) => {
|
|
62
|
-
expect(configurator).toBeInstanceOf(FrameworkMockConfigurator);
|
|
63
|
-
expect(configurator).toBeInstanceOf(FrameworkConfigurator);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('awaits an asynchronous callback before initializing', async () => {
|
|
68
|
-
const fusion = await mockFramework(async (configurator) => {
|
|
69
|
-
await Promise.resolve();
|
|
70
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
expect(fusion.modules.auth.account?.name).toBe('Ada Lovelace');
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('FrameworkMockConfigurator', () => {
|
|
78
|
-
it('exposes the same msal configurator the auth module is built from', async () => {
|
|
79
|
-
const fusion = await mockFramework((configurator) => {
|
|
80
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace', username: 'ada@equinor.com' });
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(fusion.modules.auth.account).toMatchObject({
|
|
84
|
-
name: 'Ada Lovelace',
|
|
85
|
-
username: 'ada@equinor.com',
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('lets the last declared account win', async () => {
|
|
90
|
-
const fusion = await mockFramework((configurator) => {
|
|
91
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
92
|
-
configurator.msal.setAccount({ name: 'Grace Hopper' });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
expect(fusion.modules.auth.account?.name).toBe('Grace Hopper');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('resolves an account callback when the config is built', async () => {
|
|
99
|
-
const fusion = await mockFramework((configurator) => {
|
|
100
|
-
configurator.msal.setAccount(async () => ({ name: 'Ada Lovelace' }));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
expect(fusion.modules.auth.account?.name).toBe('Ada Lovelace');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('builds the auth client when the module builds its config, not when the account is set', async () => {
|
|
107
|
-
const configurator = new FrameworkMockConfigurator();
|
|
108
|
-
|
|
109
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
110
|
-
|
|
111
|
-
// The account is configuration; nothing is constructed from it yet
|
|
112
|
-
expect(configurator.msal.getClient()).toBeUndefined();
|
|
113
|
-
|
|
114
|
-
const fusion = await init(configurator);
|
|
115
|
-
|
|
116
|
-
expect(fusion.modules.auth.account?.name).toBe('Ada Lovelace');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('exposes the same service discovery configurator the module is built from', async () => {
|
|
120
|
-
const fusion = await mockFramework((configurator) => {
|
|
121
|
-
configurator.serviceDiscovery.setBaseUri('http://localhost:6669');
|
|
122
|
-
configurator.serviceDiscovery.addService({ key: 'my-api' });
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const service = await fusion.modules.serviceDiscovery.resolveService('my-api');
|
|
126
|
-
|
|
127
|
-
expect(service.uri).toContain('http://localhost:6669');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('lets a service be removed so its absence can be asserted', async () => {
|
|
131
|
-
const fusion = await mockFramework((configurator) => {
|
|
132
|
-
configurator.serviceDiscovery.setResolveUnknownServices(false);
|
|
133
|
-
configurator.serviceDiscovery.removeService('bookmarks');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
await expect(fusion.modules.serviceDiscovery.resolveService('bookmarks')).rejects.toThrow();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('accepts an enableX helper directly, because it is a real configurator', async () => {
|
|
140
|
-
const fusion = await mockFramework((configurator) => {
|
|
141
|
-
enableMsalMock(configurator, (builder) => builder.setAccount({ name: 'Grace Hopper' }));
|
|
142
|
-
enableServiceDiscoveryMock(configurator, (builder) => builder.addService({ key: 'my-api' }));
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
expect(fusion.modules.auth.account?.name).toBe('Grace Hopper');
|
|
146
|
-
await expect(fusion.modules.serviceDiscovery.resolveService('my-api')).resolves.toBeDefined();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('registers a module through addModule', async () => {
|
|
150
|
-
const fusion = await mockFramework((configurator) => {
|
|
151
|
-
configurator.addModule((c) =>
|
|
152
|
-
enableMsalMock(c, (builder) => builder.setAccount({ name: 'Grace Hopper' })),
|
|
153
|
-
);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
expect(fusion.modules.auth.account?.name).toBe('Grace Hopper');
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('returns itself from addModule so calls can be chained', () => {
|
|
160
|
-
const configurator = new FrameworkMockConfigurator();
|
|
161
|
-
|
|
162
|
-
expect(configurator.addModule(() => undefined)).toBe(configurator);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('exposes the same http configurator the http module is built from', async () => {
|
|
166
|
-
const configurator = new FrameworkMockConfigurator();
|
|
167
|
-
|
|
168
|
-
configurator.http.configureClient('my-api', { baseUri: 'http://localhost:6669' });
|
|
169
|
-
|
|
170
|
-
const fusion = await init(configurator);
|
|
171
|
-
|
|
172
|
-
expect(fusion.modules.http.createClient('my-api')).toBeDefined();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('exposes the same services configurator the services module is built from', () => {
|
|
176
|
-
const configurator = new FrameworkMockConfigurator();
|
|
177
|
-
|
|
178
|
-
expect(configurator.services).toBeDefined();
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('exposes the same context configurator the context module is built from', () => {
|
|
182
|
-
const configurator = new FrameworkMockConfigurator();
|
|
183
|
-
|
|
184
|
-
expect(configurator.context).toBeDefined();
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('seeds the initial context through the context module, so `.context` backs the real module', async () => {
|
|
188
|
-
const fusion = await mockFramework((configurator) => {
|
|
189
|
-
configurator.context.setCurrentContext({
|
|
190
|
-
id: 'my-ctx',
|
|
191
|
-
type: { id: 'ProjectMaster' },
|
|
192
|
-
value: {},
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
expect(fusion.modules.context.currentContext).toMatchObject({ id: 'my-ctx' });
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('exposes the same telemetry configurator the telemetry module is built from', () => {
|
|
200
|
-
const configurator = new FrameworkMockConfigurator();
|
|
201
|
-
|
|
202
|
-
expect(configurator.telemetry).toBeDefined();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('collects a tracked event through the telemetry mock adapter, reaching no real endpoint', async () => {
|
|
206
|
-
let mockConfigurator: FrameworkMockConfigurator | undefined;
|
|
207
|
-
const fusion = await mockFramework((configurator) => {
|
|
208
|
-
mockConfigurator = configurator;
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
fusion.modules.telemetry.trackEvent({
|
|
212
|
-
name: 'button-click',
|
|
213
|
-
level: TelemetryLevel.Information,
|
|
214
|
-
scope: [],
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
await vi.waitFor(() => {
|
|
218
|
-
expect(mockConfigurator?.telemetry.adapter.getItems('button-click')).toHaveLength(1);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('lets a module supplied through TModules get the same kind of accessor as msal and serviceDiscovery', async () => {
|
|
223
|
-
// Standing in for an application subclassing FrameworkMockConfigurator to
|
|
224
|
-
// expose its own module the same way the built-ins are exposed.
|
|
225
|
-
class AppMockConfigurator extends FrameworkMockConfigurator<[WidgetsModule]> {
|
|
226
|
-
constructor() {
|
|
227
|
-
super();
|
|
228
|
-
this._pin(widgetsModule);
|
|
229
|
-
}
|
|
230
|
-
get widgets(): WidgetsConfigurator {
|
|
231
|
-
return this._getConfig('widgets');
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const configurator = new AppMockConfigurator();
|
|
236
|
-
configurator.widgets.setName('Ada');
|
|
237
|
-
|
|
238
|
-
const fusion = await init(configurator);
|
|
239
|
-
|
|
240
|
-
expect(fusion.modules.widgets.name).toBe('Ada');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('pins the same instance across repeated reads, so declarations accumulate on one configurator', () => {
|
|
244
|
-
class AppMockConfigurator extends FrameworkMockConfigurator<[WidgetsModule]> {
|
|
245
|
-
constructor() {
|
|
246
|
-
super();
|
|
247
|
-
this._pin(widgetsModule);
|
|
248
|
-
}
|
|
249
|
-
get widgets(): WidgetsConfigurator {
|
|
250
|
-
return this._getConfig('widgets');
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const configurator = new AppMockConfigurator();
|
|
255
|
-
|
|
256
|
-
expect(configurator.widgets).toBe(configurator.widgets);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('throws from _getConfig when nothing was pinned for that name', () => {
|
|
260
|
-
class AppMockConfigurator extends FrameworkMockConfigurator {
|
|
261
|
-
readMissing(): unknown {
|
|
262
|
-
return this._getConfig('widgets');
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
expect(() => new AppMockConfigurator().readMissing()).toThrow(/widgets/);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('throws from _pin when the module declares no configure factory', () => {
|
|
270
|
-
class AppMockConfigurator extends FrameworkMockConfigurator {
|
|
271
|
-
pinMissingConfigure(): void {
|
|
272
|
-
this._pin({ ...widgetsModule, configure: undefined } as WidgetsModule);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
expect(() => new AppMockConfigurator().pinMissingConfigure()).toThrow(/configure factory/);
|
|
277
|
-
});
|
|
278
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Entry point for `@equinor/fusion-framework` — the core initialization
|
|
3
|
-
* package of Fusion Framework.
|
|
4
|
-
*
|
|
5
|
-
* @remarks
|
|
6
|
-
* Re-exports the {@link FrameworkConfigurator} (used to configure framework
|
|
7
|
-
* modules before initialization), the {@link init} function (used to
|
|
8
|
-
* bootstrap the framework), and all public type aliases that describe
|
|
9
|
-
* the resulting module graph.
|
|
10
|
-
*
|
|
11
|
-
* @packageDocumentation
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { FrameworkEvent, FrameworkEventInit } from '@equinor/fusion-framework-module-event';
|
|
15
|
-
import type { Fusion } from './types.js';
|
|
16
|
-
|
|
17
|
-
declare module '@equinor/fusion-framework-module-event' {
|
|
18
|
-
interface FrameworkEventMap {
|
|
19
|
-
/**
|
|
20
|
-
* Dispatched after all framework modules have been initialized and the
|
|
21
|
-
* global `window.Fusion` reference has been set.
|
|
22
|
-
*/
|
|
23
|
-
onFrameworkLoaded: FrameworkEvent<FrameworkEventInit<Fusion>>;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
declare global {
|
|
28
|
-
interface Window {
|
|
29
|
-
/** Global Fusion instance, set during {@link init}. */
|
|
30
|
-
Fusion: Fusion;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
FrameworkConfigurator,
|
|
36
|
-
/**
|
|
37
|
-
* @deprecated Use {@link FrameworkConfigurator} instead.
|
|
38
|
-
*/
|
|
39
|
-
FrameworkConfigurator as FusionConfigurator,
|
|
40
|
-
} from './FrameworkConfigurator';
|
|
41
|
-
|
|
42
|
-
export type { FusionModules, FusionModulesInstance, Fusion, FusionRenderFn } from './types';
|
|
43
|
-
|
|
44
|
-
export { default, init } from './init';
|
package/src/init.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
2
|
-
|
|
3
|
-
import type { FrameworkConfigurator } from './FrameworkConfigurator.js';
|
|
4
|
-
import type { Fusion, FusionModules } from './types.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Initialize Fusion Framework from a fully-configured
|
|
8
|
-
* {@link FrameworkConfigurator}.
|
|
9
|
-
*
|
|
10
|
-
* This is the main bootstrap entry point. It resolves all module
|
|
11
|
-
* configurations, instantiates every registered module, assigns the
|
|
12
|
-
* resulting {@link Fusion} object to `window.Fusion`, and dispatches
|
|
13
|
-
* the `onFrameworkLoaded` event.
|
|
14
|
-
*
|
|
15
|
-
* @template TModules - Additional module descriptors beyond the built-in
|
|
16
|
-
* Fusion modules.
|
|
17
|
-
* @template TRef - Reference object forwarded to modules during
|
|
18
|
-
* initialization (e.g. a parent framework instance).
|
|
19
|
-
*
|
|
20
|
-
* @param configurator - A {@link FrameworkConfigurator} that has been set up
|
|
21
|
-
* with the desired module configurations (MSAL, HTTP, service discovery,
|
|
22
|
-
* etc.).
|
|
23
|
-
* @param ref - Optional reference object passed through to each module's
|
|
24
|
-
* initializer, typically used when an application framework is initialized
|
|
25
|
-
* within an outer host framework.
|
|
26
|
-
* @returns A promise that resolves to the initialized {@link Fusion}
|
|
27
|
-
* instance containing all configured module instances.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```typescript
|
|
31
|
-
* import { FrameworkConfigurator, init } from '@equinor/fusion-framework';
|
|
32
|
-
*
|
|
33
|
-
* const configurator = new FrameworkConfigurator();
|
|
34
|
-
* configurator.configureMsal({ clientId: '…', authority: '…' });
|
|
35
|
-
*
|
|
36
|
-
* const fusion = await init(configurator);
|
|
37
|
-
* console.log(fusion.modules); // all instantiated modules
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export const init = async <TModules extends Array<AnyModule>, TRef extends object>(
|
|
41
|
-
configurator: FrameworkConfigurator<TModules>,
|
|
42
|
-
ref?: TRef,
|
|
43
|
-
): Promise<Fusion<TModules>> => {
|
|
44
|
-
const modules = await configurator.initialize<FusionModules>(ref);
|
|
45
|
-
const fusion = {
|
|
46
|
-
modules,
|
|
47
|
-
};
|
|
48
|
-
// Expose globally so portal shells and widgets can access the running instance.
|
|
49
|
-
// Guarded because the framework must also initialize where no DOM exists, such as
|
|
50
|
-
// a test runner or a server-side render.
|
|
51
|
-
if (typeof window !== 'undefined') {
|
|
52
|
-
// Global exposure predates strict typing on `Window.Fusion`; the shape is guaranteed by this function's own construction above
|
|
53
|
-
window.Fusion = fusion as unknown as Fusion;
|
|
54
|
-
}
|
|
55
|
-
modules.event.dispatchEvent('onFrameworkLoaded', { detail: fusion });
|
|
56
|
-
|
|
57
|
-
// The generic TModules type is erased on the plain object above; restore it for the return type
|
|
58
|
-
return fusion as unknown as Fusion<TModules>;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export default init;
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
contextMockModule,
|
|
5
|
-
type ContextMockConfigurator,
|
|
6
|
-
} from '@equinor/fusion-framework-module-context/mock';
|
|
7
|
-
import {
|
|
8
|
-
module as httpModule,
|
|
9
|
-
type IHttpClientConfigurator,
|
|
10
|
-
} from '@equinor/fusion-framework-module-http';
|
|
11
|
-
import {
|
|
12
|
-
msalMockModule,
|
|
13
|
-
type MsalMockConfigurator,
|
|
14
|
-
} from '@equinor/fusion-framework-module-msal/mock';
|
|
15
|
-
import {
|
|
16
|
-
serviceDiscoveryMockModule,
|
|
17
|
-
type ServiceDiscoveryMockConfigurator,
|
|
18
|
-
} from '@equinor/fusion-framework-module-service-discovery/mock';
|
|
19
|
-
import servicesModule, { type IApiConfigurator } from '@equinor/fusion-framework-module-services';
|
|
20
|
-
import {
|
|
21
|
-
telemetryMockModule,
|
|
22
|
-
type TelemetryMockConfigurator,
|
|
23
|
-
} from '@equinor/fusion-framework-module-telemetry/mock';
|
|
24
|
-
|
|
25
|
-
import { FrameworkConfigurator } from '../FrameworkConfigurator.js';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The real framework configurator, with every built-in module that reaches
|
|
29
|
-
* outside the process backed by a test double, and every other built-in
|
|
30
|
-
* module reachable the same way.
|
|
31
|
-
*
|
|
32
|
-
* @remarks
|
|
33
|
-
* Nothing else changes: the same module set, the same configuration pipeline and
|
|
34
|
-
* the same lifecycle are used. Only the boundaries that would need credentials or
|
|
35
|
-
* network access are substituted, so a test still exercises module wiring,
|
|
36
|
-
* configuration validation and lifecycle hooks.
|
|
37
|
-
*
|
|
38
|
-
* Every built-in module exposes its own configurator as a property, so a test
|
|
39
|
-
* reaches it directly instead of registering a callback to receive it. `http`
|
|
40
|
-
* is the real configurator — fake a response by registering a
|
|
41
|
-
* short-circuiting middleware through `.http.addMiddleware(...)` instead of
|
|
42
|
-
* swapping the module out; see `@equinor/fusion-framework-module-http/mock`'s
|
|
43
|
-
* `createOpenApiMockMiddleware` for faking a whole `@equinor/fusion-openapi-mock`
|
|
44
|
-
* document that way. `services` is not backed by a test double yet either, so
|
|
45
|
-
* calls through its configurator still reach the network — but
|
|
46
|
-
* the configurator itself is reachable the same way `.msal` is, since its
|
|
47
|
-
* `configure` factory takes no `ref` and so loses nothing by being pinned early.
|
|
48
|
-
*
|
|
49
|
-
* `event` is deliberately not pinned: its `configure` factory reads `ref` to
|
|
50
|
-
* wire bubbling to a parent event provider when this configurator is hoisted
|
|
51
|
-
* inside a host framework, and pinning would freeze that decision before a
|
|
52
|
-
* `ref` could ever be known.
|
|
53
|
-
*
|
|
54
|
-
* Because this *is* a `FrameworkConfigurator`, every `enableX` helper an
|
|
55
|
-
* application already uses accepts it unchanged — including the ones an
|
|
56
|
-
* application team writes for their own modules.
|
|
57
|
-
*
|
|
58
|
-
* @typeParam TModules - Module descriptors beyond the built-in set. Supply this
|
|
59
|
-
* when a test registers application modules, so they are typed on the resulting
|
|
60
|
-
* instance.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```typescript
|
|
64
|
-
* const configurator = new FrameworkMockConfigurator();
|
|
65
|
-
*
|
|
66
|
-
* configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
67
|
-
* configurator.serviceDiscovery.setBaseUri('http://localhost:6669');
|
|
68
|
-
*
|
|
69
|
-
* const fusion = await init(configurator);
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
export class FrameworkMockConfigurator<
|
|
73
|
-
TModules extends Array<AnyModule> = [],
|
|
74
|
-
> extends FrameworkConfigurator<TModules> {
|
|
75
|
-
static override readonly className: string = 'FrameworkMockConfigurator';
|
|
76
|
-
|
|
77
|
-
// Keyed by module name, so `_getConfig` can look a pinned configurator up
|
|
78
|
-
// without needing the module descriptor again.
|
|
79
|
-
#configurators = new Map<string, unknown>();
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Creates a framework configurator backed by the built-in mock modules.
|
|
83
|
-
*/
|
|
84
|
-
constructor() {
|
|
85
|
-
super();
|
|
86
|
-
|
|
87
|
-
// Pinning up front — rather than waiting for an accessor to be read — is
|
|
88
|
-
// what replaces the modules `FrameworkConfigurator`'s own constructor
|
|
89
|
-
// already registered, whether or not a test ever touches the accessor.
|
|
90
|
-
this._pin(msalMockModule);
|
|
91
|
-
this._pin(serviceDiscoveryMockModule);
|
|
92
|
-
this._pin(httpModule);
|
|
93
|
-
this._pin(servicesModule);
|
|
94
|
-
this._pin(contextMockModule);
|
|
95
|
-
this._pin(telemetryMockModule);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Pins a module to a single configurator instance for the lifetime of this
|
|
100
|
-
* configurator, so it can be reached by name through {@link _getConfig}.
|
|
101
|
-
*
|
|
102
|
-
* @remarks
|
|
103
|
-
* The module system otherwise builds a fresh configurator from its own
|
|
104
|
-
* `configure` factory during the configure phase — too late for a test to
|
|
105
|
-
* reach, and a new instance on every call besides. This replaces that
|
|
106
|
-
* factory with one that always returns the same instance, and registers the
|
|
107
|
-
* result under the module's own name.
|
|
108
|
-
*
|
|
109
|
-
* A subclass registering a module supplied through {@link TModules} uses
|
|
110
|
-
* this the same way `.msal` and `.serviceDiscovery` do, to expose its own
|
|
111
|
-
* named accessor:
|
|
112
|
-
*
|
|
113
|
-
* ```typescript
|
|
114
|
-
* class MyMockConfigurator extends FrameworkMockConfigurator<[InvoiceModule]> {
|
|
115
|
-
* constructor() {
|
|
116
|
-
* super();
|
|
117
|
-
* this._pin(invoiceMockModule);
|
|
118
|
-
* }
|
|
119
|
-
*
|
|
120
|
-
* public get invoices(): InvoiceMockConfigurator {
|
|
121
|
-
* return this._getConfig('invoices');
|
|
122
|
-
* }
|
|
123
|
-
* }
|
|
124
|
-
* ```
|
|
125
|
-
*
|
|
126
|
-
* @param module - The module descriptor to pin a configurator for.
|
|
127
|
-
* @template TModule - The specific module descriptor type being pinned.
|
|
128
|
-
* @throws {Error} If the module declares no `configure` factory to pin, or
|
|
129
|
-
* the factory returns a promise instead of a configurator — pinning is
|
|
130
|
-
* synchronous, so a test can reach the accessor immediately.
|
|
131
|
-
*/
|
|
132
|
-
protected _pin<TModule extends AnyModule>(module: TModule): void {
|
|
133
|
-
// A module without a configure factory has nothing this method could pin
|
|
134
|
-
if (!module.configure) {
|
|
135
|
-
throw new Error(`Cannot pin "${module.name}": it declares no configure factory.`);
|
|
136
|
-
}
|
|
137
|
-
const instance = module.configure();
|
|
138
|
-
// Async factories would make the pinned instance unavailable until the module system
|
|
139
|
-
// resolves it later, defeating the point of pinning it for immediate synchronous access
|
|
140
|
-
if (instance instanceof Promise) {
|
|
141
|
-
throw new Error(
|
|
142
|
-
`Cannot pin "${module.name}": its configure factory returns a promise, so it cannot be resolved synchronously.`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
this.#configurators.set(module.name, instance);
|
|
146
|
-
this.addConfig({ module: { ...module, configure: () => instance } as TModule });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Returns the configurator pinned for a module by name.
|
|
151
|
-
*
|
|
152
|
-
* @param name - The module's name, as passed to {@link _pin}.
|
|
153
|
-
* @template TConfig - The specific configurator type expected for this module.
|
|
154
|
-
* @returns The configurator pinned under `name`.
|
|
155
|
-
* @throws {Error} If no configurator has been pinned for that name.
|
|
156
|
-
*/
|
|
157
|
-
protected _getConfig<TConfig>(name: string): TConfig {
|
|
158
|
-
const config = this.#configurators.get(name);
|
|
159
|
-
// A missing entry means _pin was never called for this module name
|
|
160
|
-
if (config === undefined) {
|
|
161
|
-
throw new Error(
|
|
162
|
-
`No configurator is pinned for module "${name}" — call this._pin(module) before this._getConfig("${name}").`,
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
return config as TConfig;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Configures the user the framework signs in.
|
|
170
|
-
*
|
|
171
|
-
* @remarks
|
|
172
|
-
* The same {@link MsalMockConfigurator} the auth module is configured from, so
|
|
173
|
-
* a change made here is what the module sees.
|
|
174
|
-
*
|
|
175
|
-
* @returns The MSAL mock configurator.
|
|
176
|
-
*/
|
|
177
|
-
public get msal(): MsalMockConfigurator {
|
|
178
|
-
return this._getConfig<MsalMockConfigurator>(msalMockModule.name);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Configures the registry services are resolved from.
|
|
183
|
-
*
|
|
184
|
-
* @remarks
|
|
185
|
-
* The same {@link ServiceDiscoveryMockConfigurator} the service discovery
|
|
186
|
-
* module is configured from, so a change made here is what the module sees.
|
|
187
|
-
*
|
|
188
|
-
* @returns The service discovery mock configurator.
|
|
189
|
-
*/
|
|
190
|
-
public get serviceDiscovery(): ServiceDiscoveryMockConfigurator {
|
|
191
|
-
return this._getConfig<ServiceDiscoveryMockConfigurator>(serviceDiscoveryMockModule.name);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Configures the HTTP module's named clients.
|
|
196
|
-
*
|
|
197
|
-
* @remarks
|
|
198
|
-
* The same {@link IHttpClientConfigurator} the HTTP module is configured
|
|
199
|
-
* from — the real one, not a test double. Register a short-circuiting
|
|
200
|
-
* {@link HttpMiddleware} through `addMiddleware` to answer from that
|
|
201
|
-
* instead of the network.
|
|
202
|
-
*
|
|
203
|
-
* @returns The real HTTP configurator.
|
|
204
|
-
*/
|
|
205
|
-
public get http(): IHttpClientConfigurator {
|
|
206
|
-
return this._getConfig<IHttpClientConfigurator>(httpModule.name);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Configures the typed API clients the `services` module builds.
|
|
211
|
-
*
|
|
212
|
-
* @remarks
|
|
213
|
-
* The real configurator — `services` has no test double yet.
|
|
214
|
-
*
|
|
215
|
-
* @returns The real API configurator.
|
|
216
|
-
*/
|
|
217
|
-
public get services(): IApiConfigurator {
|
|
218
|
-
return this._getConfig<IApiConfigurator>(servicesModule.name);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Configures context resolution.
|
|
223
|
-
*
|
|
224
|
-
* @remarks
|
|
225
|
-
* The same {@link ContextMockConfigurator} the context module is configured
|
|
226
|
-
* from, so seeding an item here is what `fusion.modules.context` resolves.
|
|
227
|
-
*
|
|
228
|
-
* @returns The context mock configurator.
|
|
229
|
-
*/
|
|
230
|
-
public get context(): ContextMockConfigurator {
|
|
231
|
-
return this._getConfig<ContextMockConfigurator>(contextMockModule.name);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Configures telemetry.
|
|
236
|
-
*
|
|
237
|
-
* @remarks
|
|
238
|
-
* The same {@link TelemetryMockConfigurator} the telemetry module is
|
|
239
|
-
* configured from, so a tracked event or measurement can be read back from
|
|
240
|
-
* its adapter instead of reaching Application Insights.
|
|
241
|
-
*
|
|
242
|
-
* @returns The telemetry mock configurator.
|
|
243
|
-
*/
|
|
244
|
-
public get telemetry(): TelemetryMockConfigurator {
|
|
245
|
-
return this._getConfig<TelemetryMockConfigurator>(telemetryMockModule.name);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Registers a module through its own enabler.
|
|
250
|
-
*
|
|
251
|
-
* @remarks
|
|
252
|
-
* Sugar for calling the enabler directly — `enableMyModuleMock(configurator)`
|
|
253
|
-
* works just as well, because this class *is* a `FrameworkConfigurator`. Use
|
|
254
|
-
* whichever reads better at the call site.
|
|
255
|
-
*
|
|
256
|
-
* @param configure - Callback receiving this configurator.
|
|
257
|
-
* @returns This configurator, for chaining.
|
|
258
|
-
*
|
|
259
|
-
* @example
|
|
260
|
-
* ```typescript
|
|
261
|
-
* configurator.addModule((c) => enableMyModuleMock(c, { total: 42 }));
|
|
262
|
-
* ```
|
|
263
|
-
*/
|
|
264
|
-
public addModule(configure: (configurator: this) => void): this {
|
|
265
|
-
configure(this);
|
|
266
|
-
return this;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export default FrameworkMockConfigurator;
|