feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath - #5241
Merged
Merged
Conversation
…ents, and operators subpath Add EventModuleConfigurator, a BaseConfigBuilder-based configurator with fluent setOnDispatch/setOnBubble setters, replacing direct property assignment on the config object (kept as a deprecated path, no major bump needed). IEventModuleConfigurator is renamed to EventModuleConfig and converted from an interface to a type; IEventModuleConfigurator is kept as a deprecated alias. Narrow the dispatchEvent return type for registered FrameworkEventMap keys and pre-constructed event instances so callers get back the specific event type. Fix event$ to emit only after listeners and the bubble hook have run, matching its documented contract (it previously emitted before dispatch). Add waitForEvent/watchEvents test helpers and a ./operators subpath export for filterEvent (moved from src/filter-event.ts to src/operators/filter-event.ts). Rename configurator.ts -> EventModuleConfigurator.ts and provider.ts -> EventModuleProvider.ts for filename-convention compliance, and add a docs/ folder (configuration, observable-patterns, lifecycle, testing).
…t page Trim vue-press/src/modules/event/README.md to an @include of the package README, add a docs/ mirror (configuration, observable-patterns, lifecycle, testing) that @includes the package's own docs, matching the http/module pattern. Move the event React bindings page from event/react.md to react/event/README.md, alongside react/router/, as an @include of @equinor/fusion-framework-react-module-event's README. Update the sidebar to match.
🦋 Changeset detectedLatest commit: e584b84 The changes in this PR will be included in the next version bump. This PR includes changesets to release 28 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
odinr
marked this pull request as ready for review
August 8, 2026 17:26
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a fluent event configurator, event-observation utilities, stronger dispatch typing, and reorganized package documentation.
Changes:
- Adds
EventModuleConfigurator, post-dispatch event emission, and narrower dispatch types. - Adds
waitForEvent,watchEvents, and operator/utility subpaths with tests. - Consolidates event documentation and VuePress navigation.
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
vue-press/src/modules/react/event/README.md |
Adds relocated React event page. |
vue-press/src/modules/event/README.md |
Includes package README. |
vue-press/src/modules/event/react.md |
Removes old React page. |
vue-press/src/modules/event/docs/testing.md |
Includes testing guide. |
vue-press/src/modules/event/docs/observable-patterns.md |
Includes observable guide. |
vue-press/src/modules/event/docs/lifecycle.md |
Includes lifecycle guide. |
vue-press/src/modules/event/docs/configuration.md |
Includes configuration guide. |
vue-press/src/.vuepress/sidebar.ts |
Updates event navigation. |
pnpm-lock.yaml |
Records Vitest dependencies. |
packages/modules/event/vitest.config.ts |
Configures package tests. |
packages/modules/event/src/utils/watch-events.ts |
Implements event collection. |
packages/modules/event/src/utils/wait-for-event.ts |
Implements event waiting. |
packages/modules/event/src/utils/index.ts |
Exports utility APIs. |
packages/modules/event/src/utils/apply-event-matcher.ts |
Applies event matchers. |
packages/modules/event/src/operators/index.ts |
Exports RxJS operators. |
packages/modules/event/src/operators/filter-event.ts |
Relocates filterEvent. |
packages/modules/event/src/module.ts |
Integrates fluent configuration. |
packages/modules/event/src/index.ts |
Updates public exports. |
packages/modules/event/src/FrameworkEvent.ts |
Updates provider import. |
packages/modules/event/src/EventModuleProvider.ts |
Changes dispatch typing and emission order. |
packages/modules/event/src/EventModuleConfigurator.ts |
Adds fluent configurator. |
packages/modules/event/src/configurator.ts |
Removes legacy configurator file. |
packages/modules/event/src/__tests__/watchEvents.test.ts |
Tests event collection. |
packages/modules/event/src/__tests__/waitForEvent.test.ts |
Tests event waiting. |
packages/modules/event/README.md |
Links expanded documentation. |
packages/modules/event/package.json |
Adds subpaths and test script. |
packages/modules/event/docs/testing.md |
Documents utility helpers. |
packages/modules/event/docs/observable-patterns.md |
Documents observable usage. |
packages/modules/event/docs/lifecycle.md |
Documents dispatch lifecycle. |
packages/modules/event/docs/configuration.md |
Documents configuration APIs. |
.changeset/module-event_utils-helpers.md |
Records utility feature release. |
.changeset/module-event_configurator.md |
Records configurator release. |
.changeset/docs_event-module-docs.md |
Records documentation update. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (6)
packages/modules/event/src/EventModuleProvider.ts:91
- The new narrowing exists only on
IEventModuleProvider; the exportedEventModuleProviderclass still exposes its implementation signature returningPromise<FrameworkEvent>. Consumers holding the concrete class therefore receive neither advertised narrowing, so add matching overload declarations to the class before its implementation.
packages/modules/event/src/EventModuleProvider.ts:68 - This return type is unsound when a
FrameworkEventMapentry is a custom event subclass: the string overload constructs a plainFrameworkEvent, so TypeScript promises subclass members that do not exist at runtime. Return the constructed base event parameterized with the mapped init/type, reserving exact-instance preservation for the preconstructed-event overload.
packages/modules/event/src/EventModuleProvider.ts:236 - Publishing the live mutable
FrameworkEventdoes not makepreventDefault/stopPropagationno-ops after dispatch. Anevent$observer can invoke them during synchronousnext, changing the event'scanceled/bubblesstate beforedispatchEventreturns and affecting later observers; lock lifecycle mutation after dispatch or publish an immutable snapshot.
packages/modules/event/src/utils/wait-for-event.ts:126 - This abort listener is never removed when the wait resolves, times out, or the source errors/completes. Reusing a long-lived signal therefore accumulates retained subscriptions and callbacks; use a named abort handler, remove it from shared cleanup, and route timeout/abort through that cleanup.
signal.addEventListener(
'abort',
() => {
sub.unsubscribe();
clearTimeout(timer);
reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
},
{ once: true },
packages/modules/event/src/EventModuleProvider.ts:236
- The changed
event$ordering and intentionalfinallyemission have no direct tests. Add coverage proving listeners and the bubble hook run before stream emission, plus an error-path test proving an event is still emitted when dispatch throws.
packages/modules/event/docs/lifecycle.md:102 - This unmatched closing fence renders a stray code block at the end of the lifecycle page. Remove it.
</details>
---
💡 <a href="/equinor/fusion-framework/new/feat/fusion-testing?filename=.github/skills/code-review/SKILL.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add a `code-review` agent skill</a> or configure MCP servers for context-aware, tailored reviews. <a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review#mcp-servers-and-agent-skills" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn more in the docs.</a>
Contributor
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
odinr
added a commit
that referenced
this pull request
Aug 13, 2026
…ents, and operators subpath (#5241) * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath Add EventModuleConfigurator, a BaseConfigBuilder-based configurator with fluent setOnDispatch/setOnBubble setters, replacing direct property assignment on the config object (kept as a deprecated path, no major bump needed). IEventModuleConfigurator is renamed to EventModuleConfig and converted from an interface to a type; IEventModuleConfigurator is kept as a deprecated alias. Narrow the dispatchEvent return type for registered FrameworkEventMap keys and pre-constructed event instances so callers get back the specific event type. Fix event$ to emit only after listeners and the bubble hook have run, matching its documented contract (it previously emitted before dispatch). Add waitForEvent/watchEvents test helpers and a ./operators subpath export for filterEvent (moved from src/filter-event.ts to src/operators/filter-event.ts). Rename configurator.ts -> EventModuleConfigurator.ts and provider.ts -> EventModuleProvider.ts for filename-convention compliance, and add a docs/ folder (configuration, observable-patterns, lifecycle, testing). * docs(vue-press): align event module docs with docs/ folder, move React page Trim vue-press/src/modules/event/README.md to an @include of the package README, add a docs/ mirror (configuration, observable-patterns, lifecycle, testing) that @includes the package's own docs, matching the http/module pattern. Move the event React bindings page from event/react.md to react/event/README.md, alongside react/router/, as an @include of @equinor/fusion-framework-react-module-event's README. Update the sidebar to match. * style(module-event): apply Biome formatting fixes flagged by reviewdog * fix(module-event): fix waitForEvent TDZ on sync completion, correct doc inaccuracies
odinr
added a commit
that referenced
this pull request
Aug 14, 2026
…ents, and operators subpath (#5241) * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath Add EventModuleConfigurator, a BaseConfigBuilder-based configurator with fluent setOnDispatch/setOnBubble setters, replacing direct property assignment on the config object (kept as a deprecated path, no major bump needed). IEventModuleConfigurator is renamed to EventModuleConfig and converted from an interface to a type; IEventModuleConfigurator is kept as a deprecated alias. Narrow the dispatchEvent return type for registered FrameworkEventMap keys and pre-constructed event instances so callers get back the specific event type. Fix event$ to emit only after listeners and the bubble hook have run, matching its documented contract (it previously emitted before dispatch). Add waitForEvent/watchEvents test helpers and a ./operators subpath export for filterEvent (moved from src/filter-event.ts to src/operators/filter-event.ts). Rename configurator.ts -> EventModuleConfigurator.ts and provider.ts -> EventModuleProvider.ts for filename-convention compliance, and add a docs/ folder (configuration, observable-patterns, lifecycle, testing). * docs(vue-press): align event module docs with docs/ folder, move React page Trim vue-press/src/modules/event/README.md to an @include of the package README, add a docs/ mirror (configuration, observable-patterns, lifecycle, testing) that @includes the package's own docs, matching the http/module pattern. Move the event React bindings page from event/react.md to react/event/README.md, alongside react/router/, as an @include of @equinor/fusion-framework-react-module-event's README. Update the sidebar to match. * style(module-event): apply Biome formatting fixes flagged by reviewdog * fix(module-event): fix waitForEvent TDZ on sync completion, correct doc inaccuracies
odinr
added a commit
that referenced
this pull request
Aug 16, 2026
…ents, and operators subpath (#5241) * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath Add EventModuleConfigurator, a BaseConfigBuilder-based configurator with fluent setOnDispatch/setOnBubble setters, replacing direct property assignment on the config object (kept as a deprecated path, no major bump needed). IEventModuleConfigurator is renamed to EventModuleConfig and converted from an interface to a type; IEventModuleConfigurator is kept as a deprecated alias. Narrow the dispatchEvent return type for registered FrameworkEventMap keys and pre-constructed event instances so callers get back the specific event type. Fix event$ to emit only after listeners and the bubble hook have run, matching its documented contract (it previously emitted before dispatch). Add waitForEvent/watchEvents test helpers and a ./operators subpath export for filterEvent (moved from src/filter-event.ts to src/operators/filter-event.ts). Rename configurator.ts -> EventModuleConfigurator.ts and provider.ts -> EventModuleProvider.ts for filename-convention compliance, and add a docs/ folder (configuration, observable-patterns, lifecycle, testing). * docs(vue-press): align event module docs with docs/ folder, move React page Trim vue-press/src/modules/event/README.md to an @include of the package README, add a docs/ mirror (configuration, observable-patterns, lifecycle, testing) that @includes the package's own docs, matching the http/module pattern. Move the event React bindings page from event/react.md to react/event/README.md, alongside react/router/, as an @include of @equinor/fusion-framework-react-module-event's README. Update the sidebar to match. * style(module-event): apply Biome formatting fixes flagged by reviewdog * fix(module-event): fix waitForEvent TDZ on sync completion, correct doc inaccuracies
odinr
added a commit
that referenced
this pull request
Aug 16, 2026
…kbooks (#5297) * feat(openapi-mock): add package for generating OpenAPI-shaped fake responses Adds @equinor/fusion-openapi-mock, a framework-agnostic utility that fakes OpenAPI 3 responses straight from a parsed spec document. Every operation with an operationId is faked from its declared success response schema, with overrides/register for edge cases, seed for repeatable output, and a FieldFakerMap for realistic per-field values via @faker-js/faker. No dependency on any HTTP or routing framework: resolve({ method, path, query }) returns a plain { status, mock }, so it composes with @equinor/fusion-framework-module-http's mock router or any other server. Related: equinor/fusion-core-tasks#1660 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(deps): update lockfile for @equinor/fusion-openapi-mock * fix(openapi-mock): address review feedback on route matching, response fallback, and validation - sort operations by specificity so literal routes win over templated ones - fall back to lowest declared numeric status when no 2xx/default response exists - validate parsed YAML faker maps and fetched documents are plain objects - use an isolated faker instance per generation instead of mutating the global singleton - refactor createOpenApiMock and apply-field-fakers into smaller, focused functions * refactor(openapi-mock): favor readability and avoid mutable locals - replace && shorthand and let-based branches with explicit if/early-return - extract resolveResponseSchema, parseDocumentText helpers - rewrite resolvePointer's pointer walk with Array#reduce instead of a mutable loop variable * feat(module-msal): add a mock entry point for testing without credentials or network Adds ./mock, so applications can run against the auth module without credentials or network access: import { enableMsalMock, createMsalMockClient } from '@equinor/fusion-framework-module-msal/mock'; enableMsalMock(configurator, (builder) => { builder.setAccount({ name: 'Ada Lovelace' }); }); Only the MSAL client is substituted: - MsalMockClient resolves tokens in-process and takes the same MsalClientConfig as the real MsalClient, keeps a real account cache keyed by homeAccountId (matching MSAL's getAccount/getAllAccounts semantics), and produces structurally valid, unsigned JWTs. - The client construction seam (_createClient/_createClientConfig) is extracted from MsalConfigurator._processConfig, giving a supported seam for authenticating through something other than Entra ID. No client is built when the module is hoisted onto a host application's provider, since a client built during configuration would be discarded or shadow the host's signed-in user (_isHoisted). - MsalMockConfigurator.setAccount lets a test declare the signed-in user on the builder; the account is applied before MsalProvider initializes, so the provider's own start-up path (including automatic login when signedOut + requiresAuth) acts on it. - The config schema moved into MsalConfig.schema.ts with MsalConfigExtension, an extension point mock configuration merges into without the builder needing to cast past its type. MsalProvider, MsalConfigurator and IMsalProvider are otherwise untouched; the mock module's configure differs from the real module, initialize does not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(module-http): add a mock entry point with middleware and adapters Adds @equinor/fusion-framework-module-http/mock, so HTTP clients answer requests from registered route handlers instead of the network: import { enableHttpMock } from '@equinor/fusion-framework-module-http/mock'; enableHttpMock(configurator, (builder) => { builder.configureClient('catalog', { baseUri: 'https://api.example.com' }); builder.get('/items', () => Response.json([{ id: 1 }])); }); Route handlers are Fetch-standard middleware - (request: Request) => Response | undefined | Promise<...> - tried in registration order, with undefined falling through to the next one. One router is shared across every named client a HttpMockConfigurator builds, matched against each request's fully resolved URL. HttpClient gains a _performFetch(uri, init) seam, isolated from _fetch$ so a test double replaces only the network call while request preparation, the response pipeline, and abort handling run unchanged. Two adapters drop in an existing backend with no hard dependency on it: - fromExpressStyleHandler(handler) adapts an Express-style (req, res) handler (or a whole framework built from them, like openapi-backend). - fromOpenApiMock(openApiMock) adapts an @equinor/fusion-openapi-mock instance (duck-typed, no package dependency), so a real openapi.json/openapi.yaml fakes every response with no handlers written at all. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(module-service-discovery): add a mock entry point backed by an in-memory registry Adds @equinor/fusion-framework-module-service-discovery/mock, so service discovery resolves from an in-memory registry instead of the network: import { mockServiceDiscovery } from '@equinor/fusion-framework-module-service-discovery/mock'; mockServiceDiscovery(configurator, { services: [{ key: 'apps', uri: 'https://apps.test' }] }); ServiceDiscoveryMockConfigurator builds the registry on the builder itself - setBaseUri, addService, addServices, removeService, setServices, setResolveUnknownServices - and the client is constructed from that registry when the module builds its config, so a test never has to construct a client just to add a service. setBaseUri also lets the default service endpoints point at a local mock server such as http://localhost:3000, so an application can make real HTTP calls against Mockoon, Prism or the dev server without a service worker intercepting requests. Exposes the discovery client on the provider as .client, mirroring MsalProvider.client, so a test can spy on it directly: vi.spyOn(fusion.modules.serviceDiscovery.client, 'resolveService').mockResolvedValue(service); configureServiceDiscovery now also accepts a synchronous callback - the underlying builder always allowed it, only the exported type required a promise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(module): fix configurator phase ordering and dot-path optional branches Two independent fixes needed by the framework mock work: - Module re-registration now replaces the prior module's configure, afterConfig, and afterInit callbacks instead of appending to them, keyed by module name (_dedupeModulesByName / _removeModuleCallbacks). This prevents stale callback execution when a mock module (e.g. enableMsalMock) overrides a real module registration, and fixes configurator phases running out of order or skipping post-configure hooks in certain initialization paths. - DotPath now unwraps an optional object property with NonNullable the same way DotPathType already did, so a path BaseConfigBuilder understands is one _set no longer refuses. Given { foo?: { bar: string } }, 'foo.bar' is now a valid path, matching 'foo' which was already allowed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(framework): add a mock entry point that composes each module's own test double Adds @equinor/fusion-framework/mock for initializing the framework in a test: import { mockFramework } from '@equinor/fusion-framework/mock'; const fusion = await mockFramework((configurator) => { configurator.msal.setAccount({ name: 'Ada Lovelace' }); configurator.serviceDiscovery.setBaseUri('http://localhost:6669'); }); mockFramework runs the real configure -> initialize pipeline with the real built-in modules and substitutes only the boundaries that leave the process - the MSAL client and the service discovery client. Module wiring, configuration validation and lifecycle hooks behave as they do in production. FrameworkMockConfigurator gains _pin/_getConfig, so a module supplied through TModules gets the same kind of named accessor .msal and .serviceDiscovery already have, rather than that pinning being hand-written once per built-in mock. .services, .context and .telemetry are exposed the same way, though they still perform real I/O until those modules have a test double of their own. event is intentionally left out - its configure factory reads ref to wire event bubbling to a parent event provider when hoisted, and pinning it would call configure() with ref always undefined, silently disabling that bubbling. The entry point owns no mock logic of its own: each module exports its own test double from its own ./mock entry point, and this one only composes the built-in set. It has no test-runner dependency and provides no mocking API, since replacing an individual call belongs to the test runner. init() no longer throws when no DOM is present - the window.Fusion assignment (for portal shells and widgets) is now skipped when window is undefined, so a test runner using the node environment or a server-side render no longer fails with 'ReferenceError: window is not defined'. Also restructures documentation so each README is an entry point rather than a manual: long-form content (including each package's testing.md) moved into per-package docs/ folders, matching the convention already used by @equinor/fusion-framework-module and @equinor/fusion-framework-module-http. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review comments on PR #5205 - msal-config-schema: reject unparseable version instead of coercing to "null" - HttpMockRouter: reset RegExp lastIndex so global/sticky match patterns are reusable across requests - extract decodeJwtSegment into its own file, fix atob/UTF-8 round-trip for non-ASCII claims - service-discovery docs: dedupe heading, close dangling code fence * fix(http-mock): await handleRequest in fromExpressStyleHandler Prevents an async Express-style handler's rejection from becoming an unhandled promise rejection instead of failing the middleware call. * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath (#5241) * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath Add EventModuleConfigurator, a BaseConfigBuilder-based configurator with fluent setOnDispatch/setOnBubble setters, replacing direct property assignment on the config object (kept as a deprecated path, no major bump needed). IEventModuleConfigurator is renamed to EventModuleConfig and converted from an interface to a type; IEventModuleConfigurator is kept as a deprecated alias. Narrow the dispatchEvent return type for registered FrameworkEventMap keys and pre-constructed event instances so callers get back the specific event type. Fix event$ to emit only after listeners and the bubble hook have run, matching its documented contract (it previously emitted before dispatch). Add waitForEvent/watchEvents test helpers and a ./operators subpath export for filterEvent (moved from src/filter-event.ts to src/operators/filter-event.ts). Rename configurator.ts -> EventModuleConfigurator.ts and provider.ts -> EventModuleProvider.ts for filename-convention compliance, and add a docs/ folder (configuration, observable-patterns, lifecycle, testing). * docs(vue-press): align event module docs with docs/ folder, move React page Trim vue-press/src/modules/event/README.md to an @include of the package README, add a docs/ mirror (configuration, observable-patterns, lifecycle, testing) that @includes the package's own docs, matching the http/module pattern. Move the event React bindings page from event/react.md to react/event/README.md, alongside react/router/, as an @include of @equinor/fusion-framework-react-module-event's README. Update the sidebar to match. * style(module-event): apply Biome formatting fixes flagged by reviewdog * fix(module-event): fix waitForEvent TDZ on sync completion, correct doc inaccuracies * feat(module-analytics): add MockAnalyticsAdapter for recording tracked analytics events (#5242) * feat(module-analytics): add MockAnalyticsAdapter for recording tracked analytics events - Add MockAnalyticsAdapter implementing IAnalyticsAdapter, recording events in-memory for test assertions (getAnalytics, waitForAnalytic) - Add ./mock subpath export - Add vitest.config.ts to make the package a discoverable vitest project - Document the new adapter in README.md and add a changeset Implements equinor/fusion-core-tasks#1657 * test(module-analytics): verify MockAnalyticsAdapter through the real module pipeline The existing unit tests only exercise MockAnalyticsAdapter's own logic in isolation. Add an integration test, mirroring the pattern used by msal and service-discovery's mock tests, that wires it through the real enableAnalytics -> ModulesConfigurator -> AnalyticsProvider.initialize() pipeline to prove events from provider.trackAnalytic and a real collector actually reach it, and that it doesn't interfere with other adapters. * docs(module-analytics): move testing docs to docs/testing.md, mirror in vue-press Adopt the docs/ convention already used by the event, http, and module packages: extract the README's inline Testing section into packages/modules/analytics/docs/testing.md, link to it from README, and add a vue-press mirror page (@include) with a sidebar entry. * docs(module-analytics): slim README to overview, split domain docs into docs/ Move Adapters, Collectors, and Tracking Events Manually out of the README into docs/adapters.md, docs/collectors.md, and docs/tracking-events.md, matching the event/http/module docs/ convention. Drops duplicate Configuration/Creating Custom Collectors content that had accumulated in the old README's Tracking Events Manually section. README is now a slim overview + entry points + documentation table + quick start, linking out to docs/ for the domain reference. Mirrors and sidebar entries added in vue-press. * fix(module-analytics): reject waitForAnalytic when a predicate matcher throws A throwing predicate matcher surfaced on RxJS's error channel, but the subscribe observer had no error handler — the exception was reported globally and the returned promise stayed pending forever. Add an error handler that rejects through the same cleanup path, and cover it with a test. * docs(module-analytics): fix broken abort and ModulesConfigurator examples in testing.md The abort example never called controller.abort() (so copying it hangs indefinitely) and redeclared `event` from the preceding example in the same code block. The bespoke ModulesConfigurator example destructured `analytics` from initialize() without a type — enableAnalytics only registers the module at runtime, so this doesn't typecheck as written; cast to IAnalyticsProvider, matching what the integration test does. * feat(azure-identity,msal-node): add MockAuthProvider test double for auth module (#5244) * feat(azure-identity,msal-node): add MockAuthProvider test double for auth module Add a configurable IAuthProvider test double, exported from a new /mock subpath in each package (@equinor/fusion-framework-module-azure-identity/mock, @equinor/fusion-framework-module-msal-node/mock). Unlike token_only mode's static-token providers (AuthProviderTokenOnly / AuthTokenProvider), MockAuthProvider actually implements login/logout, moving between signed-in and signed-out state, and exposes setAccount, setAccessToken and setExpiresOn so a test can control the returned token and simulate an expired token to exercise refresh logic. No real network calls are made. Implemented as two mirrored (twin) implementations rather than a shared package, since azure-identity's and msal-node's IAuthProvider interfaces differ in login's return type (AuthRecord vs. AuthenticationResult) and neither package depends on the other. Registers via enableAuthMock(configurator, configure?), a full 'auth' module replacement mirroring @equinor/fusion-framework-module-msal/mock's enableMsalMock, so no special-cased wiring is needed in either module. Closes equinor/fusion-core-tasks#1665. * fix(azure-identity,msal-node): make enableAuthMock generic over TModules/TRef Replaces the IModulesConfigurator<any, any> parameter with a generic <TModules extends Array<AnyModule> = Array<AnyModule>, TRef = unknown> signature, so the optional configure callback keeps the caller's actual TRef type instead of erasing it to unknown. Addresses review feedback on PR #5244 (copilot-pull-request-reviewer). * fix(azure-identity,msal-node): fix double-registration in mock README example The Testing section called enableAuthMock(configurator) once for its return value, then again with a configure callback to seed the account. The second call replaces the first provider (enableAuthMock always registers last), so the retained `auth` reference from the first call was no longer the active provider by the time login/acquireAccessToken ran against it. Collapse to a single enableAuthMock call that both seeds the account and returns the provider that stays registered. Addresses review feedback on PR #5244 (copilot-pull-request-reviewer). * feat(module-bookmark): add BookmarkMockClient and enableBookmarkMock (#5245) * feat(module-bookmark): add BookmarkMockClient and enableBookmarkMock Add a purpose-built mock for the bookmark module, exported from a new `/mock` subpath (`@equinor/fusion-framework-module-bookmark/mock`), following the client-boundary-swap pattern already used by msal's `enableMsalMock`. - `BookmarkMockClient`: in-memory `IBookmarkClient` backed by a Map of seeded bookmarks and a Set of favorite ids. - `BookmarkMockConfigurator`: extends the real `BookmarkModuleConfigurator` with `setBookmarks`/`setCurrentBookmark`/`setFavorite`, and falls back to trivial application/context resolvers when no real `app`/`context` module is registered (both are required by the config schema). - `enableBookmarkMock`/`bookmarkMockModule`: module descriptor mirroring the real module, only swapping `configure`. Create, update, delete, and favorite calls all flow through the real `BookmarkProvider` and `bookmark-flows/` epics unmodified. Related: equinor/fusion-core-tasks#1667 * fix(module-bookmark): match sourceSystem, strip list payloads, normalize null payload in mock client - matchesFilter now checks filter.sourceSystem (identifier/name/subSystem), since BookmarkProvider.getAllBookmarks always passes sourceSystem through the filter. - getAllBookmarks now strips payloads from returned bookmarks, matching the real BookmarkClient's list behavior. - updateBookmark now normalizes an explicit payload: null clear to undefined, matching setBookmarkData and avoiding a later getBookmarkData() emitting null as T. Addresses PR #5245 review comments on BookmarkMockClient.ts:44, :140, :270. * fix(module-bookmark): respect inherited resolve.application/context on mock fallback Gate the mock's fallback application/context resolvers on initial?.resolve as well as this._has(...), so a nested mock configurator does not overwrite resolvers already inherited from a parent config. Addresses PR #5245 review comment on BookmarkMockConfigurator.ts:150. * test(module-bookmark): assert getBookmark rejects for an unknown id Adds a Vitest assertion exercising the async error path through the real BookmarkProvider.getBookmark fetch flow, so a regression that stops missing ids from surfacing as observable errors would fail this suite. Addresses PR #5245 review comment on BookmarkMockClient.ts:125. * docs(module-bookmark): add required created/createdBy fields to mock seed example The README's setBookmarks example omitted created/createdBy, which are required on Bookmark, so copying the snippet as-is produced a type error. Addresses PR #5245 review comment on README.md:245. * docs(module-bookmark): add required created/createdBy fields to changeset example The changeset's setBookmarks example omitted created/createdBy, which are required on Bookmark, so the advertised snippet did not type-check. Addresses PR #5245 review comment on .changeset/module-bookmark_mock-entry-point.md:15. * refactor(module-context): extend BaseConfigBuilder and reorganize internal files - ContextModuleConfigurator now extends BaseConfigBuilder (createConfigAsync), matching the pattern used by module-event/module-http/module-msal - rename configurator.ts -> ContextModuleConfigurator.ts, split its exported types into ContextModuleConfig.ts and ContextModuleConfigurator.interface.ts - move get/query/related-context-selector.ts into src/selectors/ - move parse-context-item.ts into src/utils/ and export it from utils/index.ts - delete unused ContextConfigBuilder.ts - add ContextModuleConfigurator.test.ts covering the new builder surface No public export names change; index.ts still exports the same ContextModuleConfigurator/IContextModuleConfigurator/ContextModuleConfig. * feat(module-context): add mock and mock/fixtures entry points Add ./mock (ContextMockConfigurator, enableContextMock) — a ContextModuleConfigurator backed by an in-memory pool instead of a real client. Only the data source is substituted; validateContext, resolveContext, and parent-context propagation still run through the real ContextProvider. Friendly seeding methods (setCurrentContext/setContexts/addContext/ setRelatedContexts) cover the common cases; setResolver is the escape hatch. Add ./mock/fixtures (createContextItems, createContextItemFactory) — realistic ContextItem generators backed by an optional @faker-js/faker peer dependency, kept on a separate entry point so enableContextMock never pulls faker in. Adds an http workspace dependency (for the vitest project setup) and a package-level vitest.config.ts/test script. * docs(module-context): restructure README into docs/ and mirror in vue-press Move long-form content out of README.md into docs/data-model.md (ContextItem/ ContextItemType shape, query/related parameter types), docs/lifecycle.md (setting/resolving context, initial-context resolution, parent/child propagation) and docs/recipes.md (OData query params, path rewriting, accepting related context type families, skipping initial-context lookup, custom search errors), matching the module/http convention. README.md keeps the elevator pitch, a new "How it fits together" section covering the module's dependency graph (services required, event/navigation optional and auto-detected) and hierarchy-aware parent connection, and a documentation table linking to the three new pages. vue-press/src/modules/context mirrors the split with @include stubs, and the sidebar gains an Overview/Data model/Lifecycle/Recipes breakdown for Context. * feat(framework): back mock context accessor with ContextMockConfigurator FrameworkMockConfigurator.context now pins contextMockModule (from @equinor/fusion-framework-module-context/mock) instead of the real context module, so `.context` seeding methods (setCurrentContext/setContexts/ addContext/setRelatedContexts/setResolver) work the same way `.http` and `.serviceDiscovery` already do — no more real I/O through context in tests. docs/testing.md, docs/testing-extending.md and docs/testing-api.md document both context mocking strategies: the in-memory ContextMockConfigurator above, and mocking the context API's HTTP responses directly for tests that need to exercise the real configurator/services/HTTP pipeline. * chore: add changesets for context mock entry point and docs restructure * fix(module-context): document createConfig as a breaking change Extending BaseConfigBuilder changes the public createConfig method from Promise<ContextModuleConfig> to Observable<ContextModuleConfig>. Bump the changeset to major with migration guidance to createConfigAsync, matching the precedent set when service-discovery/msal adopted the same base class. Addresses review comment on PR #5243 (discussion_r3744095539). * docs(module-context): clarify setResolver only affects id-based lookup setResolver overrides ContextClient.resolveContext(id) (used by setCurrentContextById and the mock's initial context), not ContextProvider.resolveContext(item), which always resolves related context through setRelatedContexts regardless of setResolver. Reword the class and method docs so the two same-named methods aren't conflated. Addresses review comment on PR #5243 (discussion_r3744095551). * test(framework): assert seeding via .context reaches the built framework The existing test only checked that .context is defined, which also passed with the real ContextModuleConfigurator. Add a case that seeds via configurator.context.setCurrentContext(...), initializes the framework, and asserts fusion.modules.context.currentContext, covering the _pin(contextMockModule) composition path. Addresses review comment on PR #5243 (discussion_r3744095562). * feat(module-telemetry): add MockTelemetryAdapter and wire it into FrameworkMockConfigurator Closes equinor/fusion-core-tasks#1708 Adds a collecting MockTelemetryAdapter + TelemetryMockConfigurator behind a new ./mock subpath, following the MockAnalyticsAdapter shape. Wires FrameworkMockConfigurator.telemetry to the mock configurator so telemetry tracked through a mocked framework instance never reaches Application Insights or any real endpoint. * fix(module-telemetry): close waitForItem resource leaks and simplify changeset example * feat(module-feature-flag): add mock entry point for seeding test feature flags Add `FeatureFlagMockConfigurator`, `enableFeatureFlagMock`, and a `./mock` subpath so a test can seed flags in-memory via `addFeature`/`setFeatures`, with no `localStorage`/URL dependency. Toggling still runs through the real `FeatureFlagProvider`. Also fixes a missing `telemetry` row in the framework's testing.md accessor table (pre-existing gap, unrelated to this change). Closes equinor/fusion-core-tasks#1707. * test(module-feature-flag): assert async configure callbacks are awaited before initialize resolves Addresses review comment on module.ts:49. * docs(module-feature-flag): document the ./mock subpath in the README's API Reference Addresses review comment on package.json:23. * chore: add changeset for the testing.md telemetry accessor fix Addresses review comment on testing.md:30. * refactor(module-http): replace router-based mock with addMiddleware on the real configurator Closes equinor/fusion-core-tasks#1706 Replaces enableHttpMock/HttpMockConfigurator/HttpMockRouter with addMiddleware on the real HttpClientConfigurator, via a new HttpMiddlewareHandler that wraps _performFetch instead of swapping the configurator out. A middleware calling next(uri, init) falls through to the real call (or the next middleware) unchanged, so a test exercises the exact same client and configuration a real app registers. createRouterMiddleware and createOpenApiMockMiddleware replace the old router and its Express-style adapters, covering the same handler/OpenAPI-fake cases without depending on a real routing library. * docs(module-http): mirror the testing guide into vue-press Adds the http module's docs/testing.md via the same @include pattern used by client-configuration.md and server-sent-events.md, with a matching sidebar entry. * fix(framework): pin FrameworkMockConfigurator.http to the real HttpClientConfigurator FrameworkMockConfigurator.http returns the real IHttpClientConfigurator now that module-http's mock support is addMiddleware on that real configurator, not a separate mock-specific one. Fake a response with .http.addMiddleware(...) instead of swapping the module out. Updates testing.md/testing-api.md/testing-extending.md to match. * test(module-context): migrate off the removed enableHttpMock API enableHttpMock/http.get(...) no longer exist; rebuilds the fixture with configureHttp + createRouterMiddleware against configurator.addConfig, matching the addMiddleware-based mocking module-http now exposes. Updates the README's mention of the HTTP-mock alternative to match. * feat(module-app): add MockAppClient mock entry point Part of equinor/fusion-core-tasks#1706 Adds MockAppClient, exported from a new ./mock subpath, so a test can serve one app's own manifest and config locally instead of contacting the app service: - getAppManifest resolves locally only for manifest.appKey with no tag. - getAppConfig resolves locally for manifest.appKey when tag is absent or equal to manifest.build?.version. - Every other request still goes through the real AppClient it wraps. Also exports AppConfig as a value from the package root (previously type-only), so a test can construct one directly. * feat(app): add mockAppModules and AppMockConfigurator /mock entry point Closes equinor/fusion-core-tasks#1706 Adds a ./mock entry point: mockAppModules runs an application's real module pipeline — the real event/http/msal modules, the real AppConfigurator configuration pipeline and real lifecycle — against a mocked parent Fusion instance, so a test exercises the wiring an application actually depends on instead of a reimplementation of it. AppMockConfigurator extends the real AppConfigurator, pinning http and msal to the same test doubles FrameworkMockConfigurator uses so .http/.msal are reachable synchronously; event is deliberately left unpinned since its configure factory needs a real ref. Its own addConfig override redirects a pinned module's later registrations at the pinned descriptor. The base AppConfigurator constructor's own call into _configureHttpClientsFromAppConfig is deferred (via a no-op override + an explicit super call after pinning) so it never runs before this class's own fields exist. enableAppManifestMock registers the app module on a parent mockFramework configurator, serving one app's manifest/config locally while delegating everything else to service discovery. mockAppModules uses it to build its zero-configuration default parent. Restructures README.md into an entry point pointing at docs/http-clients.md, docs/bookmarks.md and docs/testing.md, matching the convention already used by module-http and module-msal. * fix(app): remove dead initialize-app-configurator.ts duplicate initialize-app-modules.ts superseded this file when initializeAppModules gained the TConfigurator type parameter, but the old file was left in place -- nothing imported it, so it was dead code that still compiled and still linted (fusion-lint's require-tsdoc flagged it for the missing @template tags the newer file already has). * style: apply biome formatting fixes Long import lists wrapped onto multiple lines, a ternary split across lines, and a single-quoted string containing an apostrophe rewritten with double quotes -- no behavior change. * refactor(module-http): flatten mock/adapters into mock/, fix lint warnings mock/ only ever had one barrel (index.ts) plus an adapters/ subfolder holding the 3 files it re-exported -- two barrels doing the same job for exactly one category of file. Moved create-open-api-mock-middleware.ts, create-router-middleware.ts and resolve-open-api-mock-response.ts up into mock/ directly and dropped the adapters/ barrel, fixing relative import depths and OpenApiMockLike's re-export path along the way. Also fixes the fusion-lint warnings the files carried before the move: renamed each file to match its exported symbol (filename-convention), added the missing intent comments on create-router-middleware.ts's if-blocks and .map() (require-intent-comment), and replaced a comma-operator expression in compilePath with an explicit if/return (noCommaOperator). * fix(module-app): treat only an absent tag as 'no tag' in MockAppClient getAppManifest's !args.tag and getAppConfig's isOwnTag both treated an explicit empty-string tag the same as an absent one, so getAppManifest({ appKey, tag: '' }) and getAppConfig({ appKey, tag: '' }) answered locally instead of delegating, contradicting the documented 'tag is absent' rule. Compare against undefined explicitly instead of a falsy check. Adds MockAppClient.test.ts covering both methods' local-vs-delegate decision, including the explicit-empty-string-tag case. * docs(vue-press): add changeset for the http module testing page The new vue-press page and sidebar entry is a consumer-facing docs change to the published @equinor/fusion-framework-docs package, which needed a changeset like every other vue-press page addition in this repo. * chore(changesets): bump http mock changesets to major enableHttpMock, HttpMockConfigurator, and HttpMockRouter were removed from @equinor/fusion-framework-module-http's /mock entry point, and FrameworkMockConfigurator.http's return type changed from a mock-specific configurator to the real IHttpClientConfigurator -- both are breaking changes for existing consumers and need a major bump, not minor. * docs(app): correct mockAppModules' network-access claim The http module registered by AppMockConfigurator is the real HttpClientConfigurator -- addMiddleware only short-circuits requests a middleware matches with a response, so an unmatched client or a middleware that calls next still reaches the real network. msal is the only module genuinely backed by a test double. Reworded the doc comment to describe what is actually substituted instead of promising no network access. * fix(module-http): abort the real network call through middleware on abort() next(...) resolves through a Promise (HttpMiddlewareHandler's toPromise), so a middleware calling it creates a subscription to _performFetch that sits outside the tree takeUntil(this._abort$) tears down -- the outer request settled on abort(), but the underlying fetch kept running. abort() now also aborts a per-request AbortSignal combined into the request init, so _performFetch (fromFetch by default) is cancelled directly regardless of whether middleware severed the RxJS teardown chain. Adds a regression test with a pass-through middleware asserting the underlying fetch's signal is aborted. * test: add react-app hook testing utilities and msal proxy/hoisting coverage - add render-app-hook testing utility + useAccessToken hook test in react/app - add msal-hoisting coverage in packages/app - add createProxyProvider behavior coverage in modules/msal * test(react-app): add useTrackFeature hook tests Add @equinor/fusion-framework-module-telemetry as a devDependency so tests can read the mock telemetry adapter via fusion.modules.telemetry.getAdapter('mock'). * test(react-app): render Apploader against a real dynamic-import child app script Add renderAppComponent, a component-level counterpart to renderAppHook, and use it in a new Apploader.test.tsx that exercises the real useApploader -> App.initialize() -> dynamic import() pipeline (no vi.mock of createApp or the import mechanism), backed by a fixture child app script. * test(react-app): add useCurrentBookmark hook tests Covers the app-scoped happy path, filtering out a bookmark that belongs to a different app, and the framework-scoped fallback with its deprecation warning. Adds @equinor/fusion-framework-module-bookmark as a devDependency for the mock helpers. * test(react-app): add useCurrentContext hook tests Covers resolving the seeded initial context and switching to another seeded item via setCurrentContext, against the app-scoped context module mock. Adds @equinor/fusion-framework-module-context as a devDependency for the mock helpers. * test(react-app): add useFeature hook tests Covers resolving an app-scoped flag, app-over-framework override on a shared key, a framework-only flag surfacing through the merged stream, and toggleFeature inverting the current value with no explicit argument. * test(react-app): add useHelpCenter hook tests Covers every open* action, asserting the framework event dispatched on the app's event module carries the expected page and payload. * test(react-app): add useToken and useCurrentAccount hook tests Completes coverage of the msal folder alongside the existing useAccessToken tests: useToken's full AuthenticationResult happy/error paths, and useCurrentAccount against the default mock user, a custom msal.setAccount() override, and the signed-out case. * test(react-app): add useAppSetting and useAppSettings hook tests Covers the settings folder's public hooks against the real fetch/update round-trip (via an in-memory apps-client mock): resolving persisted values, default-value fallback before resolution, persisting an update (direct value and updater-function forms), and surfacing a persistence failure through onError instead of throwing. useAppSettingsStatus is internal (not exported from settings/index.ts) and is exercised indirectly through these onLoading/onUpdating-adjacent paths. * docs(react-app): fix HTTP config example and nullable/async return types Fixes issues found while writing hook tests: README/createComponent examples used configurator.http.configureClient (test-only mock API) instead of the real configurator.configureHttpClient; setCurrentContext/useFrameworkCurrentContext were documented as void-returning when they actually return void | Promise<ContextItem | null>; AuthenticationResult.account/expiresOn were documented as non-nullable; useCurrentBookmark's deprecated framework-scope fallback and its console warning were undocumented. * docs(react-app): add app testing guide * fix(react-app): unmount rendered hooks in msal token tests Prevents the async token-acquisition effect from flushing a state update after happy-dom's window is torn down at the end of the test file, which CI surfaced as an uncaught 'window is not defined' exception (all assertions still passed, but the run was marked failed). * test: apply Biome line-length formatting suggestions Collapse two waitFor assertions that fit within the line-length limit back onto a single line, per Biome/reviewdog suggestions on PR #5271. * fix(app): treat empty-string assetUri as an explicit override if (assetUri) silently ignored a caller-supplied empty string, which is a meaningful value (selects a root-relative script path). Check against undefined instead so the override always applies when the caller passes the option. * docs(react-app): clarify network-boundary faking scope; add error-state example The docs overstated that renderAppHook/renderAppComponent fake the entire network boundary. Only requests a seeded router middleware answers are faked; unmatched requests still reach the real network. Also documents renderAppComponent under the /testing subpath table, and adds an error-state test example mirroring Apploader.test.tsx. * docs(react-app): remove invalid @template Result tag from renderAppComponent renderAppComponent has no Result type parameter; the tag referenced a generic that does not exist on this function and would fail TSDoc validation. * test(react-app): assert the seeded bookmark actually resolved before checking the app-key filter The previous waitFor(() => expect(result.current).toBeDefined()) was a no-op since result.current is always a defined object. Now waits on the unfiltered useAppModules().bookmark provider to resolve the seeded bookmark first, so the assertion that useCurrentBookmark hides it can't pass trivially against a still-pending value. * docs(changeset): qualify cross-repo issue reference Closes #1716 resolved to an unrelated PR in this repo. Qualifying as equinor/fusion-core-tasks#1716 points at the correct issue while preserving auto-close-on-merge. * test: apply Biome line-length formatting suggestions Reformat two msal-hoisting.test.ts call expressions and two useCurrentContext.test.tsx object literals that exceed the line-length limit, per Biome/reviewdog suggestions on PR #5271. * chore(deps): bump vitest to ^4.1.10 Bumps vitest and @vitest/coverage-v8 across the workspace, and migrates vitest.config.ts package.json imports to the `with { type: 'json' }` import attribute required by the newer vitest/vite toolchain. * feat(cli): add `ffc app test` command Runs the application's own Vitest suite with manifest/config/module-configurator resolved the same way `ffc app build`/`ffc app dev` do, exposed via a Vite plugin's virtual modules to @equinor/fusion-framework-react-app/vitest's test/render fixtures. Renamed packages/cli/src/lib/testing -> lib/vitest and the ./testing export -> ./vitest. * Update packages/cli/src/cli/commands/app/test.command.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix(cli): throw on an explicit but missing --configure path An explicitly supplied configure path is a user request, not a convention lookup — a typo should fail loudly instead of silently running the test suite without the app's modules. * refactor: clean up code formatting for better readability * feat(react-app): vitest render/test-app fixtures under src/vitest - rename src/testing -> src/vitest, add test-app/scope fixture chain (test-app.tsx, test.tsx, scope/*) for rendering app components and hooks with a fully wired Fusion test environment - fix(react): useFrameworkModule warns with the correct module name - fix(module-context): stop warning when there's no initial context to resolve - fix(module-msal): drop stray debug console.log in resolve-version - fix(log): guard process.env access for Vitest Browser Mode - chore: biome format fixes surfaced by pnpm check (unrelated pre-existing drift in cli/linting/event packages) * refactor: extract react-app vitest helpers into vitest-plugin-react-app Move renderAppHook/renderAppComponent/testApp and the app-test Vite plugin out of @equinor/fusion-framework-react-app's ./vitest entry-point and @equinor/fusion-framework-cli's ./vitest entry-point into a new standalone package, @equinor/fusion-framework-vitest-plugin-react-app, built on vitest-browser-react instead of @testing-library/react. - Remove the ffc app test CLI command and cli/src/lib/vitest (superseded by appTestVitePlugin registered directly in a project's vitest.config.ts). - Add packages/vitest-plugin/react-app with README, appTestVitePlugin, renderAppHook, renderAppComponent, testApp, and a /test entry-point exporting pre-seeded test/render fixtures. - Update react-app's README/docs and package.json to drop ./vitest and point at the new package instead. - Fix stray console.log in msal's resolveVersion mismatch warning, and guard fusion-log's process.env access for environments without a Node process global (Vitest Browser Mode). - Install Playwright browsers in CI for the new package's and react-app's Vitest Browser Mode tests. - Add CODEMAP.md entry for the new package. Changesets included for all affected packages. * ci: fix Playwright install invocation to filter into a package that depends on it pnpm exec at the repo root only resolves bins from root's own node_modules, and playwright is a devDependency of react-app/vitest-plugin-react-app only, not the workspace root - the previous 'pnpm exec playwright install' failed with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL / 'Command "playwright" not found'. * fix(vitest-plugin-react-app): declare vitest as a peer dependency The package uses vitest's expect/vi APIs at test-authoring time but only listed it as a devDependency, so consumers weren't told to install it themselves. * fix(vitest-plugin-react-app): correct tsconfig project reference src/index.ts and src/resolve-app-test-env.ts import @equinor/fusion-imports (utils/imports), but the tsconfig referenced utils/observable, an unrelated package. * test(vitest-plugin-react-app): cover resolveAppTestEnv and appTestVitePlugin The package's own resolution/virtual-module logic had no test coverage and no vitest.config.ts, so it wasn't even picked up as a project by the root Vitest run. * docs: add missing changeset for CLI ./vitest entry-point removal The removal of ffc app test, testApplication, the ./vitest entry-point, and resolveAppTestEnv from @equinor/fusion-framework-cli had no changeset. * fix(module-context): make telemetry an optional peer dependency The context module only reads telemetry defensively via an injected instance (never imports the package directly), matching the optional pattern already used by modules/msal. * docs(vitest-plugin-react-app): fix quick-start snippet and fixture wording Add the missing 'import { expect } from vitest' to the quick-start test snippet, and reword the fixture-sharing bullet to clarify only seeded defaults are shared, not fusion/app state between tests. * docs(vitest-plugin-react-app): clarify testApp fixture-sharing wording Match the README wording fix: testApp's JSDoc previously implied one shared mocked scope across tests, when each test actually gets fresh fusion/app instances and only the seeded defaults are shared. * fix(react-app): remove stale vitest-browser-react peer dependency vitest-browser-react was only used by the now-removed ./vitest entry-point; src/ has no remaining references to it, so it stays a devDependency only. * test(module-context): cover telemetry tracking for initial-context resolution Adds coverage for the resolver-error and successful-resolution telemetry events raised by module.ts's postInitialize, alongside the existing console.warn-fallback test for when no telemetry module is registered. * test(react-app): add useToken rerender regression test Asserts acquireToken is not re-invoked when scopes are re-rendered with the same contents (a fresh array literal each time), but is re-invoked when the contents actually change. * refactor(tests): format code for better readability in useToken and ContextModuleConfigurator tests * feat(vitest-plugin-react-app): add defineProject config helper, fix entrypoint inference - New `/config` entry-point exporting defineProject: a drop-in for Vitest's own defineProject, pre-wired with appTestVitePlugin and the @vitest/browser-playwright/chromium provider. - appTestVitePlugin now infers entrypoint from the Vitest project's own root (via configResolved) instead of process.cwd() when none is given. - @vitest/browser-playwright and playwright are now explicit peer dependencies. * fix(module): keep addConfig additive when the same module descriptor is re-registered ModulesConfigurator.addConfig discarded previously registered configure/afterConfig/ afterInit callbacks whenever it was called again for an already-registered module name, even when the descriptor itself was the exact same object. This broke config functions that register more than one named client against a shared module singleton, e.g. calling configureHttpClient/useFrameworkServiceClient more than once from @equinor/fusion-framework-module-http - only the last call's client survived; earlier ones threw "No registered http client for key [...]" at createClient() time, with no error at configuration time. addConfig now only discards previous callbacks when a genuinely different module descriptor replaces the old one for that name; re-registering the same shared descriptor stays additive. * feat(openapi-mock): split loadFakerMap into a Node-only /node entry point loadFakerMap reads sidecar files from disk (node:fs) and, for a .ts/.js sidecar, shells out to esbuild via @equinor/fusion-imports' importConfig - neither of which exists in a browser (or browser-mode Vitest) runtime. Move it to a separate @equinor/fusion-openapi-mock/node entry point (src/node.ts) so importing the package's main entry never pulls Node-only code into a browser bundle. A fields map built in code, or loaded ahead of time some other way, still works with createOpenApiMock from the main entry point regardless of runtime. Also bumps the read-pkg pnpm override (10.0.0 -> 10.1.0) picked up while resolving this package's dependency tree. * fix(vitest-plugin-react-app): warm up all source to avoid mid-test Vite reload defineProject only transformed source as each test file requested it, so a route component reached solely through a lazy/code-split import (e.g. React.lazy or a route-DSL component path) could be discovered mid-run instead of up front - Vite then reloads the page to pick it up, which fails the in-flight test file's import. Set server.warmup.clientFiles: ['src/**/*.{ts,tsx}'] so all source under src/ is pre-transformed before the first test request. * chore: exclude cookbook test files from AI indexing, exempt generators.ts from single-export lint - fusion-ai.config.ts: exclude cookbooks/**/__tests__/** and *.test|spec.{ts,tsx} from both the patterns and rawPatterns indexes - test files aren't consumer-facing API surface and were polluting cookbook search results. - fusion-lint.config.json: add generators.ts to single-export-per-file's excludePattern, matching the existing convention for deliberately multi-export utility filenames (utils.ts, helpers.ts, options.ts, etc). * test(cookbook-app-react): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the App component via renderAppComponent. - Root vitest.config.ts now globs cookbooks/**/vitest.config.ts. * test(cookbook-app-react-context): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers seeded initial context, related-context items, and switching context via the app's own context module ref. * test(cookbook-app-react-feature-flag): add Vitest coverage via vitest-plugin-react-app - FeatureFlag/FeatureFlags moved into src/components/, useFeatureLogger moved into src/hooks/, each alongside its own vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - FeatureFlag.test.tsx / FeatureFlags.test.tsx cover rendering and toggling. * test(cookbook-app-react-msal): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the default signed-in mock user, overriding configure with a custom mock account, and resolving/rendering an access token. - Removes the unused demo src/config.ts (dev-only console logging). * test(cookbook-app-react-router): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - Component and route-page tests via testWithRouter/createRouteProps, with fixtures built from seeded Faker.js generators (src/mocks/generators.ts) instead of hand-written literals. - src/mocks/{openapi.json,fields.faker.ts,api-mock.ts} + testWithApiMock wire an OpenAPI-mock-backed HTTP middleware for full-router integration tests (routing-with-api-mock.test.tsx) with deterministic, schema-shaped responses. - README.md documents the testing approach (Testing section). * test(cookbook-app-react-module): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the demo module's resolved foo/bar values, and overriding configure to seed a different demo configuration. - fix(config.ts): setBar(() => 69) returned a plain number, not a valid ObservableInput, so BaseConfigBuilder silently discarded it and bar fell through to the module's own 10-second delayed default (5) instead of 69. setBar(async () => 69) matches setFoo's existing pattern and resolves immediately. * style: apply biome format/lint fixes repo-wide Runs `biome check --write --unsafe .` across the whole repo: - Wraps long lines exceeding the configured line width (mostly test files with chained assertions/JSX props). - cookbooks/app-react/src/config.ts: prefixes 3 intentionally-unused callback parameters (env, config, instance) with `_`, per lint/correctness/noUnusedFunctionParameters. No behavioral changes. * test(vitest-config): include coverage tracking for packages only * docs(cookbook-app-react-msal): clarify config.ts removal has no functional impact Corrects the test's doc comment, which claimed composing with a real configure that no longer exists, and documents the intentional removal in the changeset instead of silently dropping the note. Addresses review comment on PR #5293 (discussion_r3778886987). * docs(cookbook-app-react): document silenced lifecycle console.log in changeset The demo console.log calls were intentionally left commented out to keep Vitest output clean; the changeset previously claimed 'no runtime changes', which wasn't accurate. Documents the behavior change instead of restoring the logging. Addresses review comment on PR #5293 (discussion_r3778887017). * fix(cookbook-app-react-router): fix faker.date.past() with unfixed refDate faker.seed() only makes the pseudo-random offset deterministic; without a fixed refDate, date.past() still resolves relative to the current wall-clock time, so generated joinDate values drift across days despite the seeded-fixture contract. Adds a shared fakerRefDate constant used by both generateUser and the User.joinDate field faker. Addresses review comments on PR #5293 (discussion_r3778887040, discussion_r3778887110). * docs(cookbook-app-react-context): fix changeset API names The changeset named renderAppComponent and renderAppComponent.fusion.app, neither of which exists in the added tests; they use the plugin's render/app fixtures instead. Addresses review comment on PR #5293 (discussion_r3778887093). * fix(cookbook-app-react-router): add missing tsconfig project reference Adds a reference to packages/vite-plugins/raw-imports matching the new workspace dependency in package.json, so isolated package builds resolve it correctly. Addresses review comment on PR #5293 (discussion_r3778887191). * fix(turbo): include bin/build in cli package's cached build outputs The build task only declared dist/** as cacheable output, but packages/cli's rollup build writes its bundled CLI binaries to bin/build/*.mjs (gitignored, not under dist/). On a turbo cache hit in CI, bin/build/ was never restored, so any cookbook invoking the fusion-framework-cli binary during its own build failed with ERR_MODULE_NOT_FOUND for bin/build/cli.mjs. Reproduced locally by deleting bin/build and re-running turbo build without --force (cache hit skipped restoring it); fixed by adding bin/build/** to outputs. * test: migrate React tests to Vitest browser * docs(testing): publish Fusion app testing guide (#5296) * docs(testing): add canonical package guidance * feat(docs): map package links in VuePress * docs(testing): publish the app testing journey * fix(docs): repair internal documentation links * test(module-state): use browser PouchDB adapter * test: decouple browser test projects * fix(docs): scope package link rewrites * Refactor code structure for improved readability and maintainability * fix(pnpm): remove duplicate '@remix-run/router' entries from lockfile * fix(define-project): enhance dependency optimization for esbuild scanner * refactor(memfs-plugin): move mocks for fs/promises and import-script to the top for better organization * docs(advanced): document `test.override(...)` for fixture overriding in test files * chore: drop spurious react-app major changeset; ./vitest never shipped * chore: drop spurious cli major changeset; ffc app test never shipped * chore: fix two more false-major changesets; mock infra never released * Remove coverage configuration from vitest.config.ts * feat: add migration guides for transitioning to Fusion Vitest and explain Browser Mode default * test(cookbook-app-react-router): await styling before asserting active link color * style(vue-press): add missing trailing newline in vitest config --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
odinr
added a commit
that referenced
this pull request
Aug 16, 2026
…kbooks (#5297) * feat(openapi-mock): add package for generating OpenAPI-shaped fake responses Adds @equinor/fusion-openapi-mock, a framework-agnostic utility that fakes OpenAPI 3 responses straight from a parsed spec document. Every operation with an operationId is faked from its declared success response schema, with overrides/register for edge cases, seed for repeatable output, and a FieldFakerMap for realistic per-field values via @faker-js/faker. No dependency on any HTTP or routing framework: resolve({ method, path, query }) returns a plain { status, mock }, so it composes with @equinor/fusion-framework-module-http's mock router or any other server. Related: equinor/fusion-core-tasks#1660 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(deps): update lockfile for @equinor/fusion-openapi-mock * fix(openapi-mock): address review feedback on route matching, response fallback, and validation - sort operations by specificity so literal routes win over templated ones - fall back to lowest declared numeric status when no 2xx/default response exists - validate parsed YAML faker maps and fetched documents are plain objects - use an isolated faker instance per generation instead of mutating the global singleton - refactor createOpenApiMock and apply-field-fakers into smaller, focused functions * refactor(openapi-mock): favor readability and avoid mutable locals - replace && shorthand and let-based branches with explicit if/early-return - extract resolveResponseSchema, parseDocumentText helpers - rewrite resolvePointer's pointer walk with Array#reduce instead of a mutable loop variable * feat(module-msal): add a mock entry point for testing without credentials or network Adds ./mock, so applications can run against the auth module without credentials or network access: import { enableMsalMock, createMsalMockClient } from '@equinor/fusion-framework-module-msal/mock'; enableMsalMock(configurator, (builder) => { builder.setAccount({ name: 'Ada Lovelace' }); }); Only the MSAL client is substituted: - MsalMockClient resolves tokens in-process and takes the same MsalClientConfig as the real MsalClient, keeps a real account cache keyed by homeAccountId (matching MSAL's getAccount/getAllAccounts semantics), and produces structurally valid, unsigned JWTs. - The client construction seam (_createClient/_createClientConfig) is extracted from MsalConfigurator._processConfig, giving a supported seam for authenticating through something other than Entra ID. No client is built when the module is hoisted onto a host application's provider, since a client built during configuration would be discarded or shadow the host's signed-in user (_isHoisted). - MsalMockConfigurator.setAccount lets a test declare the signed-in user on the builder; the account is applied before MsalProvider initializes, so the provider's own start-up path (including automatic login when signedOut + requiresAuth) acts on it. - The config schema moved into MsalConfig.schema.ts with MsalConfigExtension, an extension point mock configuration merges into without the builder needing to cast past its type. MsalProvider, MsalConfigurator and IMsalProvider are otherwise untouched; the mock module's configure differs from the real module, initialize does not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(module-http): add a mock entry point with middleware and adapters Adds @equinor/fusion-framework-module-http/mock, so HTTP clients answer requests from registered route handlers instead of the network: import { enableHttpMock } from '@equinor/fusion-framework-module-http/mock'; enableHttpMock(configurator, (builder) => { builder.configureClient('catalog', { baseUri: 'https://api.example.com' }); builder.get('/items', () => Response.json([{ id: 1 }])); }); Route handlers are Fetch-standard middleware - (request: Request) => Response | undefined | Promise<...> - tried in registration order, with undefined falling through to the next one. One router is shared across every named client a HttpMockConfigurator builds, matched against each request's fully resolved URL. HttpClient gains a _performFetch(uri, init) seam, isolated from _fetch$ so a test double replaces only the network call while request preparation, the response pipeline, and abort handling run unchanged. Two adapters drop in an existing backend with no hard dependency on it: - fromExpressStyleHandler(handler) adapts an Express-style (req, res) handler (or a whole framework built from them, like openapi-backend). - fromOpenApiMock(openApiMock) adapts an @equinor/fusion-openapi-mock instance (duck-typed, no package dependency), so a real openapi.json/openapi.yaml fakes every response with no handlers written at all. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(module-service-discovery): add a mock entry point backed by an in-memory registry Adds @equinor/fusion-framework-module-service-discovery/mock, so service discovery resolves from an in-memory registry instead of the network: import { mockServiceDiscovery } from '@equinor/fusion-framework-module-service-discovery/mock'; mockServiceDiscovery(configurator, { services: [{ key: 'apps', uri: 'https://apps.test' }] }); ServiceDiscoveryMockConfigurator builds the registry on the builder itself - setBaseUri, addService, addServices, removeService, setServices, setResolveUnknownServices - and the client is constructed from that registry when the module builds its config, so a test never has to construct a client just to add a service. setBaseUri also lets the default service endpoints point at a local mock server such as http://localhost:3000, so an application can make real HTTP calls against Mockoon, Prism or the dev server without a service worker intercepting requests. Exposes the discovery client on the provider as .client, mirroring MsalProvider.client, so a test can spy on it directly: vi.spyOn(fusion.modules.serviceDiscovery.client, 'resolveService').mockResolvedValue(service); configureServiceDiscovery now also accepts a synchronous callback - the underlying builder always allowed it, only the exported type required a promise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(module): fix configurator phase ordering and dot-path optional branches Two independent fixes needed by the framework mock work: - Module re-registration now replaces the prior module's configure, afterConfig, and afterInit callbacks instead of appending to them, keyed by module name (_dedupeModulesByName / _removeModuleCallbacks). This prevents stale callback execution when a mock module (e.g. enableMsalMock) overrides a real module registration, and fixes configurator phases running out of order or skipping post-configure hooks in certain initialization paths. - DotPath now unwraps an optional object property with NonNullable the same way DotPathType already did, so a path BaseConfigBuilder understands is one _set no longer refuses. Given { foo?: { bar: string } }, 'foo.bar' is now a valid path, matching 'foo' which was already allowed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(framework): add a mock entry point that composes each module's own test double Adds @equinor/fusion-framework/mock for initializing the framework in a test: import { mockFramework } from '@equinor/fusion-framework/mock'; const fusion = await mockFramework((configurator) => { configurator.msal.setAccount({ name: 'Ada Lovelace' }); configurator.serviceDiscovery.setBaseUri('http://localhost:6669'); }); mockFramework runs the real configure -> initialize pipeline with the real built-in modules and substitutes only the boundaries that leave the process - the MSAL client and the service discovery client. Module wiring, configuration validation and lifecycle hooks behave as they do in production. FrameworkMockConfigurator gains _pin/_getConfig, so a module supplied through TModules gets the same kind of named accessor .msal and .serviceDiscovery already have, rather than that pinning being hand-written once per built-in mock. .services, .context and .telemetry are exposed the same way, though they still perform real I/O until those modules have a test double of their own. event is intentionally left out - its configure factory reads ref to wire event bubbling to a parent event provider when hoisted, and pinning it would call configure() with ref always undefined, silently disabling that bubbling. The entry point owns no mock logic of its own: each module exports its own test double from its own ./mock entry point, and this one only composes the built-in set. It has no test-runner dependency and provides no mocking API, since replacing an individual call belongs to the test runner. init() no longer throws when no DOM is present - the window.Fusion assignment (for portal shells and widgets) is now skipped when window is undefined, so a test runner using the node environment or a server-side render no longer fails with 'ReferenceError: window is not defined'. Also restructures documentation so each README is an entry point rather than a manual: long-form content (including each package's testing.md) moved into per-package docs/ folders, matching the convention already used by @equinor/fusion-framework-module and @equinor/fusion-framework-module-http. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review comments on PR #5205 - msal-config-schema: reject unparseable version instead of coercing to "null" - HttpMockRouter: reset RegExp lastIndex so global/sticky match patterns are reusable across requests - extract decodeJwtSegment into its own file, fix atob/UTF-8 round-trip for non-ASCII claims - service-discovery docs: dedupe heading, close dangling code fence * fix(http-mock): await handleRequest in fromExpressStyleHandler Prevents an async Express-style handler's rejection from becoming an unhandled promise rejection instead of failing the middleware call. * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath (#5241) * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath Add EventModuleConfigurator, a BaseConfigBuilder-based configurator with fluent setOnDispatch/setOnBubble setters, replacing direct property assignment on the config object (kept as a deprecated path, no major bump needed). IEventModuleConfigurator is renamed to EventModuleConfig and converted from an interface to a type; IEventModuleConfigurator is kept as a deprecated alias. Narrow the dispatchEvent return type for registered FrameworkEventMap keys and pre-constructed event instances so callers get back the specific event type. Fix event$ to emit only after listeners and the bubble hook have run, matching its documented contract (it previously emitted before dispatch). Add waitForEvent/watchEvents test helpers and a ./operators subpath export for filterEvent (moved from src/filter-event.ts to src/operators/filter-event.ts). Rename configurator.ts -> EventModuleConfigurator.ts and provider.ts -> EventModuleProvider.ts for filename-convention compliance, and add a docs/ folder (configuration, observable-patterns, lifecycle, testing). * docs(vue-press): align event module docs with docs/ folder, move React page Trim vue-press/src/modules/event/README.md to an @include of the package README, add a docs/ mirror (configuration, observable-patterns, lifecycle, testing) that @includes the package's own docs, matching the http/module pattern. Move the event React bindings page from event/react.md to react/event/README.md, alongside react/router/, as an @include of @equinor/fusion-framework-react-module-event's README. Update the sidebar to match. * style(module-event): apply Biome formatting fixes flagged by reviewdog * fix(module-event): fix waitForEvent TDZ on sync completion, correct doc inaccuracies * feat(module-analytics): add MockAnalyticsAdapter for recording tracked analytics events (#5242) * feat(module-analytics): add MockAnalyticsAdapter for recording tracked analytics events - Add MockAnalyticsAdapter implementing IAnalyticsAdapter, recording events in-memory for test assertions (getAnalytics, waitForAnalytic) - Add ./mock subpath export - Add vitest.config.ts to make the package a discoverable vitest project - Document the new adapter in README.md and add a changeset Implements equinor/fusion-core-tasks#1657 * test(module-analytics): verify MockAnalyticsAdapter through the real module pipeline The existing unit tests only exercise MockAnalyticsAdapter's own logic in isolation. Add an integration test, mirroring the pattern used by msal and service-discovery's mock tests, that wires it through the real enableAnalytics -> ModulesConfigurator -> AnalyticsProvider.initialize() pipeline to prove events from provider.trackAnalytic and a real collector actually reach it, and that it doesn't interfere with other adapters. * docs(module-analytics): move testing docs to docs/testing.md, mirror in vue-press Adopt the docs/ convention already used by the event, http, and module packages: extract the README's inline Testing section into packages/modules/analytics/docs/testing.md, link to it from README, and add a vue-press mirror page (@include) with a sidebar entry. * docs(module-analytics): slim README to overview, split domain docs into docs/ Move Adapters, Collectors, and Tracking Events Manually out of the README into docs/adapters.md, docs/collectors.md, and docs/tracking-events.md, matching the event/http/module docs/ convention. Drops duplicate Configuration/Creating Custom Collectors content that had accumulated in the old README's Tracking Events Manually section. README is now a slim overview + entry points + documentation table + quick start, linking out to docs/ for the domain reference. Mirrors and sidebar entries added in vue-press. * fix(module-analytics): reject waitForAnalytic when a predicate matcher throws A throwing predicate matcher surfaced on RxJS's error channel, but the subscribe observer had no error handler — the exception was reported globally and the returned promise stayed pending forever. Add an error handler that rejects through the same cleanup path, and cover it with a test. * docs(module-analytics): fix broken abort and ModulesConfigurator examples in testing.md The abort example never called controller.abort() (so copying it hangs indefinitely) and redeclared `event` from the preceding example in the same code block. The bespoke ModulesConfigurator example destructured `analytics` from initialize() without a type — enableAnalytics only registers the module at runtime, so this doesn't typecheck as written; cast to IAnalyticsProvider, matching what the integration test does. * feat(azure-identity,msal-node): add MockAuthProvider test double for auth module (#5244) * feat(azure-identity,msal-node): add MockAuthProvider test double for auth module Add a configurable IAuthProvider test double, exported from a new /mock subpath in each package (@equinor/fusion-framework-module-azure-identity/mock, @equinor/fusion-framework-module-msal-node/mock). Unlike token_only mode's static-token providers (AuthProviderTokenOnly / AuthTokenProvider), MockAuthProvider actually implements login/logout, moving between signed-in and signed-out state, and exposes setAccount, setAccessToken and setExpiresOn so a test can control the returned token and simulate an expired token to exercise refresh logic. No real network calls are made. Implemented as two mirrored (twin) implementations rather than a shared package, since azure-identity's and msal-node's IAuthProvider interfaces differ in login's return type (AuthRecord vs. AuthenticationResult) and neither package depends on the other. Registers via enableAuthMock(configurator, configure?), a full 'auth' module replacement mirroring @equinor/fusion-framework-module-msal/mock's enableMsalMock, so no special-cased wiring is needed in either module. Closes equinor/fusion-core-tasks#1665. * fix(azure-identity,msal-node): make enableAuthMock generic over TModules/TRef Replaces the IModulesConfigurator<any, any> parameter with a generic <TModules extends Array<AnyModule> = Array<AnyModule>, TRef = unknown> signature, so the optional configure callback keeps the caller's actual TRef type instead of erasing it to unknown. Addresses review feedback on PR #5244 (copilot-pull-request-reviewer). * fix(azure-identity,msal-node): fix double-registration in mock README example The Testing section called enableAuthMock(configurator) once for its return value, then again with a configure callback to seed the account. The second call replaces the first provider (enableAuthMock always registers last), so the retained `auth` reference from the first call was no longer the active provider by the time login/acquireAccessToken ran against it. Collapse to a single enableAuthMock call that both seeds the account and returns the provider that stays registered. Addresses review feedback on PR #5244 (copilot-pull-request-reviewer). * feat(module-bookmark): add BookmarkMockClient and enableBookmarkMock (#5245) * feat(module-bookmark): add BookmarkMockClient and enableBookmarkMock Add a purpose-built mock for the bookmark module, exported from a new `/mock` subpath (`@equinor/fusion-framework-module-bookmark/mock`), following the client-boundary-swap pattern already used by msal's `enableMsalMock`. - `BookmarkMockClient`: in-memory `IBookmarkClient` backed by a Map of seeded bookmarks and a Set of favorite ids. - `BookmarkMockConfigurator`: extends the real `BookmarkModuleConfigurator` with `setBookmarks`/`setCurrentBookmark`/`setFavorite`, and falls back to trivial application/context resolvers when no real `app`/`context` module is registered (both are required by the config schema). - `enableBookmarkMock`/`bookmarkMockModule`: module descriptor mirroring the real module, only swapping `configure`. Create, update, delete, and favorite calls all flow through the real `BookmarkProvider` and `bookmark-flows/` epics unmodified. Related: equinor/fusion-core-tasks#1667 * fix(module-bookmark): match sourceSystem, strip list payloads, normalize null payload in mock client - matchesFilter now checks filter.sourceSystem (identifier/name/subSystem), since BookmarkProvider.getAllBookmarks always passes sourceSystem through the filter. - getAllBookmarks now strips payloads from returned bookmarks, matching the real BookmarkClient's list behavior. - updateBookmark now normalizes an explicit payload: null clear to undefined, matching setBookmarkData and avoiding a later getBookmarkData() emitting null as T. Addresses PR #5245 review comments on BookmarkMockClient.ts:44, :140, :270. * fix(module-bookmark): respect inherited resolve.application/context on mock fallback Gate the mock's fallback application/context resolvers on initial?.resolve as well as this._has(...), so a nested mock configurator does not overwrite resolvers already inherited from a parent config. Addresses PR #5245 review comment on BookmarkMockConfigurator.ts:150. * test(module-bookmark): assert getBookmark rejects for an unknown id Adds a Vitest assertion exercising the async error path through the real BookmarkProvider.getBookmark fetch flow, so a regression that stops missing ids from surfacing as observable errors would fail this suite. Addresses PR #5245 review comment on BookmarkMockClient.ts:125. * docs(module-bookmark): add required created/createdBy fields to mock seed example The README's setBookmarks example omitted created/createdBy, which are required on Bookmark, so copying the snippet as-is produced a type error. Addresses PR #5245 review comment on README.md:245. * docs(module-bookmark): add required created/createdBy fields to changeset example The changeset's setBookmarks example omitted created/createdBy, which are required on Bookmark, so the advertised snippet did not type-check. Addresses PR #5245 review comment on .changeset/module-bookmark_mock-entry-point.md:15. * refactor(module-context): extend BaseConfigBuilder and reorganize internal files - ContextModuleConfigurator now extends BaseConfigBuilder (createConfigAsync), matching the pattern used by module-event/module-http/module-msal - rename configurator.ts -> ContextModuleConfigurator.ts, split its exported types into ContextModuleConfig.ts and ContextModuleConfigurator.interface.ts - move get/query/related-context-selector.ts into src/selectors/ - move parse-context-item.ts into src/utils/ and export it from utils/index.ts - delete unused ContextConfigBuilder.ts - add ContextModuleConfigurator.test.ts covering the new builder surface No public export names change; index.ts still exports the same ContextModuleConfigurator/IContextModuleConfigurator/ContextModuleConfig. * feat(module-context): add mock and mock/fixtures entry points Add ./mock (ContextMockConfigurator, enableContextMock) — a ContextModuleConfigurator backed by an in-memory pool instead of a real client. Only the data source is substituted; validateContext, resolveContext, and parent-context propagation still run through the real ContextProvider. Friendly seeding methods (setCurrentContext/setContexts/addContext/ setRelatedContexts) cover the common cases; setResolver is the escape hatch. Add ./mock/fixtures (createContextItems, createContextItemFactory) — realistic ContextItem generators backed by an optional @faker-js/faker peer dependency, kept on a separate entry point so enableContextMock never pulls faker in. Adds an http workspace dependency (for the vitest project setup) and a package-level vitest.config.ts/test script. * docs(module-context): restructure README into docs/ and mirror in vue-press Move long-form content out of README.md into docs/data-model.md (ContextItem/ ContextItemType shape, query/related parameter types), docs/lifecycle.md (setting/resolving context, initial-context resolution, parent/child propagation) and docs/recipes.md (OData query params, path rewriting, accepting related context type families, skipping initial-context lookup, custom search errors), matching the module/http convention. README.md keeps the elevator pitch, a new "How it fits together" section covering the module's dependency graph (services required, event/navigation optional and auto-detected) and hierarchy-aware parent connection, and a documentation table linking to the three new pages. vue-press/src/modules/context mirrors the split with @include stubs, and the sidebar gains an Overview/Data model/Lifecycle/Recipes breakdown for Context. * feat(framework): back mock context accessor with ContextMockConfigurator FrameworkMockConfigurator.context now pins contextMockModule (from @equinor/fusion-framework-module-context/mock) instead of the real context module, so `.context` seeding methods (setCurrentContext/setContexts/ addContext/setRelatedContexts/setResolver) work the same way `.http` and `.serviceDiscovery` already do — no more real I/O through context in tests. docs/testing.md, docs/testing-extending.md and docs/testing-api.md document both context mocking strategies: the in-memory ContextMockConfigurator above, and mocking the context API's HTTP responses directly for tests that need to exercise the real configurator/services/HTTP pipeline. * chore: add changesets for context mock entry point and docs restructure * fix(module-context): document createConfig as a breaking change Extending BaseConfigBuilder changes the public createConfig method from Promise<ContextModuleConfig> to Observable<ContextModuleConfig>. Bump the changeset to major with migration guidance to createConfigAsync, matching the precedent set when service-discovery/msal adopted the same base class. Addresses review comment on PR #5243 (discussion_r3744095539). * docs(module-context): clarify setResolver only affects id-based lookup setResolver overrides ContextClient.resolveContext(id) (used by setCurrentContextById and the mock's initial context), not ContextProvider.resolveContext(item), which always resolves related context through setRelatedContexts regardless of setResolver. Reword the class and method docs so the two same-named methods aren't conflated. Addresses review comment on PR #5243 (discussion_r3744095551). * test(framework): assert seeding via .context reaches the built framework The existing test only checked that .context is defined, which also passed with the real ContextModuleConfigurator. Add a case that seeds via configurator.context.setCurrentContext(...), initializes the framework, and asserts fusion.modules.context.currentContext, covering the _pin(contextMockModule) composition path. Addresses review comment on PR #5243 (discussion_r3744095562). * feat(module-telemetry): add MockTelemetryAdapter and wire it into FrameworkMockConfigurator Closes equinor/fusion-core-tasks#1708 Adds a collecting MockTelemetryAdapter + TelemetryMockConfigurator behind a new ./mock subpath, following the MockAnalyticsAdapter shape. Wires FrameworkMockConfigurator.telemetry to the mock configurator so telemetry tracked through a mocked framework instance never reaches Application Insights or any real endpoint. * fix(module-telemetry): close waitForItem resource leaks and simplify changeset example * feat(module-feature-flag): add mock entry point for seeding test feature flags Add `FeatureFlagMockConfigurator`, `enableFeatureFlagMock`, and a `./mock` subpath so a test can seed flags in-memory via `addFeature`/`setFeatures`, with no `localStorage`/URL dependency. Toggling still runs through the real `FeatureFlagProvider`. Also fixes a missing `telemetry` row in the framework's testing.md accessor table (pre-existing gap, unrelated to this change). Closes equinor/fusion-core-tasks#1707. * test(module-feature-flag): assert async configure callbacks are awaited before initialize resolves Addresses review comment on module.ts:49. * docs(module-feature-flag): document the ./mock subpath in the README's API Reference Addresses review comment on package.json:23. * chore: add changeset for the testing.md telemetry accessor fix Addresses review comment on testing.md:30. * refactor(module-http): replace router-based mock with addMiddleware on the real configurator Closes equinor/fusion-core-tasks#1706 Replaces enableHttpMock/HttpMockConfigurator/HttpMockRouter with addMiddleware on the real HttpClientConfigurator, via a new HttpMiddlewareHandler that wraps _performFetch instead of swapping the configurator out. A middleware calling next(uri, init) falls through to the real call (or the next middleware) unchanged, so a test exercises the exact same client and configuration a real app registers. createRouterMiddleware and createOpenApiMockMiddleware replace the old router and its Express-style adapters, covering the same handler/OpenAPI-fake cases without depending on a real routing library. * docs(module-http): mirror the testing guide into vue-press Adds the http module's docs/testing.md via the same @include pattern used by client-configuration.md and server-sent-events.md, with a matching sidebar entry. * fix(framework): pin FrameworkMockConfigurator.http to the real HttpClientConfigurator FrameworkMockConfigurator.http returns the real IHttpClientConfigurator now that module-http's mock support is addMiddleware on that real configurator, not a separate mock-specific one. Fake a response with .http.addMiddleware(...) instead of swapping the module out. Updates testing.md/testing-api.md/testing-extending.md to match. * test(module-context): migrate off the removed enableHttpMock API enableHttpMock/http.get(...) no longer exist; rebuilds the fixture with configureHttp + createRouterMiddleware against configurator.addConfig, matching the addMiddleware-based mocking module-http now exposes. Updates the README's mention of the HTTP-mock alternative to match. * feat(module-app): add MockAppClient mock entry point Part of equinor/fusion-core-tasks#1706 Adds MockAppClient, exported from a new ./mock subpath, so a test can serve one app's own manifest and config locally instead of contacting the app service: - getAppManifest resolves locally only for manifest.appKey with no tag. - getAppConfig resolves locally for manifest.appKey when tag is absent or equal to manifest.build?.version. - Every other request still goes through the real AppClient it wraps. Also exports AppConfig as a value from the package root (previously type-only), so a test can construct one directly. * feat(app): add mockAppModules and AppMockConfigurator /mock entry point Closes equinor/fusion-core-tasks#1706 Adds a ./mock entry point: mockAppModules runs an application's real module pipeline — the real event/http/msal modules, the real AppConfigurator configuration pipeline and real lifecycle — against a mocked parent Fusion instance, so a test exercises the wiring an application actually depends on instead of a reimplementation of it. AppMockConfigurator extends the real AppConfigurator, pinning http and msal to the same test doubles FrameworkMockConfigurator uses so .http/.msal are reachable synchronously; event is deliberately left unpinned since its configure factory needs a real ref. Its own addConfig override redirects a pinned module's later registrations at the pinned descriptor. The base AppConfigurator constructor's own call into _configureHttpClientsFromAppConfig is deferred (via a no-op override + an explicit super call after pinning) so it never runs before this class's own fields exist. enableAppManifestMock registers the app module on a parent mockFramework configurator, serving one app's manifest/config locally while delegating everything else to service discovery. mockAppModules uses it to build its zero-configuration default parent. Restructures README.md into an entry point pointing at docs/http-clients.md, docs/bookmarks.md and docs/testing.md, matching the convention already used by module-http and module-msal. * fix(app): remove dead initialize-app-configurator.ts duplicate initialize-app-modules.ts superseded this file when initializeAppModules gained the TConfigurator type parameter, but the old file was left in place -- nothing imported it, so it was dead code that still compiled and still linted (fusion-lint's require-tsdoc flagged it for the missing @template tags the newer file already has). * style: apply biome formatting fixes Long import lists wrapped onto multiple lines, a ternary split across lines, and a single-quoted string containing an apostrophe rewritten with double quotes -- no behavior change. * refactor(module-http): flatten mock/adapters into mock/, fix lint warnings mock/ only ever had one barrel (index.ts) plus an adapters/ subfolder holding the 3 files it re-exported -- two barrels doing the same job for exactly one category of file. Moved create-open-api-mock-middleware.ts, create-router-middleware.ts and resolve-open-api-mock-response.ts up into mock/ directly and dropped the adapters/ barrel, fixing relative import depths and OpenApiMockLike's re-export path along the way. Also fixes the fusion-lint warnings the files carried before the move: renamed each file to match its exported symbol (filename-convention), added the missing intent comments on create-router-middleware.ts's if-blocks and .map() (require-intent-comment), and replaced a comma-operator expression in compilePath with an explicit if/return (noCommaOperator). * fix(module-app): treat only an absent tag as 'no tag' in MockAppClient getAppManifest's !args.tag and getAppConfig's isOwnTag both treated an explicit empty-string tag the same as an absent one, so getAppManifest({ appKey, tag: '' }) and getAppConfig({ appKey, tag: '' }) answered locally instead of delegating, contradicting the documented 'tag is absent' rule. Compare against undefined explicitly instead of a falsy check. Adds MockAppClient.test.ts covering both methods' local-vs-delegate decision, including the explicit-empty-string-tag case. * docs(vue-press): add changeset for the http module testing page The new vue-press page and sidebar entry is a consumer-facing docs change to the published @equinor/fusion-framework-docs package, which needed a changeset like every other vue-press page addition in this repo. * chore(changesets): bump http mock changesets to major enableHttpMock, HttpMockConfigurator, and HttpMockRouter were removed from @equinor/fusion-framework-module-http's /mock entry point, and FrameworkMockConfigurator.http's return type changed from a mock-specific configurator to the real IHttpClientConfigurator -- both are breaking changes for existing consumers and need a major bump, not minor. * docs(app): correct mockAppModules' network-access claim The http module registered by AppMockConfigurator is the real HttpClientConfigurator -- addMiddleware only short-circuits requests a middleware matches with a response, so an unmatched client or a middleware that calls next still reaches the real network. msal is the only module genuinely backed by a test double. Reworded the doc comment to describe what is actually substituted instead of promising no network access. * fix(module-http): abort the real network call through middleware on abort() next(...) resolves through a Promise (HttpMiddlewareHandler's toPromise), so a middleware calling it creates a subscription to _performFetch that sits outside the tree takeUntil(this._abort$) tears down -- the outer request settled on abort(), but the underlying fetch kept running. abort() now also aborts a per-request AbortSignal combined into the request init, so _performFetch (fromFetch by default) is cancelled directly regardless of whether middleware severed the RxJS teardown chain. Adds a regression test with a pass-through middleware asserting the underlying fetch's signal is aborted. * test: add react-app hook testing utilities and msal proxy/hoisting coverage - add render-app-hook testing utility + useAccessToken hook test in react/app - add msal-hoisting coverage in packages/app - add createProxyProvider behavior coverage in modules/msal * test(react-app): add useTrackFeature hook tests Add @equinor/fusion-framework-module-telemetry as a devDependency so tests can read the mock telemetry adapter via fusion.modules.telemetry.getAdapter('mock'). * test(react-app): render Apploader against a real dynamic-import child app script Add renderAppComponent, a component-level counterpart to renderAppHook, and use it in a new Apploader.test.tsx that exercises the real useApploader -> App.initialize() -> dynamic import() pipeline (no vi.mock of createApp or the import mechanism), backed by a fixture child app script. * test(react-app): add useCurrentBookmark hook tests Covers the app-scoped happy path, filtering out a bookmark that belongs to a different app, and the framework-scoped fallback with its deprecation warning. Adds @equinor/fusion-framework-module-bookmark as a devDependency for the mock helpers. * test(react-app): add useCurrentContext hook tests Covers resolving the seeded initial context and switching to another seeded item via setCurrentContext, against the app-scoped context module mock. Adds @equinor/fusion-framework-module-context as a devDependency for the mock helpers. * test(react-app): add useFeature hook tests Covers resolving an app-scoped flag, app-over-framework override on a shared key, a framework-only flag surfacing through the merged stream, and toggleFeature inverting the current value with no explicit argument. * test(react-app): add useHelpCenter hook tests Covers every open* action, asserting the framework event dispatched on the app's event module carries the expected page and payload. * test(react-app): add useToken and useCurrentAccount hook tests Completes coverage of the msal folder alongside the existing useAccessToken tests: useToken's full AuthenticationResult happy/error paths, and useCurrentAccount against the default mock user, a custom msal.setAccount() override, and the signed-out case. * test(react-app): add useAppSetting and useAppSettings hook tests Covers the settings folder's public hooks against the real fetch/update round-trip (via an in-memory apps-client mock): resolving persisted values, default-value fallback before resolution, persisting an update (direct value and updater-function forms), and surfacing a persistence failure through onError instead of throwing. useAppSettingsStatus is internal (not exported from settings/index.ts) and is exercised indirectly through these onLoading/onUpdating-adjacent paths. * docs(react-app): fix HTTP config example and nullable/async return types Fixes issues found while writing hook tests: README/createComponent examples used configurator.http.configureClient (test-only mock API) instead of the real configurator.configureHttpClient; setCurrentContext/useFrameworkCurrentContext were documented as void-returning when they actually return void | Promise<ContextItem | null>; AuthenticationResult.account/expiresOn were documented as non-nullable; useCurrentBookmark's deprecated framework-scope fallback and its console warning were undocumented. * docs(react-app): add app testing guide * fix(react-app): unmount rendered hooks in msal token tests Prevents the async token-acquisition effect from flushing a state update after happy-dom's window is torn down at the end of the test file, which CI surfaced as an uncaught 'window is not defined' exception (all assertions still passed, but the run was marked failed). * test: apply Biome line-length formatting suggestions Collapse two waitFor assertions that fit within the line-length limit back onto a single line, per Biome/reviewdog suggestions on PR #5271. * fix(app): treat empty-string assetUri as an explicit override if (assetUri) silently ignored a caller-supplied empty string, which is a meaningful value (selects a root-relative script path). Check against undefined instead so the override always applies when the caller passes the option. * docs(react-app): clarify network-boundary faking scope; add error-state example The docs overstated that renderAppHook/renderAppComponent fake the entire network boundary. Only requests a seeded router middleware answers are faked; unmatched requests still reach the real network. Also documents renderAppComponent under the /testing subpath table, and adds an error-state test example mirroring Apploader.test.tsx. * docs(react-app): remove invalid @template Result tag from renderAppComponent renderAppComponent has no Result type parameter; the tag referenced a generic that does not exist on this function and would fail TSDoc validation. * test(react-app): assert the seeded bookmark actually resolved before checking the app-key filter The previous waitFor(() => expect(result.current).toBeDefined()) was a no-op since result.current is always a defined object. Now waits on the unfiltered useAppModules().bookmark provider to resolve the seeded bookmark first, so the assertion that useCurrentBookmark hides it can't pass trivially against a still-pending value. * docs(changeset): qualify cross-repo issue reference Closes #1716 resolved to an unrelated PR in this repo. Qualifying as equinor/fusion-core-tasks#1716 points at the correct issue while preserving auto-close-on-merge. * test: apply Biome line-length formatting suggestions Reformat two msal-hoisting.test.ts call expressions and two useCurrentContext.test.tsx object literals that exceed the line-length limit, per Biome/reviewdog suggestions on PR #5271. * chore(deps): bump vitest to ^4.1.10 Bumps vitest and @vitest/coverage-v8 across the workspace, and migrates vitest.config.ts package.json imports to the `with { type: 'json' }` import attribute required by the newer vitest/vite toolchain. * feat(cli): add `ffc app test` command Runs the application's own Vitest suite with manifest/config/module-configurator resolved the same way `ffc app build`/`ffc app dev` do, exposed via a Vite plugin's virtual modules to @equinor/fusion-framework-react-app/vitest's test/render fixtures. Renamed packages/cli/src/lib/testing -> lib/vitest and the ./testing export -> ./vitest. * Update packages/cli/src/cli/commands/app/test.command.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix(cli): throw on an explicit but missing --configure path An explicitly supplied configure path is a user request, not a convention lookup — a typo should fail loudly instead of silently running the test suite without the app's modules. * refactor: clean up code formatting for better readability * feat(react-app): vitest render/test-app fixtures under src/vitest - rename src/testing -> src/vitest, add test-app/scope fixture chain (test-app.tsx, test.tsx, scope/*) for rendering app components and hooks with a fully wired Fusion test environment - fix(react): useFrameworkModule warns with the correct module name - fix(module-context): stop warning when there's no initial context to resolve - fix(module-msal): drop stray debug console.log in resolve-version - fix(log): guard process.env access for Vitest Browser Mode - chore: biome format fixes surfaced by pnpm check (unrelated pre-existing drift in cli/linting/event packages) * refactor: extract react-app vitest helpers into vitest-plugin-react-app Move renderAppHook/renderAppComponent/testApp and the app-test Vite plugin out of @equinor/fusion-framework-react-app's ./vitest entry-point and @equinor/fusion-framework-cli's ./vitest entry-point into a new standalone package, @equinor/fusion-framework-vitest-plugin-react-app, built on vitest-browser-react instead of @testing-library/react. - Remove the ffc app test CLI command and cli/src/lib/vitest (superseded by appTestVitePlugin registered directly in a project's vitest.config.ts). - Add packages/vitest-plugin/react-app with README, appTestVitePlugin, renderAppHook, renderAppComponent, testApp, and a /test entry-point exporting pre-seeded test/render fixtures. - Update react-app's README/docs and package.json to drop ./vitest and point at the new package instead. - Fix stray console.log in msal's resolveVersion mismatch warning, and guard fusion-log's process.env access for environments without a Node process global (Vitest Browser Mode). - Install Playwright browsers in CI for the new package's and react-app's Vitest Browser Mode tests. - Add CODEMAP.md entry for the new package. Changesets included for all affected packages. * ci: fix Playwright install invocation to filter into a package that depends on it pnpm exec at the repo root only resolves bins from root's own node_modules, and playwright is a devDependency of react-app/vitest-plugin-react-app only, not the workspace root - the previous 'pnpm exec playwright install' failed with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL / 'Command "playwright" not found'. * fix(vitest-plugin-react-app): declare vitest as a peer dependency The package uses vitest's expect/vi APIs at test-authoring time but only listed it as a devDependency, so consumers weren't told to install it themselves. * fix(vitest-plugin-react-app): correct tsconfig project reference src/index.ts and src/resolve-app-test-env.ts import @equinor/fusion-imports (utils/imports), but the tsconfig referenced utils/observable, an unrelated package. * test(vitest-plugin-react-app): cover resolveAppTestEnv and appTestVitePlugin The package's own resolution/virtual-module logic had no test coverage and no vitest.config.ts, so it wasn't even picked up as a project by the root Vitest run. * docs: add missing changeset for CLI ./vitest entry-point removal The removal of ffc app test, testApplication, the ./vitest entry-point, and resolveAppTestEnv from @equinor/fusion-framework-cli had no changeset. * fix(module-context): make telemetry an optional peer dependency The context module only reads telemetry defensively via an injected instance (never imports the package directly), matching the optional pattern already used by modules/msal. * docs(vitest-plugin-react-app): fix quick-start snippet and fixture wording Add the missing 'import { expect } from vitest' to the quick-start test snippet, and reword the fixture-sharing bullet to clarify only seeded defaults are shared, not fusion/app state between tests. * docs(vitest-plugin-react-app): clarify testApp fixture-sharing wording Match the README wording fix: testApp's JSDoc previously implied one shared mocked scope across tests, when each test actually gets fresh fusion/app instances and only the seeded defaults are shared. * fix(react-app): remove stale vitest-browser-react peer dependency vitest-browser-react was only used by the now-removed ./vitest entry-point; src/ has no remaining references to it, so it stays a devDependency only. * test(module-context): cover telemetry tracking for initial-context resolution Adds coverage for the resolver-error and successful-resolution telemetry events raised by module.ts's postInitialize, alongside the existing console.warn-fallback test for when no telemetry module is registered. * test(react-app): add useToken rerender regression test Asserts acquireToken is not re-invoked when scopes are re-rendered with the same contents (a fresh array literal each time), but is re-invoked when the contents actually change. * refactor(tests): format code for better readability in useToken and ContextModuleConfigurator tests * feat(vitest-plugin-react-app): add defineProject config helper, fix entrypoint inference - New `/config` entry-point exporting defineProject: a drop-in for Vitest's own defineProject, pre-wired with appTestVitePlugin and the @vitest/browser-playwright/chromium provider. - appTestVitePlugin now infers entrypoint from the Vitest project's own root (via configResolved) instead of process.cwd() when none is given. - @vitest/browser-playwright and playwright are now explicit peer dependencies. * fix(module): keep addConfig additive when the same module descriptor is re-registered ModulesConfigurator.addConfig discarded previously registered configure/afterConfig/ afterInit callbacks whenever it was called again for an already-registered module name, even when the descriptor itself was the exact same object. This broke config functions that register more than one named client against a shared module singleton, e.g. calling configureHttpClient/useFrameworkServiceClient more than once from @equinor/fusion-framework-module-http - only the last call's client survived; earlier ones threw "No registered http client for key [...]" at createClient() time, with no error at configuration time. addConfig now only discards previous callbacks when a genuinely different module descriptor replaces the old one for that name; re-registering the same shared descriptor stays additive. * feat(openapi-mock): split loadFakerMap into a Node-only /node entry point loadFakerMap reads sidecar files from disk (node:fs) and, for a .ts/.js sidecar, shells out to esbuild via @equinor/fusion-imports' importConfig - neither of which exists in a browser (or browser-mode Vitest) runtime. Move it to a separate @equinor/fusion-openapi-mock/node entry point (src/node.ts) so importing the package's main entry never pulls Node-only code into a browser bundle. A fields map built in code, or loaded ahead of time some other way, still works with createOpenApiMock from the main entry point regardless of runtime. Also bumps the read-pkg pnpm override (10.0.0 -> 10.1.0) picked up while resolving this package's dependency tree. * fix(vitest-plugin-react-app): warm up all source to avoid mid-test Vite reload defineProject only transformed source as each test file requested it, so a route component reached solely through a lazy/code-split import (e.g. React.lazy or a route-DSL component path) could be discovered mid-run instead of up front - Vite then reloads the page to pick it up, which fails the in-flight test file's import. Set server.warmup.clientFiles: ['src/**/*.{ts,tsx}'] so all source under src/ is pre-transformed before the first test request. * chore: exclude cookbook test files from AI indexing, exempt generators.ts from single-export lint - fusion-ai.config.ts: exclude cookbooks/**/__tests__/** and *.test|spec.{ts,tsx} from both the patterns and rawPatterns indexes - test files aren't consumer-facing API surface and were polluting cookbook search results. - fusion-lint.config.json: add generators.ts to single-export-per-file's excludePattern, matching the existing convention for deliberately multi-export utility filenames (utils.ts, helpers.ts, options.ts, etc). * test(cookbook-app-react): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the App component via renderAppComponent. - Root vitest.config.ts now globs cookbooks/**/vitest.config.ts. * test(cookbook-app-react-context): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers seeded initial context, related-context items, and switching context via the app's own context module ref. * test(cookbook-app-react-feature-flag): add Vitest coverage via vitest-plugin-react-app - FeatureFlag/FeatureFlags moved into src/components/, useFeatureLogger moved into src/hooks/, each alongside its own vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - FeatureFlag.test.tsx / FeatureFlags.test.tsx cover rendering and toggling. * test(cookbook-app-react-msal): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the default signed-in mock user, overriding configure with a custom mock account, and resolving/rendering an access token. - Removes the unused demo src/config.ts (dev-only console logging). * test(cookbook-app-react-router): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - Component and route-page tests via testWithRouter/createRouteProps, with fixtures built from seeded Faker.js generators (src/mocks/generators.ts) instead of hand-written literals. - src/mocks/{openapi.json,fields.faker.ts,api-mock.ts} + testWithApiMock wire an OpenAPI-mock-backed HTTP middleware for full-router integration tests (routing-with-api-mock.test.tsx) with deterministic, schema-shaped responses. - README.md documents the testing approach (Testing section). * test(cookbook-app-react-module): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the demo module's resolved foo/bar values, and overriding configure to seed a different demo configuration. - fix(config.ts): setBar(() => 69) returned a plain number, not a valid ObservableInput, so BaseConfigBuilder silently discarded it and bar fell through to the module's own 10-second delayed default (5) instead of 69. setBar(async () => 69) matches setFoo's existing pattern and resolves immediately. * style: apply biome format/lint fixes repo-wide Runs `biome check --write --unsafe .` across the whole repo: - Wraps long lines exceeding the configured line width (mostly test files with chained assertions/JSX props). - cookbooks/app-react/src/config.ts: prefixes 3 intentionally-unused callback parameters (env, config, instance) with `_`, per lint/correctness/noUnusedFunctionParameters. No behavioral changes. * test(vitest-config): include coverage tracking for packages only * docs(cookbook-app-react-msal): clarify config.ts removal has no functional impact Corrects the test's doc comment, which claimed composing with a real configure that no longer exists, and documents the intentional removal in the changeset instead of silently dropping the note. Addresses review comment on PR #5293 (discussion_r3778886987). * docs(cookbook-app-react): document silenced lifecycle console.log in changeset The demo console.log calls were intentionally left commented out to keep Vitest output clean; the changeset previously claimed 'no runtime changes', which wasn't accurate. Documents the behavior change instead of restoring the logging. Addresses review comment on PR #5293 (discussion_r3778887017). * fix(cookbook-app-react-router): fix faker.date.past() with unfixed refDate faker.seed() only makes the pseudo-random offset deterministic; without a fixed refDate, date.past() still resolves relative to the current wall-clock time, so generated joinDate values drift across days despite the seeded-fixture contract. Adds a shared fakerRefDate constant used by both generateUser and the User.joinDate field faker. Addresses review comments on PR #5293 (discussion_r3778887040, discussion_r3778887110). * docs(cookbook-app-react-context): fix changeset API names The changeset named renderAppComponent and renderAppComponent.fusion.app, neither of which exists in the added tests; they use the plugin's render/app fixtures instead. Addresses review comment on PR #5293 (discussion_r3778887093). * fix(cookbook-app-react-router): add missing tsconfig project reference Adds a reference to packages/vite-plugins/raw-imports matching the new workspace dependency in package.json, so isolated package builds resolve it correctly. Addresses review comment on PR #5293 (discussion_r3778887191). * fix(turbo): include bin/build in cli package's cached build outputs The build task only declared dist/** as cacheable output, but packages/cli's rollup build writes its bundled CLI binaries to bin/build/*.mjs (gitignored, not under dist/). On a turbo cache hit in CI, bin/build/ was never restored, so any cookbook invoking the fusion-framework-cli binary during its own build failed with ERR_MODULE_NOT_FOUND for bin/build/cli.mjs. Reproduced locally by deleting bin/build and re-running turbo build without --force (cache hit skipped restoring it); fixed by adding bin/build/** to outputs. * test: migrate React tests to Vitest browser * docs(testing): publish Fusion app testing guide (#5296) * docs(testing): add canonical package guidance * feat(docs): map package links in VuePress * docs(testing): publish the app testing journey * fix(docs): repair internal documentation links * test(module-state): use browser PouchDB adapter * test: decouple browser test projects * fix(docs): scope package link rewrites * Refactor code structure for improved readability and maintainability * fix(pnpm): remove duplicate '@remix-run/router' entries from lockfile * fix(define-project): enhance dependency optimization for esbuild scanner * refactor(memfs-plugin): move mocks for fs/promises and import-script to the top for better organization * docs(advanced): document `test.override(...)` for fixture overriding in test files * chore: drop spurious react-app major changeset; ./vitest never shipped * chore: drop spurious cli major changeset; ffc app test never shipped * chore: fix two more false-major changesets; mock infra never released * Remove coverage configuration from vitest.config.ts * feat: add migration guides for transitioning to Fusion Vitest and explain Browser Mode default * test(cookbook-app-react-router): await styling before asserting active link color * style(vue-press): add missing trailing newline in vitest config --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
odinr
added a commit
that referenced
this pull request
Aug 19, 2026
…kbooks (#5297) * feat(openapi-mock): add package for generating OpenAPI-shaped fake responses Adds @equinor/fusion-openapi-mock, a framework-agnostic utility that fakes OpenAPI 3 responses straight from a parsed spec document. Every operation with an operationId is faked from its declared success response schema, with overrides/register for edge cases, seed for repeatable output, and a FieldFakerMap for realistic per-field values via @faker-js/faker. No dependency on any HTTP or routing framework: resolve({ method, path, query }) returns a plain { status, mock }, so it composes with @equinor/fusion-framework-module-http's mock router or any other server. Related: equinor/fusion-core-tasks#1660 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(deps): update lockfile for @equinor/fusion-openapi-mock * fix(openapi-mock): address review feedback on route matching, response fallback, and validation - sort operations by specificity so literal routes win over templated ones - fall back to lowest declared numeric status when no 2xx/default response exists - validate parsed YAML faker maps and fetched documents are plain objects - use an isolated faker instance per generation instead of mutating the global singleton - refactor createOpenApiMock and apply-field-fakers into smaller, focused functions * refactor(openapi-mock): favor readability and avoid mutable locals - replace && shorthand and let-based branches with explicit if/early-return - extract resolveResponseSchema, parseDocumentText helpers - rewrite resolvePointer's pointer walk with Array#reduce instead of a mutable loop variable * feat(module-msal): add a mock entry point for testing without credentials or network Adds ./mock, so applications can run against the auth module without credentials or network access: import { enableMsalMock, createMsalMockClient } from '@equinor/fusion-framework-module-msal/mock'; enableMsalMock(configurator, (builder) => { builder.setAccount({ name: 'Ada Lovelace' }); }); Only the MSAL client is substituted: - MsalMockClient resolves tokens in-process and takes the same MsalClientConfig as the real MsalClient, keeps a real account cache keyed by homeAccountId (matching MSAL's getAccount/getAllAccounts semantics), and produces structurally valid, unsigned JWTs. - The client construction seam (_createClient/_createClientConfig) is extracted from MsalConfigurator._processConfig, giving a supported seam for authenticating through something other than Entra ID. No client is built when the module is hoisted onto a host application's provider, since a client built during configuration would be discarded or shadow the host's signed-in user (_isHoisted). - MsalMockConfigurator.setAccount lets a test declare the signed-in user on the builder; the account is applied before MsalProvider initializes, so the provider's own start-up path (including automatic login when signedOut + requiresAuth) acts on it. - The config schema moved into MsalConfig.schema.ts with MsalConfigExtension, an extension point mock configuration merges into without the builder needing to cast past its type. MsalProvider, MsalConfigurator and IMsalProvider are otherwise untouched; the mock module's configure differs from the real module, initialize does not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(module-http): add a mock entry point with middleware and adapters Adds @equinor/fusion-framework-module-http/mock, so HTTP clients answer requests from registered route handlers instead of the network: import { enableHttpMock } from '@equinor/fusion-framework-module-http/mock'; enableHttpMock(configurator, (builder) => { builder.configureClient('catalog', { baseUri: 'https://api.example.com' }); builder.get('/items', () => Response.json([{ id: 1 }])); }); Route handlers are Fetch-standard middleware - (request: Request) => Response | undefined | Promise<...> - tried in registration order, with undefined falling through to the next one. One router is shared across every named client a HttpMockConfigurator builds, matched against each request's fully resolved URL. HttpClient gains a _performFetch(uri, init) seam, isolated from _fetch$ so a test double replaces only the network call while request preparation, the response pipeline, and abort handling run unchanged. Two adapters drop in an existing backend with no hard dependency on it: - fromExpressStyleHandler(handler) adapts an Express-style (req, res) handler (or a whole framework built from them, like openapi-backend). - fromOpenApiMock(openApiMock) adapts an @equinor/fusion-openapi-mock instance (duck-typed, no package dependency), so a real openapi.json/openapi.yaml fakes every response with no handlers written at all. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(module-service-discovery): add a mock entry point backed by an in-memory registry Adds @equinor/fusion-framework-module-service-discovery/mock, so service discovery resolves from an in-memory registry instead of the network: import { mockServiceDiscovery } from '@equinor/fusion-framework-module-service-discovery/mock'; mockServiceDiscovery(configurator, { services: [{ key: 'apps', uri: 'https://apps.test' }] }); ServiceDiscoveryMockConfigurator builds the registry on the builder itself - setBaseUri, addService, addServices, removeService, setServices, setResolveUnknownServices - and the client is constructed from that registry when the module builds its config, so a test never has to construct a client just to add a service. setBaseUri also lets the default service endpoints point at a local mock server such as http://localhost:3000, so an application can make real HTTP calls against Mockoon, Prism or the dev server without a service worker intercepting requests. Exposes the discovery client on the provider as .client, mirroring MsalProvider.client, so a test can spy on it directly: vi.spyOn(fusion.modules.serviceDiscovery.client, 'resolveService').mockResolvedValue(service); configureServiceDiscovery now also accepts a synchronous callback - the underlying builder always allowed it, only the exported type required a promise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(module): fix configurator phase ordering and dot-path optional branches Two independent fixes needed by the framework mock work: - Module re-registration now replaces the prior module's configure, afterConfig, and afterInit callbacks instead of appending to them, keyed by module name (_dedupeModulesByName / _removeModuleCallbacks). This prevents stale callback execution when a mock module (e.g. enableMsalMock) overrides a real module registration, and fixes configurator phases running out of order or skipping post-configure hooks in certain initialization paths. - DotPath now unwraps an optional object property with NonNullable the same way DotPathType already did, so a path BaseConfigBuilder understands is one _set no longer refuses. Given { foo?: { bar: string } }, 'foo.bar' is now a valid path, matching 'foo' which was already allowed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(framework): add a mock entry point that composes each module's own test double Adds @equinor/fusion-framework/mock for initializing the framework in a test: import { mockFramework } from '@equinor/fusion-framework/mock'; const fusion = await mockFramework((configurator) => { configurator.msal.setAccount({ name: 'Ada Lovelace' }); configurator.serviceDiscovery.setBaseUri('http://localhost:6669'); }); mockFramework runs the real configure -> initialize pipeline with the real built-in modules and substitutes only the boundaries that leave the process - the MSAL client and the service discovery client. Module wiring, configuration validation and lifecycle hooks behave as they do in production. FrameworkMockConfigurator gains _pin/_getConfig, so a module supplied through TModules gets the same kind of named accessor .msal and .serviceDiscovery already have, rather than that pinning being hand-written once per built-in mock. .services, .context and .telemetry are exposed the same way, though they still perform real I/O until those modules have a test double of their own. event is intentionally left out - its configure factory reads ref to wire event bubbling to a parent event provider when hoisted, and pinning it would call configure() with ref always undefined, silently disabling that bubbling. The entry point owns no mock logic of its own: each module exports its own test double from its own ./mock entry point, and this one only composes the built-in set. It has no test-runner dependency and provides no mocking API, since replacing an individual call belongs to the test runner. init() no longer throws when no DOM is present - the window.Fusion assignment (for portal shells and widgets) is now skipped when window is undefined, so a test runner using the node environment or a server-side render no longer fails with 'ReferenceError: window is not defined'. Also restructures documentation so each README is an entry point rather than a manual: long-form content (including each package's testing.md) moved into per-package docs/ folders, matching the convention already used by @equinor/fusion-framework-module and @equinor/fusion-framework-module-http. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review comments on PR #5205 - msal-config-schema: reject unparseable version instead of coercing to "null" - HttpMockRouter: reset RegExp lastIndex so global/sticky match patterns are reusable across requests - extract decodeJwtSegment into its own file, fix atob/UTF-8 round-trip for non-ASCII claims - service-discovery docs: dedupe heading, close dangling code fence * fix(http-mock): await handleRequest in fromExpressStyleHandler Prevents an async Express-style handler's rejection from becoming an unhandled promise rejection instead of failing the middleware call. * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath (#5241) * feat(module-event): add EventModuleConfigurator, waitForEvent/watchEvents, and operators subpath Add EventModuleConfigurator, a BaseConfigBuilder-based configurator with fluent setOnDispatch/setOnBubble setters, replacing direct property assignment on the config object (kept as a deprecated path, no major bump needed). IEventModuleConfigurator is renamed to EventModuleConfig and converted from an interface to a type; IEventModuleConfigurator is kept as a deprecated alias. Narrow the dispatchEvent return type for registered FrameworkEventMap keys and pre-constructed event instances so callers get back the specific event type. Fix event$ to emit only after listeners and the bubble hook have run, matching its documented contract (it previously emitted before dispatch). Add waitForEvent/watchEvents test helpers and a ./operators subpath export for filterEvent (moved from src/filter-event.ts to src/operators/filter-event.ts). Rename configurator.ts -> EventModuleConfigurator.ts and provider.ts -> EventModuleProvider.ts for filename-convention compliance, and add a docs/ folder (configuration, observable-patterns, lifecycle, testing). * docs(vue-press): align event module docs with docs/ folder, move React page Trim vue-press/src/modules/event/README.md to an @include of the package README, add a docs/ mirror (configuration, observable-patterns, lifecycle, testing) that @includes the package's own docs, matching the http/module pattern. Move the event React bindings page from event/react.md to react/event/README.md, alongside react/router/, as an @include of @equinor/fusion-framework-react-module-event's README. Update the sidebar to match. * style(module-event): apply Biome formatting fixes flagged by reviewdog * fix(module-event): fix waitForEvent TDZ on sync completion, correct doc inaccuracies * feat(module-analytics): add MockAnalyticsAdapter for recording tracked analytics events (#5242) * feat(module-analytics): add MockAnalyticsAdapter for recording tracked analytics events - Add MockAnalyticsAdapter implementing IAnalyticsAdapter, recording events in-memory for test assertions (getAnalytics, waitForAnalytic) - Add ./mock subpath export - Add vitest.config.ts to make the package a discoverable vitest project - Document the new adapter in README.md and add a changeset Implements equinor/fusion-core-tasks#1657 * test(module-analytics): verify MockAnalyticsAdapter through the real module pipeline The existing unit tests only exercise MockAnalyticsAdapter's own logic in isolation. Add an integration test, mirroring the pattern used by msal and service-discovery's mock tests, that wires it through the real enableAnalytics -> ModulesConfigurator -> AnalyticsProvider.initialize() pipeline to prove events from provider.trackAnalytic and a real collector actually reach it, and that it doesn't interfere with other adapters. * docs(module-analytics): move testing docs to docs/testing.md, mirror in vue-press Adopt the docs/ convention already used by the event, http, and module packages: extract the README's inline Testing section into packages/modules/analytics/docs/testing.md, link to it from README, and add a vue-press mirror page (@include) with a sidebar entry. * docs(module-analytics): slim README to overview, split domain docs into docs/ Move Adapters, Collectors, and Tracking Events Manually out of the README into docs/adapters.md, docs/collectors.md, and docs/tracking-events.md, matching the event/http/module docs/ convention. Drops duplicate Configuration/Creating Custom Collectors content that had accumulated in the old README's Tracking Events Manually section. README is now a slim overview + entry points + documentation table + quick start, linking out to docs/ for the domain reference. Mirrors and sidebar entries added in vue-press. * fix(module-analytics): reject waitForAnalytic when a predicate matcher throws A throwing predicate matcher surfaced on RxJS's error channel, but the subscribe observer had no error handler — the exception was reported globally and the returned promise stayed pending forever. Add an error handler that rejects through the same cleanup path, and cover it with a test. * docs(module-analytics): fix broken abort and ModulesConfigurator examples in testing.md The abort example never called controller.abort() (so copying it hangs indefinitely) and redeclared `event` from the preceding example in the same code block. The bespoke ModulesConfigurator example destructured `analytics` from initialize() without a type — enableAnalytics only registers the module at runtime, so this doesn't typecheck as written; cast to IAnalyticsProvider, matching what the integration test does. * feat(azure-identity,msal-node): add MockAuthProvider test double for auth module (#5244) * feat(azure-identity,msal-node): add MockAuthProvider test double for auth module Add a configurable IAuthProvider test double, exported from a new /mock subpath in each package (@equinor/fusion-framework-module-azure-identity/mock, @equinor/fusion-framework-module-msal-node/mock). Unlike token_only mode's static-token providers (AuthProviderTokenOnly / AuthTokenProvider), MockAuthProvider actually implements login/logout, moving between signed-in and signed-out state, and exposes setAccount, setAccessToken and setExpiresOn so a test can control the returned token and simulate an expired token to exercise refresh logic. No real network calls are made. Implemented as two mirrored (twin) implementations rather than a shared package, since azure-identity's and msal-node's IAuthProvider interfaces differ in login's return type (AuthRecord vs. AuthenticationResult) and neither package depends on the other. Registers via enableAuthMock(configurator, configure?), a full 'auth' module replacement mirroring @equinor/fusion-framework-module-msal/mock's enableMsalMock, so no special-cased wiring is needed in either module. Closes equinor/fusion-core-tasks#1665. * fix(azure-identity,msal-node): make enableAuthMock generic over TModules/TRef Replaces the IModulesConfigurator<any, any> parameter with a generic <TModules extends Array<AnyModule> = Array<AnyModule>, TRef = unknown> signature, so the optional configure callback keeps the caller's actual TRef type instead of erasing it to unknown. Addresses review feedback on PR #5244 (copilot-pull-request-reviewer). * fix(azure-identity,msal-node): fix double-registration in mock README example The Testing section called enableAuthMock(configurator) once for its return value, then again with a configure callback to seed the account. The second call replaces the first provider (enableAuthMock always registers last), so the retained `auth` reference from the first call was no longer the active provider by the time login/acquireAccessToken ran against it. Collapse to a single enableAuthMock call that both seeds the account and returns the provider that stays registered. Addresses review feedback on PR #5244 (copilot-pull-request-reviewer). * feat(module-bookmark): add BookmarkMockClient and enableBookmarkMock (#5245) * feat(module-bookmark): add BookmarkMockClient and enableBookmarkMock Add a purpose-built mock for the bookmark module, exported from a new `/mock` subpath (`@equinor/fusion-framework-module-bookmark/mock`), following the client-boundary-swap pattern already used by msal's `enableMsalMock`. - `BookmarkMockClient`: in-memory `IBookmarkClient` backed by a Map of seeded bookmarks and a Set of favorite ids. - `BookmarkMockConfigurator`: extends the real `BookmarkModuleConfigurator` with `setBookmarks`/`setCurrentBookmark`/`setFavorite`, and falls back to trivial application/context resolvers when no real `app`/`context` module is registered (both are required by the config schema). - `enableBookmarkMock`/`bookmarkMockModule`: module descriptor mirroring the real module, only swapping `configure`. Create, update, delete, and favorite calls all flow through the real `BookmarkProvider` and `bookmark-flows/` epics unmodified. Related: equinor/fusion-core-tasks#1667 * fix(module-bookmark): match sourceSystem, strip list payloads, normalize null payload in mock client - matchesFilter now checks filter.sourceSystem (identifier/name/subSystem), since BookmarkProvider.getAllBookmarks always passes sourceSystem through the filter. - getAllBookmarks now strips payloads from returned bookmarks, matching the real BookmarkClient's list behavior. - updateBookmark now normalizes an explicit payload: null clear to undefined, matching setBookmarkData and avoiding a later getBookmarkData() emitting null as T. Addresses PR #5245 review comments on BookmarkMockClient.ts:44, :140, :270. * fix(module-bookmark): respect inherited resolve.application/context on mock fallback Gate the mock's fallback application/context resolvers on initial?.resolve as well as this._has(...), so a nested mock configurator does not overwrite resolvers already inherited from a parent config. Addresses PR #5245 review comment on BookmarkMockConfigurator.ts:150. * test(module-bookmark): assert getBookmark rejects for an unknown id Adds a Vitest assertion exercising the async error path through the real BookmarkProvider.getBookmark fetch flow, so a regression that stops missing ids from surfacing as observable errors would fail this suite. Addresses PR #5245 review comment on BookmarkMockClient.ts:125. * docs(module-bookmark): add required created/createdBy fields to mock seed example The README's setBookmarks example omitted created/createdBy, which are required on Bookmark, so copying the snippet as-is produced a type error. Addresses PR #5245 review comment on README.md:245. * docs(module-bookmark): add required created/createdBy fields to changeset example The changeset's setBookmarks example omitted created/createdBy, which are required on Bookmark, so the advertised snippet did not type-check. Addresses PR #5245 review comment on .changeset/module-bookmark_mock-entry-point.md:15. * refactor(module-context): extend BaseConfigBuilder and reorganize internal files - ContextModuleConfigurator now extends BaseConfigBuilder (createConfigAsync), matching the pattern used by module-event/module-http/module-msal - rename configurator.ts -> ContextModuleConfigurator.ts, split its exported types into ContextModuleConfig.ts and ContextModuleConfigurator.interface.ts - move get/query/related-context-selector.ts into src/selectors/ - move parse-context-item.ts into src/utils/ and export it from utils/index.ts - delete unused ContextConfigBuilder.ts - add ContextModuleConfigurator.test.ts covering the new builder surface No public export names change; index.ts still exports the same ContextModuleConfigurator/IContextModuleConfigurator/ContextModuleConfig. * feat(module-context): add mock and mock/fixtures entry points Add ./mock (ContextMockConfigurator, enableContextMock) — a ContextModuleConfigurator backed by an in-memory pool instead of a real client. Only the data source is substituted; validateContext, resolveContext, and parent-context propagation still run through the real ContextProvider. Friendly seeding methods (setCurrentContext/setContexts/addContext/ setRelatedContexts) cover the common cases; setResolver is the escape hatch. Add ./mock/fixtures (createContextItems, createContextItemFactory) — realistic ContextItem generators backed by an optional @faker-js/faker peer dependency, kept on a separate entry point so enableContextMock never pulls faker in. Adds an http workspace dependency (for the vitest project setup) and a package-level vitest.config.ts/test script. * docs(module-context): restructure README into docs/ and mirror in vue-press Move long-form content out of README.md into docs/data-model.md (ContextItem/ ContextItemType shape, query/related parameter types), docs/lifecycle.md (setting/resolving context, initial-context resolution, parent/child propagation) and docs/recipes.md (OData query params, path rewriting, accepting related context type families, skipping initial-context lookup, custom search errors), matching the module/http convention. README.md keeps the elevator pitch, a new "How it fits together" section covering the module's dependency graph (services required, event/navigation optional and auto-detected) and hierarchy-aware parent connection, and a documentation table linking to the three new pages. vue-press/src/modules/context mirrors the split with @include stubs, and the sidebar gains an Overview/Data model/Lifecycle/Recipes breakdown for Context. * feat(framework): back mock context accessor with ContextMockConfigurator FrameworkMockConfigurator.context now pins contextMockModule (from @equinor/fusion-framework-module-context/mock) instead of the real context module, so `.context` seeding methods (setCurrentContext/setContexts/ addContext/setRelatedContexts/setResolver) work the same way `.http` and `.serviceDiscovery` already do — no more real I/O through context in tests. docs/testing.md, docs/testing-extending.md and docs/testing-api.md document both context mocking strategies: the in-memory ContextMockConfigurator above, and mocking the context API's HTTP responses directly for tests that need to exercise the real configurator/services/HTTP pipeline. * chore: add changesets for context mock entry point and docs restructure * fix(module-context): document createConfig as a breaking change Extending BaseConfigBuilder changes the public createConfig method from Promise<ContextModuleConfig> to Observable<ContextModuleConfig>. Bump the changeset to major with migration guidance to createConfigAsync, matching the precedent set when service-discovery/msal adopted the same base class. Addresses review comment on PR #5243 (discussion_r3744095539). * docs(module-context): clarify setResolver only affects id-based lookup setResolver overrides ContextClient.resolveContext(id) (used by setCurrentContextById and the mock's initial context), not ContextProvider.resolveContext(item), which always resolves related context through setRelatedContexts regardless of setResolver. Reword the class and method docs so the two same-named methods aren't conflated. Addresses review comment on PR #5243 (discussion_r3744095551). * test(framework): assert seeding via .context reaches the built framework The existing test only checked that .context is defined, which also passed with the real ContextModuleConfigurator. Add a case that seeds via configurator.context.setCurrentContext(...), initializes the framework, and asserts fusion.modules.context.currentContext, covering the _pin(contextMockModule) composition path. Addresses review comment on PR #5243 (discussion_r3744095562). * feat(module-telemetry): add MockTelemetryAdapter and wire it into FrameworkMockConfigurator Closes equinor/fusion-core-tasks#1708 Adds a collecting MockTelemetryAdapter + TelemetryMockConfigurator behind a new ./mock subpath, following the MockAnalyticsAdapter shape. Wires FrameworkMockConfigurator.telemetry to the mock configurator so telemetry tracked through a mocked framework instance never reaches Application Insights or any real endpoint. * fix(module-telemetry): close waitForItem resource leaks and simplify changeset example * feat(module-feature-flag): add mock entry point for seeding test feature flags Add `FeatureFlagMockConfigurator`, `enableFeatureFlagMock`, and a `./mock` subpath so a test can seed flags in-memory via `addFeature`/`setFeatures`, with no `localStorage`/URL dependency. Toggling still runs through the real `FeatureFlagProvider`. Also fixes a missing `telemetry` row in the framework's testing.md accessor table (pre-existing gap, unrelated to this change). Closes equinor/fusion-core-tasks#1707. * test(module-feature-flag): assert async configure callbacks are awaited before initialize resolves Addresses review comment on module.ts:49. * docs(module-feature-flag): document the ./mock subpath in the README's API Reference Addresses review comment on package.json:23. * chore: add changeset for the testing.md telemetry accessor fix Addresses review comment on testing.md:30. * refactor(module-http): replace router-based mock with addMiddleware on the real configurator Closes equinor/fusion-core-tasks#1706 Replaces enableHttpMock/HttpMockConfigurator/HttpMockRouter with addMiddleware on the real HttpClientConfigurator, via a new HttpMiddlewareHandler that wraps _performFetch instead of swapping the configurator out. A middleware calling next(uri, init) falls through to the real call (or the next middleware) unchanged, so a test exercises the exact same client and configuration a real app registers. createRouterMiddleware and createOpenApiMockMiddleware replace the old router and its Express-style adapters, covering the same handler/OpenAPI-fake cases without depending on a real routing library. * docs(module-http): mirror the testing guide into vue-press Adds the http module's docs/testing.md via the same @include pattern used by client-configuration.md and server-sent-events.md, with a matching sidebar entry. * fix(framework): pin FrameworkMockConfigurator.http to the real HttpClientConfigurator FrameworkMockConfigurator.http returns the real IHttpClientConfigurator now that module-http's mock support is addMiddleware on that real configurator, not a separate mock-specific one. Fake a response with .http.addMiddleware(...) instead of swapping the module out. Updates testing.md/testing-api.md/testing-extending.md to match. * test(module-context): migrate off the removed enableHttpMock API enableHttpMock/http.get(...) no longer exist; rebuilds the fixture with configureHttp + createRouterMiddleware against configurator.addConfig, matching the addMiddleware-based mocking module-http now exposes. Updates the README's mention of the HTTP-mock alternative to match. * feat(module-app): add MockAppClient mock entry point Part of equinor/fusion-core-tasks#1706 Adds MockAppClient, exported from a new ./mock subpath, so a test can serve one app's own manifest and config locally instead of contacting the app service: - getAppManifest resolves locally only for manifest.appKey with no tag. - getAppConfig resolves locally for manifest.appKey when tag is absent or equal to manifest.build?.version. - Every other request still goes through the real AppClient it wraps. Also exports AppConfig as a value from the package root (previously type-only), so a test can construct one directly. * feat(app): add mockAppModules and AppMockConfigurator /mock entry point Closes equinor/fusion-core-tasks#1706 Adds a ./mock entry point: mockAppModules runs an application's real module pipeline — the real event/http/msal modules, the real AppConfigurator configuration pipeline and real lifecycle — against a mocked parent Fusion instance, so a test exercises the wiring an application actually depends on instead of a reimplementation of it. AppMockConfigurator extends the real AppConfigurator, pinning http and msal to the same test doubles FrameworkMockConfigurator uses so .http/.msal are reachable synchronously; event is deliberately left unpinned since its configure factory needs a real ref. Its own addConfig override redirects a pinned module's later registrations at the pinned descriptor. The base AppConfigurator constructor's own call into _configureHttpClientsFromAppConfig is deferred (via a no-op override + an explicit super call after pinning) so it never runs before this class's own fields exist. enableAppManifestMock registers the app module on a parent mockFramework configurator, serving one app's manifest/config locally while delegating everything else to service discovery. mockAppModules uses it to build its zero-configuration default parent. Restructures README.md into an entry point pointing at docs/http-clients.md, docs/bookmarks.md and docs/testing.md, matching the convention already used by module-http and module-msal. * fix(app): remove dead initialize-app-configurator.ts duplicate initialize-app-modules.ts superseded this file when initializeAppModules gained the TConfigurator type parameter, but the old file was left in place -- nothing imported it, so it was dead code that still compiled and still linted (fusion-lint's require-tsdoc flagged it for the missing @template tags the newer file already has). * style: apply biome formatting fixes Long import lists wrapped onto multiple lines, a ternary split across lines, and a single-quoted string containing an apostrophe rewritten with double quotes -- no behavior change. * refactor(module-http): flatten mock/adapters into mock/, fix lint warnings mock/ only ever had one barrel (index.ts) plus an adapters/ subfolder holding the 3 files it re-exported -- two barrels doing the same job for exactly one category of file. Moved create-open-api-mock-middleware.ts, create-router-middleware.ts and resolve-open-api-mock-response.ts up into mock/ directly and dropped the adapters/ barrel, fixing relative import depths and OpenApiMockLike's re-export path along the way. Also fixes the fusion-lint warnings the files carried before the move: renamed each file to match its exported symbol (filename-convention), added the missing intent comments on create-router-middleware.ts's if-blocks and .map() (require-intent-comment), and replaced a comma-operator expression in compilePath with an explicit if/return (noCommaOperator). * fix(module-app): treat only an absent tag as 'no tag' in MockAppClient getAppManifest's !args.tag and getAppConfig's isOwnTag both treated an explicit empty-string tag the same as an absent one, so getAppManifest({ appKey, tag: '' }) and getAppConfig({ appKey, tag: '' }) answered locally instead of delegating, contradicting the documented 'tag is absent' rule. Compare against undefined explicitly instead of a falsy check. Adds MockAppClient.test.ts covering both methods' local-vs-delegate decision, including the explicit-empty-string-tag case. * docs(vue-press): add changeset for the http module testing page The new vue-press page and sidebar entry is a consumer-facing docs change to the published @equinor/fusion-framework-docs package, which needed a changeset like every other vue-press page addition in this repo. * chore(changesets): bump http mock changesets to major enableHttpMock, HttpMockConfigurator, and HttpMockRouter were removed from @equinor/fusion-framework-module-http's /mock entry point, and FrameworkMockConfigurator.http's return type changed from a mock-specific configurator to the real IHttpClientConfigurator -- both are breaking changes for existing consumers and need a major bump, not minor. * docs(app): correct mockAppModules' network-access claim The http module registered by AppMockConfigurator is the real HttpClientConfigurator -- addMiddleware only short-circuits requests a middleware matches with a response, so an unmatched client or a middleware that calls next still reaches the real network. msal is the only module genuinely backed by a test double. Reworded the doc comment to describe what is actually substituted instead of promising no network access. * fix(module-http): abort the real network call through middleware on abort() next(...) resolves through a Promise (HttpMiddlewareHandler's toPromise), so a middleware calling it creates a subscription to _performFetch that sits outside the tree takeUntil(this._abort$) tears down -- the outer request settled on abort(), but the underlying fetch kept running. abort() now also aborts a per-request AbortSignal combined into the request init, so _performFetch (fromFetch by default) is cancelled directly regardless of whether middleware severed the RxJS teardown chain. Adds a regression test with a pass-through middleware asserting the underlying fetch's signal is aborted. * test: add react-app hook testing utilities and msal proxy/hoisting coverage - add render-app-hook testing utility + useAccessToken hook test in react/app - add msal-hoisting coverage in packages/app - add createProxyProvider behavior coverage in modules/msal * test(react-app): add useTrackFeature hook tests Add @equinor/fusion-framework-module-telemetry as a devDependency so tests can read the mock telemetry adapter via fusion.modules.telemetry.getAdapter('mock'). * test(react-app): render Apploader against a real dynamic-import child app script Add renderAppComponent, a component-level counterpart to renderAppHook, and use it in a new Apploader.test.tsx that exercises the real useApploader -> App.initialize() -> dynamic import() pipeline (no vi.mock of createApp or the import mechanism), backed by a fixture child app script. * test(react-app): add useCurrentBookmark hook tests Covers the app-scoped happy path, filtering out a bookmark that belongs to a different app, and the framework-scoped fallback with its deprecation warning. Adds @equinor/fusion-framework-module-bookmark as a devDependency for the mock helpers. * test(react-app): add useCurrentContext hook tests Covers resolving the seeded initial context and switching to another seeded item via setCurrentContext, against the app-scoped context module mock. Adds @equinor/fusion-framework-module-context as a devDependency for the mock helpers. * test(react-app): add useFeature hook tests Covers resolving an app-scoped flag, app-over-framework override on a shared key, a framework-only flag surfacing through the merged stream, and toggleFeature inverting the current value with no explicit argument. * test(react-app): add useHelpCenter hook tests Covers every open* action, asserting the framework event dispatched on the app's event module carries the expected page and payload. * test(react-app): add useToken and useCurrentAccount hook tests Completes coverage of the msal folder alongside the existing useAccessToken tests: useToken's full AuthenticationResult happy/error paths, and useCurrentAccount against the default mock user, a custom msal.setAccount() override, and the signed-out case. * test(react-app): add useAppSetting and useAppSettings hook tests Covers the settings folder's public hooks against the real fetch/update round-trip (via an in-memory apps-client mock): resolving persisted values, default-value fallback before resolution, persisting an update (direct value and updater-function forms), and surfacing a persistence failure through onError instead of throwing. useAppSettingsStatus is internal (not exported from settings/index.ts) and is exercised indirectly through these onLoading/onUpdating-adjacent paths. * docs(react-app): fix HTTP config example and nullable/async return types Fixes issues found while writing hook tests: README/createComponent examples used configurator.http.configureClient (test-only mock API) instead of the real configurator.configureHttpClient; setCurrentContext/useFrameworkCurrentContext were documented as void-returning when they actually return void | Promise<ContextItem | null>; AuthenticationResult.account/expiresOn were documented as non-nullable; useCurrentBookmark's deprecated framework-scope fallback and its console warning were undocumented. * docs(react-app): add app testing guide * fix(react-app): unmount rendered hooks in msal token tests Prevents the async token-acquisition effect from flushing a state update after happy-dom's window is torn down at the end of the test file, which CI surfaced as an uncaught 'window is not defined' exception (all assertions still passed, but the run was marked failed). * test: apply Biome line-length formatting suggestions Collapse two waitFor assertions that fit within the line-length limit back onto a single line, per Biome/reviewdog suggestions on PR #5271. * fix(app): treat empty-string assetUri as an explicit override if (assetUri) silently ignored a caller-supplied empty string, which is a meaningful value (selects a root-relative script path). Check against undefined instead so the override always applies when the caller passes the option. * docs(react-app): clarify network-boundary faking scope; add error-state example The docs overstated that renderAppHook/renderAppComponent fake the entire network boundary. Only requests a seeded router middleware answers are faked; unmatched requests still reach the real network. Also documents renderAppComponent under the /testing subpath table, and adds an error-state test example mirroring Apploader.test.tsx. * docs(react-app): remove invalid @template Result tag from renderAppComponent renderAppComponent has no Result type parameter; the tag referenced a generic that does not exist on this function and would fail TSDoc validation. * test(react-app): assert the seeded bookmark actually resolved before checking the app-key filter The previous waitFor(() => expect(result.current).toBeDefined()) was a no-op since result.current is always a defined object. Now waits on the unfiltered useAppModules().bookmark provider to resolve the seeded bookmark first, so the assertion that useCurrentBookmark hides it can't pass trivially against a still-pending value. * docs(changeset): qualify cross-repo issue reference Closes #1716 resolved to an unrelated PR in this repo. Qualifying as equinor/fusion-core-tasks#1716 points at the correct issue while preserving auto-close-on-merge. * test: apply Biome line-length formatting suggestions Reformat two msal-hoisting.test.ts call expressions and two useCurrentContext.test.tsx object literals that exceed the line-length limit, per Biome/reviewdog suggestions on PR #5271. * chore(deps): bump vitest to ^4.1.10 Bumps vitest and @vitest/coverage-v8 across the workspace, and migrates vitest.config.ts package.json imports to the `with { type: 'json' }` import attribute required by the newer vitest/vite toolchain. * feat(cli): add `ffc app test` command Runs the application's own Vitest suite with manifest/config/module-configurator resolved the same way `ffc app build`/`ffc app dev` do, exposed via a Vite plugin's virtual modules to @equinor/fusion-framework-react-app/vitest's test/render fixtures. Renamed packages/cli/src/lib/testing -> lib/vitest and the ./testing export -> ./vitest. * Update packages/cli/src/cli/commands/app/test.command.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix(cli): throw on an explicit but missing --configure path An explicitly supplied configure path is a user request, not a convention lookup — a typo should fail loudly instead of silently running the test suite without the app's modules. * refactor: clean up code formatting for better readability * feat(react-app): vitest render/test-app fixtures under src/vitest - rename src/testing -> src/vitest, add test-app/scope fixture chain (test-app.tsx, test.tsx, scope/*) for rendering app components and hooks with a fully wired Fusion test environment - fix(react): useFrameworkModule warns with the correct module name - fix(module-context): stop warning when there's no initial context to resolve - fix(module-msal): drop stray debug console.log in resolve-version - fix(log): guard process.env access for Vitest Browser Mode - chore: biome format fixes surfaced by pnpm check (unrelated pre-existing drift in cli/linting/event packages) * refactor: extract react-app vitest helpers into vitest-plugin-react-app Move renderAppHook/renderAppComponent/testApp and the app-test Vite plugin out of @equinor/fusion-framework-react-app's ./vitest entry-point and @equinor/fusion-framework-cli's ./vitest entry-point into a new standalone package, @equinor/fusion-framework-vitest-plugin-react-app, built on vitest-browser-react instead of @testing-library/react. - Remove the ffc app test CLI command and cli/src/lib/vitest (superseded by appTestVitePlugin registered directly in a project's vitest.config.ts). - Add packages/vitest-plugin/react-app with README, appTestVitePlugin, renderAppHook, renderAppComponent, testApp, and a /test entry-point exporting pre-seeded test/render fixtures. - Update react-app's README/docs and package.json to drop ./vitest and point at the new package instead. - Fix stray console.log in msal's resolveVersion mismatch warning, and guard fusion-log's process.env access for environments without a Node process global (Vitest Browser Mode). - Install Playwright browsers in CI for the new package's and react-app's Vitest Browser Mode tests. - Add CODEMAP.md entry for the new package. Changesets included for all affected packages. * ci: fix Playwright install invocation to filter into a package that depends on it pnpm exec at the repo root only resolves bins from root's own node_modules, and playwright is a devDependency of react-app/vitest-plugin-react-app only, not the workspace root - the previous 'pnpm exec playwright install' failed with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL / 'Command "playwright" not found'. * fix(vitest-plugin-react-app): declare vitest as a peer dependency The package uses vitest's expect/vi APIs at test-authoring time but only listed it as a devDependency, so consumers weren't told to install it themselves. * fix(vitest-plugin-react-app): correct tsconfig project reference src/index.ts and src/resolve-app-test-env.ts import @equinor/fusion-imports (utils/imports), but the tsconfig referenced utils/observable, an unrelated package. * test(vitest-plugin-react-app): cover resolveAppTestEnv and appTestVitePlugin The package's own resolution/virtual-module logic had no test coverage and no vitest.config.ts, so it wasn't even picked up as a project by the root Vitest run. * docs: add missing changeset for CLI ./vitest entry-point removal The removal of ffc app test, testApplication, the ./vitest entry-point, and resolveAppTestEnv from @equinor/fusion-framework-cli had no changeset. * fix(module-context): make telemetry an optional peer dependency The context module only reads telemetry defensively via an injected instance (never imports the package directly), matching the optional pattern already used by modules/msal. * docs(vitest-plugin-react-app): fix quick-start snippet and fixture wording Add the missing 'import { expect } from vitest' to the quick-start test snippet, and reword the fixture-sharing bullet to clarify only seeded defaults are shared, not fusion/app state between tests. * docs(vitest-plugin-react-app): clarify testApp fixture-sharing wording Match the README wording fix: testApp's JSDoc previously implied one shared mocked scope across tests, when each test actually gets fresh fusion/app instances and only the seeded defaults are shared. * fix(react-app): remove stale vitest-browser-react peer dependency vitest-browser-react was only used by the now-removed ./vitest entry-point; src/ has no remaining references to it, so it stays a devDependency only. * test(module-context): cover telemetry tracking for initial-context resolution Adds coverage for the resolver-error and successful-resolution telemetry events raised by module.ts's postInitialize, alongside the existing console.warn-fallback test for when no telemetry module is registered. * test(react-app): add useToken rerender regression test Asserts acquireToken is not re-invoked when scopes are re-rendered with the same contents (a fresh array literal each time), but is re-invoked when the contents actually change. * refactor(tests): format code for better readability in useToken and ContextModuleConfigurator tests * feat(vitest-plugin-react-app): add defineProject config helper, fix entrypoint inference - New `/config` entry-point exporting defineProject: a drop-in for Vitest's own defineProject, pre-wired with appTestVitePlugin and the @vitest/browser-playwright/chromium provider. - appTestVitePlugin now infers entrypoint from the Vitest project's own root (via configResolved) instead of process.cwd() when none is given. - @vitest/browser-playwright and playwright are now explicit peer dependencies. * fix(module): keep addConfig additive when the same module descriptor is re-registered ModulesConfigurator.addConfig discarded previously registered configure/afterConfig/ afterInit callbacks whenever it was called again for an already-registered module name, even when the descriptor itself was the exact same object. This broke config functions that register more than one named client against a shared module singleton, e.g. calling configureHttpClient/useFrameworkServiceClient more than once from @equinor/fusion-framework-module-http - only the last call's client survived; earlier ones threw "No registered http client for key [...]" at createClient() time, with no error at configuration time. addConfig now only discards previous callbacks when a genuinely different module descriptor replaces the old one for that name; re-registering the same shared descriptor stays additive. * feat(openapi-mock): split loadFakerMap into a Node-only /node entry point loadFakerMap reads sidecar files from disk (node:fs) and, for a .ts/.js sidecar, shells out to esbuild via @equinor/fusion-imports' importConfig - neither of which exists in a browser (or browser-mode Vitest) runtime. Move it to a separate @equinor/fusion-openapi-mock/node entry point (src/node.ts) so importing the package's main entry never pulls Node-only code into a browser bundle. A fields map built in code, or loaded ahead of time some other way, still works with createOpenApiMock from the main entry point regardless of runtime. Also bumps the read-pkg pnpm override (10.0.0 -> 10.1.0) picked up while resolving this package's dependency tree. * fix(vitest-plugin-react-app): warm up all source to avoid mid-test Vite reload defineProject only transformed source as each test file requested it, so a route component reached solely through a lazy/code-split import (e.g. React.lazy or a route-DSL component path) could be discovered mid-run instead of up front - Vite then reloads the page to pick it up, which fails the in-flight test file's import. Set server.warmup.clientFiles: ['src/**/*.{ts,tsx}'] so all source under src/ is pre-transformed before the first test request. * chore: exclude cookbook test files from AI indexing, exempt generators.ts from single-export lint - fusion-ai.config.ts: exclude cookbooks/**/__tests__/** and *.test|spec.{ts,tsx} from both the patterns and rawPatterns indexes - test files aren't consumer-facing API surface and were polluting cookbook search results. - fusion-lint.config.json: add generators.ts to single-export-per-file's excludePattern, matching the existing convention for deliberately multi-export utility filenames (utils.ts, helpers.ts, options.ts, etc). * test(cookbook-app-react): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the App component via renderAppComponent. - Root vitest.config.ts now globs cookbooks/**/vitest.config.ts. * test(cookbook-app-react-context): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers seeded initial context, related-context items, and switching context via the app's own context module ref. * test(cookbook-app-react-feature-flag): add Vitest coverage via vitest-plugin-react-app - FeatureFlag/FeatureFlags moved into src/components/, useFeatureLogger moved into src/hooks/, each alongside its own vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - FeatureFlag.test.tsx / FeatureFlags.test.tsx cover rendering and toggling. * test(cookbook-app-react-msal): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the default signed-in mock user, overriding configure with a custom mock account, and resolving/rendering an access token. - Removes the unused demo src/config.ts (dev-only console logging). * test(cookbook-app-react-router): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - Component and route-page tests via testWithRouter/createRouteProps, with fixtures built from seeded Faker.js generators (src/mocks/generators.ts) instead of hand-written literals. - src/mocks/{openapi.json,fields.faker.ts,api-mock.ts} + testWithApiMock wire an OpenAPI-mock-backed HTTP middleware for full-router integration tests (routing-with-api-mock.test.tsx) with deterministic, schema-shaped responses. - README.md documents the testing approach (Testing section). * test(cookbook-app-react-module): add Vitest coverage via vitest-plugin-react-app - vitest.config.ts uses defineProject from @equinor/fusion-framework-vitest-plugin-react-app/config. - App.test.tsx covers the demo module's resolved foo/bar values, and overriding configure to seed a different demo configuration. - fix(config.ts): setBar(() => 69) returned a plain number, not a valid ObservableInput, so BaseConfigBuilder silently discarded it and bar fell through to the module's own 10-second delayed default (5) instead of 69. setBar(async () => 69) matches setFoo's existing pattern and resolves immediately. * style: apply biome format/lint fixes repo-wide Runs `biome check --write --unsafe .` across the whole repo: - Wraps long lines exceeding the configured line width (mostly test files with chained assertions/JSX props). - cookbooks/app-react/src/config.ts: prefixes 3 intentionally-unused callback parameters (env, config, instance) with `_`, per lint/correctness/noUnusedFunctionParameters. No behavioral changes. * test(vitest-config): include coverage tracking for packages only * docs(cookbook-app-react-msal): clarify config.ts removal has no functional impact Corrects the test's doc comment, which claimed composing with a real configure that no longer exists, and documents the intentional removal in the changeset instead of silently dropping the note. Addresses review comment on PR #5293 (discussion_r3778886987). * docs(cookbook-app-react): document silenced lifecycle console.log in changeset The demo console.log calls were intentionally left commented out to keep Vitest output clean; the changeset previously claimed 'no runtime changes', which wasn't accurate. Documents the behavior change instead of restoring the logging. Addresses review comment on PR #5293 (discussion_r3778887017). * fix(cookbook-app-react-router): fix faker.date.past() with unfixed refDate faker.seed() only makes the pseudo-random offset deterministic; without a fixed refDate, date.past() still resolves relative to the current wall-clock time, so generated joinDate values drift across days despite the seeded-fixture contract. Adds a shared fakerRefDate constant used by both generateUser and the User.joinDate field faker. Addresses review comments on PR #5293 (discussion_r3778887040, discussion_r3778887110). * docs(cookbook-app-react-context): fix changeset API names The changeset named renderAppComponent and renderAppComponent.fusion.app, neither of which exists in the added tests; they use the plugin's render/app fixtures instead. Addresses review comment on PR #5293 (discussion_r3778887093). * fix(cookbook-app-react-router): add missing tsconfig project reference Adds a reference to packages/vite-plugins/raw-imports matching the new workspace dependency in package.json, so isolated package builds resolve it correctly. Addresses review comment on PR #5293 (discussion_r3778887191). * fix(turbo): include bin/build in cli package's cached build outputs The build task only declared dist/** as cacheable output, but packages/cli's rollup build writes its bundled CLI binaries to bin/build/*.mjs (gitignored, not under dist/). On a turbo cache hit in CI, bin/build/ was never restored, so any cookbook invoking the fusion-framework-cli binary during its own build failed with ERR_MODULE_NOT_FOUND for bin/build/cli.mjs. Reproduced locally by deleting bin/build and re-running turbo build without --force (cache hit skipped restoring it); fixed by adding bin/build/** to outputs. * test: migrate React tests to Vitest browser * docs(testing): publish Fusion app testing guide (#5296) * docs(testing): add canonical package guidance * feat(docs): map package links in VuePress * docs(testing): publish the app testing journey * fix(docs): repair internal documentation links * test(module-state): use browser PouchDB adapter * test: decouple browser test projects * fix(docs): scope package link rewrites * Refactor code structure for improved readability and maintainability * fix(pnpm): remove duplicate '@remix-run/router' entries from lockfile * fix(define-project): enhance dependency optimization for esbuild scanner * refactor(memfs-plugin): move mocks for fs/promises and import-script to the top for better organization * docs(advanced): document `test.override(...)` for fixture overriding in test files * chore: drop spurious react-app major changeset; ./vitest never shipped * chore: drop spurious cli major changeset; ffc app test never shipped * chore: fix two more false-major changesets; mock infra never released * Remove coverage configuration from vitest.config.ts * feat: add migration guides for transitioning to Fusion Vitest and explain Browser Mode default * test(cookbook-app-react-router): await styling before asserting active link color * style(vue-press): add missing trailing newline in vitest config --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why is this change needed?
The event module's config API was a plain mutable object (
config.event.onDispatch = ...), inconsistent with the fluentBaseConfigBuilderpattern used by every other configurator (e.g.MsalConfigurator,ServiceDiscoveryConfigurator). It also lacked test helpers for waiting on/collecting dispatched events, and itsevent$stream emitted events before dispatch instead of after.What is the current behavior?
config.event.onDispatch = handler/delete config.event.onBubble.IEventModuleConfiguratoris aninterface.event$emits the event synchronously before listeners/bubble hook run.dispatchEvent's typed overloads return the genericFrameworkEvent, discarding the registered event type.waitForEvent/watchEventstest helpers;filterEventonly available from the root export.docs/folder; the React bindings page lived atevent/react.mdinstead of alongsidereact/router/.What is the new behavior?
EventModuleConfiguratorextendsBaseConfigBuilder<EventModuleConfig>with fluentsetOnDispatch/setOnBubblesetters. The old property-assignment style still works but is marked@deprecated Since 6.1.0.IEventModuleConfiguratoris renamed toEventModuleConfig, converted from aninterfaceto atype;IEventModuleConfiguratorremains as a deprecated type alias.event$now emits only after listeners and the bubble hook have completed (in afinally, so it still emits on listener error), matching its documented contract.dispatchEvent's typed overloads now returnPromise<FrameworkEventMap[TType]>/Promise<TType>instead of the genericFrameworkEvent.waitForEvent/watchEventsutilities (./utilssubpath) and movedfilterEventunder a new./operatorssubpath (root export preserved).configurator.ts→EventModuleConfigurator.tsandprovider.ts→EventModuleProvider.tsfor filename-convention compliance.docs/folder (configuration, observable-patterns, lifecycle, testing) and trimmed the README to link to it.@includethe package's ownREADME.md/docs/*.md(matching thehttp/modulepattern) instead of duplicating content; the React bindings page moved toreact/event/README.mdalongsidereact/router/.What is the intended behavior or invariant?
event$subscribers always observe events after dispatch (listeners + bubble hook already ran) — callingpreventDefault/stopPropagationon an emitted event has no effect.onDispatch/onBubbleproperty assignment andIEventModuleConfiguratortype remain fully functional — this is additive, not breaking.Does this PR introduce a breaking change?
No.
Impact assessment:
@equinor/fusion-framework-module-event), Patch (@equinor/fusion-framework-docs).dispatchEventcalls now get narrower return types (strictly more precise, should not break existing code).tsc -b --forceacross direct consumers (module-app,module-bookmark,module-context,module-feature-flag,module-navigation,module-telemetry,module-widget,framework,app) with no errors.Review guidance:
EventModuleProvider._dispatchEvent's emit-after-dispatch ordering fix and thedispatchEventoverload return-type narrowing inEventModuleProvider.ts.Additional context
Three changesets included:
module-event_utils-helpers.md,module-event_configurator.md(both minor,@equinor/fusion-framework-module-event), anddocs_event-module-docs.md(patch,@equinor/fusion-framework-docs).Related issues
closes equinor/fusion-core-tasks#1656
Checklist
biome lint+fusion-lintclean)