@equinor/fusion-framework-module-bookmark 4.0.3 → 4.1.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 (33) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +42 -0
  3. package/dist/esm/BookmarkModuleConfigurator.js +2 -1
  4. package/dist/esm/BookmarkModuleConfigurator.js.map +1 -1
  5. package/dist/esm/__tests__/mock/bookmark-mock.test.js +151 -0
  6. package/dist/esm/__tests__/mock/bookmark-mock.test.js.map +1 -0
  7. package/dist/esm/mock/BookmarkMockClient.js +281 -0
  8. package/dist/esm/mock/BookmarkMockClient.js.map +1 -0
  9. package/dist/esm/mock/BookmarkMockConfigurator.js +138 -0
  10. package/dist/esm/mock/BookmarkMockConfigurator.js.map +1 -0
  11. package/dist/esm/mock/index.js +26 -0
  12. package/dist/esm/mock/index.js.map +1 -0
  13. package/dist/esm/mock/module.js +42 -0
  14. package/dist/esm/mock/module.js.map +1 -0
  15. package/dist/esm/version.js +1 -1
  16. package/dist/esm/version.js.map +1 -1
  17. package/dist/tsconfig.tsbuildinfo +1 -1
  18. package/dist/types/BookmarkModuleConfigurator.d.ts +1 -0
  19. package/dist/types/__tests__/mock/bookmark-mock.test.d.ts +1 -0
  20. package/dist/types/mock/BookmarkMockClient.d.ts +121 -0
  21. package/dist/types/mock/BookmarkMockConfigurator.d.ts +86 -0
  22. package/dist/types/mock/index.d.ts +25 -0
  23. package/dist/types/mock/module.d.ts +43 -0
  24. package/dist/types/version.d.ts +1 -1
  25. package/package.json +20 -11
  26. package/src/BookmarkModuleConfigurator.ts +2 -1
  27. package/src/__tests__/mock/bookmark-mock.test.ts +199 -0
  28. package/src/mock/BookmarkMockClient.ts +310 -0
  29. package/src/mock/BookmarkMockConfigurator.ts +159 -0
  30. package/src/mock/index.ts +25 -0
  31. package/src/mock/module.ts +64 -0
  32. package/src/version.ts +1 -1
  33. package/vitest.config.ts +11 -0
