@equinor/fusion-framework-module-app 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 -1469
- package/src/AppClient.ts +0 -409
- package/src/AppConfig.ts +0 -99
- package/src/AppConfigSelector.ts +0 -25
- package/src/AppConfigurator.ts +0 -145
- package/src/AppModuleProvider.ts +0 -283
- package/src/__tests__/AppModuleProvider.test.ts +0 -47
- package/src/__tests__/MockAppClient.test.ts +0 -97
- package/src/app/App.ts +0 -984
- package/src/app/actions.ts +0 -112
- package/src/app/create-reducer.ts +0 -62
- package/src/app/create-state.ts +0 -52
- package/src/app/events.ts +0 -108
- package/src/app/filter-empty.ts +0 -11
- package/src/app/flows/handle-fetch-config.ts +0 -48
- package/src/app/flows/handle-fetch-manifest.ts +0 -53
- package/src/app/flows/handle-fetch-settings.ts +0 -50
- package/src/app/flows/handle-import-application.ts +0 -37
- package/src/app/flows/handle-update-settings.ts +0 -39
- package/src/app/flows/index.ts +0 -5
- package/src/app/index.ts +0 -10
- package/src/app/types.ts +0 -51
- package/src/enable-app-module.ts +0 -40
- package/src/errors/AppBuildError.ts +0 -47
- package/src/errors/AppConfigError.ts +0 -47
- package/src/errors/AppManifestError.ts +0 -47
- package/src/errors/AppScriptModuleError.ts +0 -20
- package/src/errors/AppSettingsError.ts +0 -47
- package/src/errors/app-error-type.ts +0 -9
- package/src/errors.ts +0 -6
- package/src/events.ts +0 -18
- package/src/index.ts +0 -40
- package/src/mock/MockAppClient.ts +0 -77
- package/src/mock/index.ts +0 -13
- package/src/module.ts +0 -59
- package/src/schemas.ts +0 -181
- package/src/types.ts +0 -263
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -30
- package/vitest.config.ts +0 -11
package/src/AppModuleProvider.ts
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BehaviorSubject,
|
|
3
|
-
distinctUntilChanged,
|
|
4
|
-
from,
|
|
5
|
-
map,
|
|
6
|
-
type Observable,
|
|
7
|
-
pairwise,
|
|
8
|
-
Subscription,
|
|
9
|
-
takeWhile,
|
|
10
|
-
} from 'rxjs';
|
|
11
|
-
|
|
12
|
-
import type { ModuleType } from '@equinor/fusion-framework-module';
|
|
13
|
-
import type { EventModule } from '@equinor/fusion-framework-module-event';
|
|
14
|
-
|
|
15
|
-
import type {
|
|
16
|
-
AppConfig,
|
|
17
|
-
AppManifest,
|
|
18
|
-
AppReference,
|
|
19
|
-
AppSettings,
|
|
20
|
-
ConfigEnvironment,
|
|
21
|
-
CurrentApp,
|
|
22
|
-
} from './types';
|
|
23
|
-
|
|
24
|
-
import { App, filterEmpty, type IApp } from './app/App';
|
|
25
|
-
import type { AppModuleConfig } from './AppConfigurator';
|
|
26
|
-
import type { AppBundleStateInitial } from './app/types';
|
|
27
|
-
import type { IAppClient } from './AppClient';
|
|
28
|
-
import { SemanticVersion } from '@equinor/fusion-framework-module';
|
|
29
|
-
import { version } from './version';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Runtime provider for the app module.
|
|
33
|
-
*
|
|
34
|
-
* Exposes methods for fetching application manifests, configurations, and user
|
|
35
|
-
* settings, and for setting or clearing the current active application. When an
|
|
36
|
-
* {@link EventModule} is available, lifecycle events are dispatched as the
|
|
37
|
-
* current app changes.
|
|
38
|
-
*
|
|
39
|
-
* @remarks
|
|
40
|
-
* Only one application can be active (`current`) at a time. Setting a new current
|
|
41
|
-
* app automatically disposes the previous one. Subscribe to {@link current$} for
|
|
42
|
-
* reactive updates.
|
|
43
|
-
*/
|
|
44
|
-
export class AppModuleProvider {
|
|
45
|
-
/**
|
|
46
|
-
* Shallow-compares two app manifests by JSON serialization.
|
|
47
|
-
*
|
|
48
|
-
* @template T - The manifest type being compared.
|
|
49
|
-
* @param a - First manifest to compare.
|
|
50
|
-
* @param b - Second manifest to compare.
|
|
51
|
-
* @returns `true` if the serialized manifests are identical.
|
|
52
|
-
*/
|
|
53
|
-
static compareAppManifest<T extends AppManifest>(a?: T, b?: T): boolean {
|
|
54
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#appClient: IAppClient;
|
|
58
|
-
|
|
59
|
-
#appBaseUri: string;
|
|
60
|
-
|
|
61
|
-
#current$: BehaviorSubject<CurrentApp | null>;
|
|
62
|
-
|
|
63
|
-
#subscription = new Subscription();
|
|
64
|
-
|
|
65
|
-
#event?: ModuleType<EventModule>;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get module version
|
|
69
|
-
* @returns The module's semantic version.
|
|
70
|
-
*/
|
|
71
|
-
get version(): SemanticVersion {
|
|
72
|
-
return new SemanticVersion(version);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* The current active application instance.
|
|
77
|
-
*
|
|
78
|
-
* - `undefined` – no application has been set yet.
|
|
79
|
-
* - `null` – the current application was explicitly cleared.
|
|
80
|
-
* - `App` – an active application instance.
|
|
81
|
-
*
|
|
82
|
-
* @returns The current active application, `null` if cleared, or `undefined` if never set.
|
|
83
|
-
*/
|
|
84
|
-
get current(): CurrentApp | null | undefined {
|
|
85
|
-
return this.#current$.value;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Observable that emits when the current application changes.
|
|
90
|
-
*
|
|
91
|
-
* Emits are deduplicated by `appKey`; re-setting the same app does not trigger
|
|
92
|
-
* a new emission.
|
|
93
|
-
*
|
|
94
|
-
* @returns An observable of the current active application.
|
|
95
|
-
*/
|
|
96
|
-
get current$(): Observable<CurrentApp | null> {
|
|
97
|
-
// dedupe emissions when the current app's key is unchanged
|
|
98
|
-
return this.#current$.pipe(
|
|
99
|
-
distinctUntilChanged((prev, next) => {
|
|
100
|
-
// compare by appKey when both are set, otherwise fall back to reference equality
|
|
101
|
-
if (prev && next) {
|
|
102
|
-
return prev.appKey === next.appKey;
|
|
103
|
-
}
|
|
104
|
-
return prev === next;
|
|
105
|
-
}),
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Creates the app module provider.
|
|
111
|
-
*
|
|
112
|
-
* @param args - Object containing the resolved {@link AppModuleConfig} and an
|
|
113
|
-
* optional {@link EventModule} instance for dispatching lifecycle events.
|
|
114
|
-
*/
|
|
115
|
-
constructor(args: { config: AppModuleConfig; event?: ModuleType<EventModule> }) {
|
|
116
|
-
const { event, config } = args;
|
|
117
|
-
|
|
118
|
-
this.#appClient = config.client;
|
|
119
|
-
this.#event = event;
|
|
120
|
-
|
|
121
|
-
this.#current$ = new BehaviorSubject<CurrentApp>(undefined);
|
|
122
|
-
|
|
123
|
-
this.#appBaseUri = config.assetUri ?? '';
|
|
124
|
-
|
|
125
|
-
this.#subscription.add(
|
|
126
|
-
this.current$
|
|
127
|
-
// dispatch a lifecycle event whenever the current app changes
|
|
128
|
-
.pipe(
|
|
129
|
-
pairwise(),
|
|
130
|
-
takeWhile(() => !!event),
|
|
131
|
-
)
|
|
132
|
-
.subscribe(([previous, next]) => {
|
|
133
|
-
event?.dispatchEvent('onCurrentAppChanged', {
|
|
134
|
-
source: this,
|
|
135
|
-
detail: { previous, next },
|
|
136
|
-
});
|
|
137
|
-
}),
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
this.#subscription.add(
|
|
141
|
-
this.#current$
|
|
142
|
-
// dispose the previous app once it has been replaced
|
|
143
|
-
.pipe(
|
|
144
|
-
pairwise(),
|
|
145
|
-
map(([previous]) => previous),
|
|
146
|
-
filterEmpty(),
|
|
147
|
-
)
|
|
148
|
-
.subscribe((app) => app.dispose()),
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Fetches the manifest for a single application by key.
|
|
154
|
-
*
|
|
155
|
-
* @param appKey - Unique application identifier.
|
|
156
|
-
* @param tag - Optional version tag (defaults to latest).
|
|
157
|
-
* @returns An observable that emits the resolved {@link AppManifest}.
|
|
158
|
-
*/
|
|
159
|
-
public getAppManifest(appKey: string, tag?: string): Observable<AppManifest> {
|
|
160
|
-
return from(this.#appClient.getAppManifest({ appKey, tag }));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Fetches manifests for all registered applications.
|
|
165
|
-
*
|
|
166
|
-
* @param filter - Optional filter; set `filterByCurrentUser` to `true` to scope
|
|
167
|
-
* results to apps accessible by the authenticated user.
|
|
168
|
-
* @returns An observable that emits an array of {@link AppManifest} objects.
|
|
169
|
-
*/
|
|
170
|
-
public getAppManifests(filter?: { filterByCurrentUser: boolean }): Observable<AppManifest[]> {
|
|
171
|
-
return from(this.#appClient.getAppManifests(filter));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* fetch all applications
|
|
176
|
-
* @deprecated use `getAppManifests` instead
|
|
177
|
-
* @returns An observable that emits an array of {@link AppManifest} objects.
|
|
178
|
-
*/
|
|
179
|
-
public getAllAppManifests(): Observable<AppManifest[]> {
|
|
180
|
-
return this.getAppManifests();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Fetches the runtime configuration for an application.
|
|
185
|
-
*
|
|
186
|
-
* @template TType - Shape of the `environment` record in the returned config.
|
|
187
|
-
* @param appKey - Unique application identifier.
|
|
188
|
-
* @param tag - Optional version tag.
|
|
189
|
-
* @returns An observable that emits the resolved {@link AppConfig}.
|
|
190
|
-
*/
|
|
191
|
-
public getAppConfig<TType extends ConfigEnvironment = ConfigEnvironment>(
|
|
192
|
-
appKey: string,
|
|
193
|
-
tag?: string,
|
|
194
|
-
): Observable<AppConfig<TType>> {
|
|
195
|
-
return from(this.#appClient.getAppConfig<TType>({ appKey, tag }));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Fetches per-user settings for an application.
|
|
200
|
-
*
|
|
201
|
-
* @param appKey - Unique application identifier.
|
|
202
|
-
* @returns An observable that emits the {@link AppSettings} record.
|
|
203
|
-
*/
|
|
204
|
-
public getAppSettings(appKey: string): Observable<AppSettings> {
|
|
205
|
-
return from(this.#appClient.getAppSettings({ appKey }));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Persists updated per-user settings for an application.
|
|
210
|
-
*
|
|
211
|
-
* @param appKey - Unique application identifier.
|
|
212
|
-
* @param settings - The settings record to save.
|
|
213
|
-
* @returns An observable that emits the persisted {@link AppSettings}.
|
|
214
|
-
*/
|
|
215
|
-
public updateAppSettings(appKey: string, settings: AppSettings): Observable<AppSettings> {
|
|
216
|
-
return from(this.#appClient.updateAppSettings({ appKey, settings }));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Sets the current active application.
|
|
221
|
-
*
|
|
222
|
-
* Accepts an app key string, an {@link IApp} instance, or an {@link AppReference}
|
|
223
|
-
* with both `appKey` and `tag`. Setting a new app disposes the previous one.
|
|
224
|
-
*
|
|
225
|
-
* @param appKeyOrApp - Application key, app reference, or an existing `IApp` instance.
|
|
226
|
-
*/
|
|
227
|
-
public setCurrentApp(appKeyOrApp: string | IApp | AppReference): void {
|
|
228
|
-
// a plain string is treated as an appKey to create a new App from
|
|
229
|
-
if (typeof appKeyOrApp === 'string') {
|
|
230
|
-
const newApp = new App({ appKey: appKeyOrApp }, { provider: this, event: this.#event });
|
|
231
|
-
this.#current$.next(newApp as CurrentApp);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// an object with appKey and tag is treated as an AppReference to create a new App from
|
|
236
|
-
if (appKeyOrApp.appKey && 'tag' in appKeyOrApp) {
|
|
237
|
-
const newApp = new App(
|
|
238
|
-
{ appKey: appKeyOrApp.appKey, tag: appKeyOrApp.tag },
|
|
239
|
-
{ provider: this, event: this.#event },
|
|
240
|
-
);
|
|
241
|
-
this.#current$.next(newApp as CurrentApp);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
this.#current$.next(appKeyOrApp as CurrentApp);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Clears the current application, disposing its resources and emitting `null`
|
|
250
|
-
* on {@link current$}.
|
|
251
|
-
*/
|
|
252
|
-
public clearCurrentApp(): void {
|
|
253
|
-
this.#current$.next(null);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Base URI used for proxying application script imports.
|
|
258
|
-
* @returns The configured asset base URI.
|
|
259
|
-
*/
|
|
260
|
-
public get assetUri(): string {
|
|
261
|
-
return this.#appBaseUri;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* This should not be used, only for legacy creation backdoor
|
|
266
|
-
* @deprecated
|
|
267
|
-
* @param value - The initial app bundle state to construct the app from.
|
|
268
|
-
* @returns The newly created {@link App} instance.
|
|
269
|
-
*/
|
|
270
|
-
public createApp(value: AppBundleStateInitial): App {
|
|
271
|
-
console.warn('AppModuleProvider.createApp is deprecated and should not be used.');
|
|
272
|
-
return new App(value, { provider: this, event: this.#event });
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Tears down the provider, unsubscribing from all internal observables.
|
|
277
|
-
*/
|
|
278
|
-
public dispose() {
|
|
279
|
-
this.#subscription.unsubscribe();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export default AppModuleProvider;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { mockFramework } from '@equinor/fusion-framework/mock';
|
|
4
|
-
import { createRouterMiddleware } from '@equinor/fusion-framework-module-http/mock';
|
|
5
|
-
|
|
6
|
-
import { enableAppModule } from '../enable-app-module';
|
|
7
|
-
import type { AppModule } from '../module';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Boots a full portal-shaped framework instance the same way `packages/dev-portal`'s
|
|
11
|
-
* `configure.ts` does — `enableAppModule` registered directly on the portal's own
|
|
12
|
-
* configurator, alongside its real `http`, `event`, `msal`, etc. — rather than a
|
|
13
|
-
* standalone module graph assembled just for this test. Only the network call
|
|
14
|
-
* itself is faked; the app module creates its client eagerly during its own
|
|
15
|
-
* `requireInstance('http')` in the SAME configure→initialize pipeline, so this also
|
|
16
|
-
* verifies middleware registered before `initialize()` reaches that client.
|
|
17
|
-
*/
|
|
18
|
-
const initializePortalWith = () =>
|
|
19
|
-
mockFramework<[AppModule]>((configurator) => {
|
|
20
|
-
configurator.http.configureClient('apps', { baseUri: 'https://apps.example.com' });
|
|
21
|
-
configurator.http.addMiddleware(
|
|
22
|
-
createRouterMiddleware('https://apps.example.com', (router) => {
|
|
23
|
-
router.get('/persons/me/apps/:appKey', ({ params }) =>
|
|
24
|
-
Response.json({
|
|
25
|
-
appKey: params.appKey,
|
|
26
|
-
displayName: 'Test App',
|
|
27
|
-
description: 'A test application',
|
|
28
|
-
type: 'standalone',
|
|
29
|
-
}),
|
|
30
|
-
);
|
|
31
|
-
}),
|
|
32
|
-
);
|
|
33
|
-
enableAppModule(configurator);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('AppModuleProvider', () => {
|
|
37
|
-
it("fetches the manifest through the portal's own http client once a current app is set", async () => {
|
|
38
|
-
const fusion = await initializePortalWith();
|
|
39
|
-
|
|
40
|
-
fusion.modules.app.setCurrentApp('test-app');
|
|
41
|
-
|
|
42
|
-
await expect(fusion.modules.app.current?.getManifestAsync()).resolves.toMatchObject({
|
|
43
|
-
appKey: 'test-app',
|
|
44
|
-
displayName: 'Test App',
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
});
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { firstValueFrom, of } from 'rxjs';
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import type { IHttpClient } from '@equinor/fusion-framework-module-http';
|
|
5
|
-
|
|
6
|
-
import { AppClient } from '../AppClient.js';
|
|
7
|
-
import { AppConfig } from '../AppConfig.js';
|
|
8
|
-
import { MockAppClient } from '../mock/MockAppClient.js';
|
|
9
|
-
import type { AppManifest } from '../types.js';
|
|
10
|
-
|
|
11
|
-
const manifest: AppManifest = {
|
|
12
|
-
appKey: 'my-app',
|
|
13
|
-
displayName: 'My App',
|
|
14
|
-
description: 'My app',
|
|
15
|
-
type: 'standalone',
|
|
16
|
-
build: { version: '1.2.3', entryPoint: 'index.js' },
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const config = new AppConfig({ environment: { key: 'value' } });
|
|
20
|
-
|
|
21
|
-
// never invoked directly -- both overrides either answer locally or delegate through
|
|
22
|
-
// the spied `AppClient.prototype` methods below, so the real client is never called
|
|
23
|
-
const client = {} as IHttpClient;
|
|
24
|
-
|
|
25
|
-
describe('MockAppClient', () => {
|
|
26
|
-
describe('getAppManifest', () => {
|
|
27
|
-
it('answers locally for its own app key with no tag', async () => {
|
|
28
|
-
const mockClient = new MockAppClient(client, manifest);
|
|
29
|
-
|
|
30
|
-
const result = mockClient.getAppManifest({ appKey: 'my-app' });
|
|
31
|
-
|
|
32
|
-
await expect(firstValueFrom(result)).resolves.toBe(manifest);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('delegates for its own app key with an explicit empty-string tag', () => {
|
|
36
|
-
const delegate = vi
|
|
37
|
-
.spyOn(AppClient.prototype, 'getAppManifest')
|
|
38
|
-
.mockReturnValue(of(manifest));
|
|
39
|
-
const mockClient = new MockAppClient(client, manifest);
|
|
40
|
-
|
|
41
|
-
mockClient.getAppManifest({ appKey: 'my-app', tag: '' });
|
|
42
|
-
|
|
43
|
-
expect(delegate).toHaveBeenCalledWith({ appKey: 'my-app', tag: '' });
|
|
44
|
-
delegate.mockRestore();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('delegates for a different app key', () => {
|
|
48
|
-
const delegate = vi
|
|
49
|
-
.spyOn(AppClient.prototype, 'getAppManifest')
|
|
50
|
-
.mockReturnValue(of(manifest));
|
|
51
|
-
const mockClient = new MockAppClient(client, manifest);
|
|
52
|
-
|
|
53
|
-
mockClient.getAppManifest({ appKey: 'other-app' });
|
|
54
|
-
|
|
55
|
-
expect(delegate).toHaveBeenCalledWith({ appKey: 'other-app' });
|
|
56
|
-
delegate.mockRestore();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('getAppConfig', () => {
|
|
61
|
-
it('answers locally for its own app key with no tag', async () => {
|
|
62
|
-
const mockClient = new MockAppClient(client, manifest, config);
|
|
63
|
-
|
|
64
|
-
const result = mockClient.getAppConfig({ appKey: 'my-app' });
|
|
65
|
-
|
|
66
|
-
await expect(firstValueFrom(result)).resolves.toBe(config);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("answers locally for its own app key with a tag matching the manifest's build version", async () => {
|
|
70
|
-
const mockClient = new MockAppClient(client, manifest, config);
|
|
71
|
-
|
|
72
|
-
const result = mockClient.getAppConfig({ appKey: 'my-app', tag: '1.2.3' });
|
|
73
|
-
|
|
74
|
-
await expect(firstValueFrom(result)).resolves.toBe(config);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('delegates for its own app key with an explicit empty-string tag', () => {
|
|
78
|
-
const delegate = vi.spyOn(AppClient.prototype, 'getAppConfig').mockReturnValue(of(config));
|
|
79
|
-
const mockClient = new MockAppClient(client, manifest, config);
|
|
80
|
-
|
|
81
|
-
mockClient.getAppConfig({ appKey: 'my-app', tag: '' });
|
|
82
|
-
|
|
83
|
-
expect(delegate).toHaveBeenCalledWith({ appKey: 'my-app', tag: '' });
|
|
84
|
-
delegate.mockRestore();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('delegates for its own app key with a tag not matching the manifest build version', () => {
|
|
88
|
-
const delegate = vi.spyOn(AppClient.prototype, 'getAppConfig').mockReturnValue(of(config));
|
|
89
|
-
const mockClient = new MockAppClient(client, manifest, config);
|
|
90
|
-
|
|
91
|
-
mockClient.getAppConfig({ appKey: 'my-app', tag: '9.9.9' });
|
|
92
|
-
|
|
93
|
-
expect(delegate).toHaveBeenCalledWith({ appKey: 'my-app', tag: '9.9.9' });
|
|
94
|
-
delegate.mockRestore();
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
});
|