@@ -0,0 +1,310 @@
1
+ import { of, throwError, type ObservableInput } from 'rxjs';
2
+ import { v4 as generateGUID } from 'uuid';
3
+
4
+ import type {
5
+ BookmarkNew,
6
+ BookmarksFilter,
7
+ BookmarkUpdate,
8
+ IBookmarkClient,
9
+ } from '../BookmarkClient.interface';
10
+ import type { Bookmark, BookmarkData, BookmarkUser, BookmarkWithoutData } from '../types';
11
+
12
+ /** Attributed to every bookmark the mock client creates or updates. */
13
+ const mockUser: BookmarkUser = { id: 'mock-user', name: 'Mock User' };
14
+
15
+ /**
16
+ * Strips the `payload` field from a bookmark, mirroring what the real
17
+ * `BookmarkClient` does before returning a {@link BookmarkWithoutData}.
18
+ *
19
+ * @param bookmark - The bookmark to strip.
20
+ * @returns The bookmark without its payload.
21
+ */
22
+ const stripPayload = (bookmark: Bookmark): BookmarkWithoutData => {
23
+ const { payload: _payload, ...rest } = bookmark;
24
+ return rest;
25
+ };
26
+
27
+ /**
28
+ * Checks whether a seeded bookmark matches the given {@link BookmarksFilter}.
29
+ *
30
+ * @remarks
31
+ * `appKey`, `contextId`, and `sourceSystem` are checked, mirroring what the real
32
+ * `BookmarkProvider` resolves and passes through as a filter (`BookmarkProvider.getAllBookmarks`
33
+ * always includes `sourceSystem`, so it must be matched even though it's rarely set explicitly).
34
+ *
35
+ * @param bookmark - The seeded bookmark to test.
36
+ * @param filter - The filter to match against, if any.
37
+ * @returns `true` when the bookmark satisfies every constraint in `filter`.
38
+ */
39
+ const matchesFilter = (bookmark: Bookmark, filter?: BookmarksFilter): boolean => {
40
+ // no filter means every seeded bookmark matches
41
+ if (!filter) return true;
42
+ // an appKey constraint excludes bookmarks attributed to a different app
43
+ if (filter.appKey && bookmark.appKey !== filter.appKey) return false;
44
+ // a contextId constraint excludes bookmarks attributed to a different context
45
+ if (filter.contextId && bookmark.context?.id !== filter.contextId) return false;
46
+ // a sourceSystem constraint excludes bookmarks attributed to a different source system
47
+ if (filter.sourceSystem) {
48
+ const { identifier, name, subSystem } = filter.sourceSystem;
49
+ // an identifier constraint excludes bookmarks attributed to a different source system identifier
50
+ if (identifier && bookmark.sourceSystem?.identifier !== identifier) return false;
51
+ // a name constraint excludes bookmarks attributed to a different source system name
52
+ if (name !== undefined && bookmark.sourceSystem?.name !== name) return false;
53
+ // a subSystem constraint excludes bookmarks attributed to a different source sub-system
54
+ if (subSystem !== undefined && bookmark.sourceSystem?.subSystem !== subSystem) return false;
55
+ }
56
+ return true;
57
+ };
58
+
59
+ /**
60
+ * Runs `fn` synchronously and reports the outcome as an {@link ObservableInput},
61
+ * so a mock method can `throw` for a "not found" case exactly like the async
62
+ * real client would reject.
63
+ *
64
+ * @template T - The value `fn` produces.
65
+ * @param fn - The synchronous operation to run.
66
+ * @returns An observable emitting `fn`'s result, or erroring with what it threw.
67
+ */
68
+ const resultOf = <T>(fn: () => T): ObservableInput<T> => {
69
+ try {
70
+ return of(fn());
71
+ } catch (error) {
72
+ return throwError(() => error);
73
+ }
74
+ };
75
+
76
+ /**
77
+ * In-memory {@link IBookmarkClient} backed by a `Map` of seeded bookmarks and a
78
+ * `Set` of favorite ids.
79
+ *
80
+ * @remarks
81
+ * Used by {@link BookmarkMockConfigurator} to swap out the real API client
82
+ * while leaving `BookmarkProvider` and the `bookmark-flows/` epics running
83
+ * unmodified — every create, update, delete, and favorite call still goes
84
+ * through the real flow logic, only the underlying data source is in-memory.
85
+ */
86
+ export class BookmarkMockClient implements IBookmarkClient {
87
+ #bookmarks = new Map<string, Bookmark>();
88
+ #favorites = new Set<string>();
89
+
90
+ /**
91
+ * Replaces the seeded bookmarks with the given collection, keyed by `id`.
92
+ *
93
+ * @param items - The bookmarks to seed. Replaces any previously seeded bookmarks.
94
+ */
95
+ public setBookmarks(items: Iterable<Bookmark>): void {
96
+ this.#bookmarks = new Map(Array.from(items, (item) => [item.id, item]));
97
+ }
98
+
99
+ /**
100
+ * Marks a seeded bookmark as a favorite, or clears it.
101
+ *
102
+ * @param bookmarkId - The id of the bookmark to update.
103
+ * @param isFavorite - `true` to favorite the bookmark, `false` to clear it.
104
+ */
105
+ public setFavorite(bookmarkId: string, isFavorite: boolean): void {
106
+ // track favorites as a set membership rather than a boolean field on the bookmark
107
+ if (isFavorite) {
108
+ this.#favorites.add(bookmarkId);
109
+ } else {
110
+ this.#favorites.delete(bookmarkId);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Looks up a seeded bookmark by id without going through the `ObservableInput` API.
116
+ *
117
+ * @param bookmarkId - The id of the bookmark to look up.
118
+ * @returns The seeded bookmark, or `undefined` when no bookmark was seeded for that id.
119
+ */
120
+ public getBookmark(bookmarkId: string): Bookmark | undefined {
121
+ return this.#bookmarks.get(bookmarkId);
122
+ }
123
+
124
+ /**
125
+ * Looks up a seeded bookmark by id, throwing when none was seeded.
126
+ *
127
+ * @param bookmarkId - The id of the bookmark to look up.
128
+ * @returns The seeded bookmark.
129
+ * @throws {Error} When no bookmark was seeded for `bookmarkId`.
130
+ */
131
+ #requireBookmark(bookmarkId: string): Bookmark {
132
+ const bookmark = this.#bookmarks.get(bookmarkId);
133
+ // fail loudly so a missing seed reads as a test setup bug, not a silent no-op
134
+ if (!bookmark) {
135
+ throw new Error(`BookmarkMockClient: no bookmark seeded for id '${bookmarkId}'`);
136
+ }
137
+ return bookmark;
138
+ }
139
+
140
+ /**
141
+ * Returns every seeded bookmark that matches `filter`, without its payload.
142
+ *
143
+ * @remarks
144
+ * The real client strips payloads from list results (see `BookmarkClient.getAllBookmarks`),
145
+ * so the mock does the same here even though the interface's declared return type is `Bookmark[]`.
146
+ *
147
+ * @param filter - Optional constraints to narrow the returned bookmarks.
148
+ * @returns The matching bookmarks, without their payloads.
149
+ */
150
+ public getAllBookmarks(filter?: BookmarksFilter): ObservableInput<Array<Bookmark>> {
151
+ return resultOf(
152
+ () =>
153
+ Array.from(this.#bookmarks.values())
154
+ // only bookmarks matching every constraint in `filter` are returned
155
+ .filter((bookmark) => matchesFilter(bookmark, filter))
156
+ // list results never include payloads, mirroring the real client
157
+ .map(stripPayload) as Array<Bookmark>,
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Returns a seeded bookmark by id, without its payload.
163
+ *
164
+ * @param bookmarkId - The id of the bookmark to look up.
165
+ * @returns The bookmark without its payload.
166
+ * @throws {Error} When no bookmark was seeded for `bookmarkId`.
167
+ */
168
+ public getBookmarkById(bookmarkId: string): ObservableInput<BookmarkWithoutData> {
169
+ return resultOf(() => stripPayload(this.#requireBookmark(bookmarkId)));
170
+ }
171
+
172
+ /**
173
+ * Returns the payload data of a seeded bookmark.
174
+ *
175
+ * @template T - The shape of the bookmark's payload.
176
+ * @param bookmarkId - The id of the bookmark to look up.
177
+ * @returns The bookmark's payload.
178
+ * @throws {Error} When no bookmark was seeded for `bookmarkId`.
179
+ */
180
+ public getBookmarkData<T extends BookmarkData>(bookmarkId: string): ObservableInput<T> {
181
+ return resultOf(() => this.#requireBookmark(bookmarkId).payload as T);
182
+ }
183
+
184
+ /**
185
+ * Replaces the payload data of a seeded bookmark.
186
+ *
187
+ * @template T - The shape of the bookmark's payload.
188
+ * @param bookmarkId - The id of the bookmark to update.
189
+ * @param data - The new payload data.
190
+ * @returns The payload that was set.
191
+ * @throws {Error} When no bookmark was seeded for `bookmarkId`.
192
+ */
193
+ public setBookmarkData<T extends BookmarkData | null>(
194
+ bookmarkId: string,
195
+ data: T,
196
+ ): ObservableInput<T> {
197
+ return resultOf(() => {
198
+ const existing = this.#requireBookmark(bookmarkId);
199
+ this.#bookmarks.set(bookmarkId, { ...existing, payload: data ?? undefined });
200
+ return data;
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Marks a seeded bookmark as a favorite.
206
+ *
207
+ * @param bookmarkId - The id of the bookmark to favorite.
208
+ * @returns `true` once the bookmark is marked as a favorite.
209
+ * @throws {Error} When no bookmark was seeded for `bookmarkId`.
210
+ */
211
+ public addBookmarkToFavorites(bookmarkId: string): ObservableInput<boolean> {
212
+ return resultOf(() => {
213
+ this.#requireBookmark(bookmarkId);
214
+ this.#favorites.add(bookmarkId);
215
+ return true;
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Clears a seeded bookmark's favorite status.
221
+ *
222
+ * @param bookmarkId - The id of the bookmark to unfavorite.
223
+ * @returns `true` once the bookmark is no longer a favorite.
224
+ */
225
+ public removeBookmarkFromFavorites(bookmarkId: string): ObservableInput<boolean> {
226
+ return resultOf(() => {
227
+ this.#favorites.delete(bookmarkId);
228
+ return true;
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Checks whether a seeded bookmark is currently a favorite.
234
+ *
235
+ * @param bookmarkId - The id of the bookmark to check.
236
+ * @returns `true` when the bookmark is a favorite.
237
+ */
238
+ public isBookmarkFavorite(bookmarkId: string): ObservableInput<boolean> {
239
+ return of(this.#favorites.has(bookmarkId));
240
+ }
241
+
242
+ /**
243
+ * Seeds a new bookmark from create input, generating its id and audit fields.
244
+ *
245
+ * @template T - The shape of the bookmark's payload.
246
+ * @param bookmark - The data to create the bookmark from.
247
+ * @returns The created bookmark.
248
+ */
249
+ public createBookmark<T extends BookmarkData>(
250
+ bookmark: BookmarkNew<T>,
251
+ ): ObservableInput<Bookmark<T>> {
252
+ return resultOf(() => {
253
+ // `contextId` is a plain id on the client input, but a `{ id }` object on the bookmark itself
254
+ const { contextId, ...rest } = bookmark;
255
+ // merge the create input with generated audit fields and a normalized context
256
+ const created: Bookmark<T> = {
257
+ ...rest,
258
+ id: generateGUID(),
259
+ created: new Date(),
260
+ createdBy: mockUser,
261
+ ...(contextId ? { context: { id: contextId } } : {}),
262
+ };
263
+ this.#bookmarks.set(created.id, created);
264
+ return created;
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Applies updates to a seeded bookmark, stamping new audit fields.
270
+ *
271
+ * @template T - The shape of the bookmark's payload.
272
+ * @param bookmarkId - The id of the bookmark to update.
273
+ * @param updates - The fields to update.
274
+ * @returns The updated bookmark.
275
+ * @throws {Error} When no bookmark was seeded for `bookmarkId`.
276
+ */
277
+ public updateBookmark<T extends BookmarkData>(
278
+ bookmarkId: string,
279
+ updates: BookmarkUpdate<T>,
280
+ ): ObservableInput<Bookmark<T>> {
281
+ return resultOf(() => {
282
+ const existing = this.#requireBookmark(bookmarkId);
283
+ // merge the updates onto the existing bookmark, then stamp new audit fields
284
+ const updated: Bookmark<T> = {
285
+ ...existing,
286
+ ...updates,
287
+ // `payload: null` clears data on a BookmarkUpdate, so normalize it to `undefined` like setBookmarkData does
288
+ payload: updates.payload === null ? undefined : (updates.payload ?? existing.payload),
289
+ updated: new Date(),
290
+ updatedBy: mockUser,
291
+ } as Bookmark<T>;
292
+ this.#bookmarks.set(bookmarkId, updated);
293
+ return updated;
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Removes a seeded bookmark and clears its favorite status.
299
+ *
300
+ * @param bookmarkId - The id of the bookmark to delete.
301
+ * @returns `true` when a bookmark was seeded for `bookmarkId` and was removed.
302
+ */
303
+ public deleteBookmark(bookmarkId: string): ObservableInput<boolean> {
304
+ return resultOf(() => {
305
+ const existed = this.#bookmarks.delete(bookmarkId);
306
+ this.#favorites.delete(bookmarkId);
307
+ return existed;
308
+ });
309
+ }
310
+ }
@@ -0,0 +1,159 @@
1
+ import { of } from 'rxjs';
2
+
3
+ import type { ConfigBuilderCallbackArgs } from '@equinor/fusion-framework-module';
4
+
5
+ import { BookmarkModuleConfigurator } from '../BookmarkModuleConfigurator';
6
+ import type { IBookmarkProvider } from '../BookmarkProvider.interface';
7
+ import type { Bookmark, BookmarkModuleConfig } from '../types';
8
+
9
+ import { BookmarkMockClient } from './BookmarkMockClient';
10
+
11
+ /**
12
+ * Fallback application resolver used when the mock is configured standalone,
13
+ * without a real `app` module registered.
14
+ */
15
+ const mockApplicationResolver: BookmarkModuleConfig['resolve']['application'] = async () => ({
16
+ appKey: 'mock-app',
17
+ });
18
+
19
+ /**
20
+ * Fallback context resolver used when the mock is configured standalone,
21
+ * without a real `context` module registered.
22
+ */
23
+ const mockContextResolver: BookmarkModuleConfig['resolve']['context'] = async () => undefined;
24
+
25
+ /**
26
+ * The real bookmark configurator, backed by an in-memory {@link BookmarkMockClient}.
27
+ *
28
+ * @remarks
29
+ * Extends {@link BookmarkModuleConfigurator} directly, so the whole builder API
30
+ * (source system, filters, resolvers, `setParent`) stays available. Only a
31
+ * default client is added — through the same
32
+ * {@link BookmarkModuleConfigurator.setClient | setClient} seam a caller would
33
+ * use to plug in their own client — so an explicit `setClient()` call still
34
+ * replaces it outright, exactly as it would on the real configurator.
35
+ *
36
+ * Seeded bookmarks and favorites flow through the real `BookmarkProvider` and
37
+ * `bookmark-flows/` epics unmodified: create, update, delete, and favorite
38
+ * calls all reach {@link BookmarkMockClient}, not a stand-in.
39
+ *
40
+ * @example Seed bookmarks and a current bookmark
41
+ * ```typescript
42
+ * enableBookmarkMock(configurator, (builder) => {
43
+ * builder.setBookmarks([myBookmark]);
44
+ * builder.setCurrentBookmark(myBookmark.id);
45
+ * builder.setFavorite(myBookmark.id, true);
46
+ * });
47
+ * ```
48
+ *
49
+ * @example Take full control of the client
50
+ * ```typescript
51
+ * enableBookmarkMock(configurator, (builder) => {
52
+ * builder.setClient(myOwnBookmarkClient);
53
+ * });
54
+ * ```
55
+ */
56
+ export class BookmarkMockConfigurator extends BookmarkModuleConfigurator {
57
+ #client = new BookmarkMockClient();
58
+ #currentId?: string;
59
+
60
+ /**
61
+ * Seeds the bookmarks the mock client resolves through `getAllBookmarks`,
62
+ * `getBookmarkById`, and `getBookmarkData`.
63
+ *
64
+ * @param items - The bookmarks to seed. Replaces any previously seeded bookmarks.
65
+ * @returns `this`, for chaining.
66
+ */
67
+ public setBookmarks(items: Iterable<Bookmark>): this {
68
+ this.#client.setBookmarks(items);
69
+ return this;
70
+ }
71
+
72
+ /**
73
+ * Declares which seeded bookmark `IBookmarkProvider.currentBookmark` reports
74
+ * as active once the module initializes.
75
+ *
76
+ * @remarks
77
+ * `BookmarkProvider` only reads its initial current bookmark from a parent
78
+ * provider (see {@link BookmarkModuleConfigurator.setParent}), so this
79
+ * registers a synthetic parent exposing the seeded bookmark — the same seam
80
+ * a real nested-portal provider would use, just standing in for one.
81
+ *
82
+ * @param bookmarkId - The id of a bookmark previously passed to {@link setBookmarks},
83
+ * or `undefined` to leave the current bookmark unset.
84
+ * @returns `this`, for chaining.
85
+ */
86
+ public setCurrentBookmark(bookmarkId: string | undefined): this {
87
+ this.#currentId = bookmarkId;
88
+ return this;
89
+ }
90
+
91
+ /**
92
+ * Marks a seeded bookmark as a favorite, or clears it, reflected through
93
+ * `IBookmarkClient.isBookmarkFavorite`.
94
+ *
95
+ * @param bookmarkId - The id of the bookmark to update.
96
+ * @param isFavorite - `true` to favorite the bookmark, `false` to clear it. Defaults to `true`.
97
+ * @returns `this`, for chaining.
98
+ */
99
+ public setFavorite(bookmarkId: string, isFavorite = true): this {
100
+ this.#client.setFavorite(bookmarkId, isFavorite);
101
+ return this;
102
+ }
103
+
104
+ /**
105
+ * Installs the mock client and a synthetic current-bookmark parent before
106
+ * building the configuration, unless the caller already declared their own.
107
+ *
108
+ * @remarks
109
+ * Also falls back to trivial application/context resolvers when neither an
110
+ * `app` nor a `context` module is registered alongside this mock — both are
111
+ * required by {@link BookmarkModuleConfig}'s schema, but a standalone test
112
+ * has no reason to pull in either module just to satisfy it. Registering a
113
+ * real `app`/`context` module still wins, exactly as it does on the real
114
+ * configurator.
115
+ *
116
+ * @param init - The config builder callback arguments.
117
+ * @param initial - An optional initial config to merge into the returned config.
118
+ * @returns The observable configuration, produced by the real configurator.
119
+ */
120
+ protected override _createConfig(
121
+ init: ConfigBuilderCallbackArgs,
122
+ initial?: Partial<BookmarkModuleConfig>,
123
+ ) {
124
+ // only stand in a mock client when the caller hasn't set their own
125
+ if (!this._has('client')) {
126
+ this.setClient(this.#client);
127
+ }
128
+
129
+ // only stand in a synthetic parent when a current bookmark was seeded and
130
+ // the caller hasn't declared their own parent provider
131
+ if (this.#currentId && !this._has('parent')) {
132
+ const current = this.#client.getBookmark(this.#currentId) ?? null;
133
+ const parent: Pick<IBookmarkProvider, 'currentBookmark' | 'currentBookmark$'> = {
134
+ currentBookmark: current,
135
+ currentBookmark$: of(current),
136
+ };
137
+ this.setParent(parent as IBookmarkProvider);
138
+ }
139
+
140
+ // no app module means the real default resolver would resolve to `undefined`, which fails
141
+ // the required `resolve.application` schema field — but `initial.resolve.application` may
142
+ // already carry a resolver inherited from a parent config, which must not be overwritten
143
+ if (
144
+ !this._has('resolve.application') &&
145
+ !initial?.resolve?.application &&
146
+ !init.hasModule('app')
147
+ ) {
148
+ this.setApplicationResolver(async () => mockApplicationResolver);
149
+ }
150
+
151
+ // no context module means the real default resolver would resolve to `undefined`, which
152
+ // fails the required `resolve.context` schema field — same inherited-config caveat as above
153
+ if (!this._has('resolve.context') && !initial?.resolve?.context && !init.hasModule('context')) {
154
+ this.setContextResolver(async () => mockContextResolver);
155
+ }
156
+
157
+ return super._createConfig(init, initial);
158
+ }
159
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Mock bookmark module for tests: real provider, real configurator, in-memory client.
3
+ *
4
+ * @remarks
5
+ * Substituting `IBookmarkClient` is the smallest change that removes the
6
+ * Fusion Core Services backend from a test. Everything above it — the
7
+ * `BookmarkProvider` store, `bookmark-flows/` epics, payload generators, and
8
+ * events — is the production code path.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { enableBookmarkMock } from '@equinor/fusion-framework-module-bookmark/mock';
13
+ *
14
+ * enableBookmarkMock(configurator, (builder) => {
15
+ * builder.setBookmarks([myBookmark]);
16
+ * builder.setCurrentBookmark(myBookmark.id);
17
+ * builder.setFavorite(myBookmark.id, true);
18
+ * });
19
+ * ```
20
+ *
21
+ * @packageDocumentation
22
+ */
23
+ export { BookmarkMockClient } from './BookmarkMockClient';
24
+ export { BookmarkMockConfigurator } from './BookmarkMockConfigurator';
25
+ export { enableBookmarkMock, bookmarkMockModule, type BookmarkMockConfigFn } from './module';
@@ -0,0 +1,64 @@
1
+ import type { AnyModule, IModulesConfigurator } from '@equinor/fusion-framework-module';
2
+
3
+ import { module, type BookmarkModule } from '../bookmark-module';
4
+
5
+ import { BookmarkMockConfigurator } from './BookmarkMockConfigurator';
6
+
7
+ /**
8
+ * The bookmark module with a mock, in-memory client instead of a live
9
+ * `@equinor/fusion-framework-module-services` connection.
10
+ *
11
+ * @remarks
12
+ * Only `configure` differs from the real module. `initialize` is the
13
+ * production one, untouched, so the real `BookmarkProvider` and
14
+ * `bookmark-flows/` epics run exactly as they do in production — a test
15
+ * observes the real create/update/delete/favorite flow, not a rehearsal of it.
16
+ */
17
+ export const bookmarkMockModule: BookmarkModule = {
18
+ ...module,
19
+ configure: () => new BookmarkMockConfigurator(),
20
+ };
21
+
22
+ /**
23
+ * Configuration callback for {@link enableBookmarkMock}.
24
+ *
25
+ * @template TRef - Reference type forwarded to the callback, inferred from the configurator.
26
+ */
27
+ export type BookmarkMockConfigFn<TRef = unknown> = (
28
+ configurator: BookmarkMockConfigurator,
29
+ ref?: TRef,
30
+ ) => void | Promise<void>;
31
+
32
+ /**
33
+ * Enables the bookmark module against an in-memory mock client, so a test
34
+ * needs no network and no real Fusion Core Services backend.
35
+ *
36
+ * @remarks
37
+ * Registered last, this replaces whichever bookmark module the configurator
38
+ * already carries, so it works on a `FrameworkConfigurator` that pre-registers
39
+ * the real one.
40
+ *
41
+ * @param configurator - The modules configurator to register on.
42
+ * @param configure - Optional callback to seed bookmarks, the current bookmark, or favorites.
43
+ * @template TModules - The array of module descriptors managed by `configurator`.
44
+ * @template TRef - Reference type forwarded to `configure`, inferred from `configurator`.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * enableBookmarkMock(configurator, (builder) => {
49
+ * builder.setBookmarks([myBookmark]);
50
+ * builder.setCurrentBookmark(myBookmark.id);
51
+ * });
52
+ * ```
53
+ */
54
+ export const enableBookmarkMock = <
55
+ TModules extends Array<AnyModule> = Array<AnyModule>,
56
+ TRef = unknown,
57
+ >(
58
+ configurator: IModulesConfigurator<TModules, TRef>,
59
+ configure?: BookmarkMockConfigFn<TRef>,
60
+ ): void => {
61
+ configurator.addConfig({ module: bookmarkMockModule, configure } as {
62
+ module: BookmarkModule;
63
+ });
64
+ };
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '4.0.3';
2
+ export const version = '4.1.0-next.0';
@@ -0,0 +1,11 @@
1
+ import { defineProject } from 'vitest/config';
2
+
3
+ import { name, version } from './package.json' with { type: 'json' };
4
+
5
+ export default defineProject({
6
+ test: {
7
+ environment: 'node',
8
+ include: ['src/__tests__/**/*.test.ts'],
9
+ name: `${name}@${version}`,
10
+ },
11
+ });