feat(module-context): mock entry point, BaseConfigBuilder refactor, docs restructure - #5243
Merged
Conversation
🦋 Changeset detectedLatest commit: 579cd82 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 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 |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds network-free context testing while aligning the context module with established configurator and documentation patterns.
Changes:
- Refactors context configuration onto
BaseConfigBuilder. - Adds in-memory context mocks, fixtures, and framework integration.
- Restructures package and VuePress documentation.
Reviewed changes
Copilot reviewed 48 out of 49 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
vue-press/src/modules/context/README.md |
Includes package overview. |
vue-press/src/modules/context/docs/recipes.md |
Includes recipes documentation. |
vue-press/src/modules/context/docs/lifecycle.md |
Includes lifecycle documentation. |
vue-press/src/modules/context/docs/data-model.md |
Includes data-model documentation. |
vue-press/src/.vuepress/sidebar.ts |
Adds context documentation navigation. |
pnpm-lock.yaml |
Updates dependency resolution. |
packages/modules/context/vitest.config.ts |
Configures context tests. |
packages/modules/context/tsconfig.json |
Adds HTTP project reference. |
packages/modules/context/src/utils/resolve-initial-context.ts |
Updates config import. |
packages/modules/context/src/utils/parse-context-item.ts |
Updates type import. |
packages/modules/context/src/utils/index.ts |
Exports parseContextItem. |
packages/modules/context/src/utils/enable-context.ts |
Uses the new configurator callback. |
packages/modules/context/src/selectors/related-context-selector.ts |
Reorganizes selector imports. |
packages/modules/context/src/selectors/query-context-selector.ts |
Reorganizes selector imports. |
packages/modules/context/src/selectors/index.ts |
Adds selector barrel. |
packages/modules/context/src/selectors/get-context-selector.ts |
Reorganizes get selector. |
packages/modules/context/src/module.ts |
Uses the refactored configurator. |
packages/modules/context/src/mock/module.ts |
Defines mock module registration. |
packages/modules/context/src/mock/index.ts |
Exposes mock APIs. |
packages/modules/context/src/mock/fixtures/string-to-seed.ts |
Adds deterministic seed hashing. |
packages/modules/context/src/mock/fixtures/index.ts |
Exposes fixture APIs. |
packages/modules/context/src/mock/fixtures/create-context-items.ts |
Generates typed fixture batches. |
packages/modules/context/src/mock/fixtures/create-context-item-factory.ts |
Generates individual fixtures. |
packages/modules/context/src/mock/ContextMockConfigurator.ts |
Implements in-memory context configuration. |
packages/modules/context/src/index.ts |
Updates public exports. |
packages/modules/context/src/ContextProvider.ts |
Updates config import. |
packages/modules/context/src/ContextModuleConfigurator.ts |
Implements BaseConfigBuilder refactor. |
packages/modules/context/src/ContextModuleConfigurator.interface.ts |
Defines configurator contracts. |
packages/modules/context/src/ContextModuleConfig.ts |
Defines resolved configuration. |
packages/modules/context/src/ContextConfigBuilder.ts |
Removes legacy builder. |
packages/modules/context/src/configurator.ts |
Removes legacy configurator. |
packages/modules/context/src/__tests__/mock/create-context-items.test.ts |
Tests batch fixtures. |
packages/modules/context/src/__tests__/mock/create-context-item-factory.test.ts |
Tests fixture factory. |
packages/modules/context/src/__tests__/mock/context-mock.test.ts |
Tests mock provider behavior. |
packages/modules/context/src/__tests__/ContextModuleConfigurator.test.ts |
Tests configurator behavior. |
packages/modules/context/README.md |
Documents configuration and mocks. |
packages/modules/context/package.json |
Adds mock exports and dependencies. |
packages/modules/context/docs/recipes.md |
Adds configuration recipes. |
packages/modules/context/docs/lifecycle.md |
Documents context lifecycle. |
packages/modules/context/docs/data-model.md |
Documents context types and clients. |
packages/framework/src/mock/index.ts |
Re-exports context mock APIs. |
packages/framework/src/mock/FrameworkMockConfigurator.ts |
Pins the context mock module. |
packages/framework/docs/testing.md |
Documents context seeding. |
packages/framework/docs/testing-extending.md |
Documents mock extension behavior. |
packages/framework/docs/testing-api.md |
Expands mock API reference. |
.changeset/module-context_mock-entry-point.md |
Records context mock feature. |
.changeset/module-context_docs-restructure.md |
Records package documentation changes. |
.changeset/framework-context-mock.md |
Records framework mock integration. |
.changeset/docs_context-module-docs.md |
Records VuePress changes. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
odinr
added a commit
that referenced
this pull request
Aug 9, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 9, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 9, 2026
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).
…ernal 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.
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.
…-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.
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.
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).
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).
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).
odinr
force-pushed
the
feat/context-mock
branch
from
August 10, 2026 08:47
4cb793d to
579cd82
Compare
Noggling
approved these changes
Aug 10, 2026
odinr
added a commit
that referenced
this pull request
Aug 10, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 10, 2026
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).
Contributor
odinr
added a commit
that referenced
this pull request
Aug 13, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 13, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 13, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 14, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 14, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 14, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 16, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 16, 2026
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).
odinr
added a commit
that referenced
this pull request
Aug 16, 2026
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).
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?
@equinor/fusion-framework-module-contexthad no test double, soFrameworkMockConfigurator.contextstill hit the network in tests — the last core module without one. The configurator also predated theBaseConfigBuilderpattern used bymodule-event/module-http/module-msal, and its README had grown into a manual instead of an entry point.What is the current behavior?
/mockentry point; seeding a context in a test means faking a realContextClientor navigation module.ContextModuleConfiguratorpredatesBaseConfigBuilder; selector/parse-item files live loose atsrc/root.FrameworkMockConfigurator.contextreturns the real configurator — no test double, real I/O.docs/folder, unlikemodule/module-http.What is the new behavior?
@equinor/fusion-framework-module-context/mock(ContextMockConfigurator,enableContextMock) — in-memory context backed by the realContextProvider(validation/resolution/parent-propagation all still run for real).@equinor/fusion-framework-module-context/mock/fixtures(createContextItems,createContextItemFactory) — realistic fixtures via an optional@faker-js/fakerpeer dependency, kept off the main/mockentry point.ContextModuleConfiguratornow extendsBaseConfigBuilder;configurator.ts→ContextModuleConfigurator.ts, split intoContextModuleConfig.ts/ContextModuleConfigurator.interface.ts; selectors moved tosrc/selectors/;parseContextItemmoved tosrc/utils/and is now exported.FrameworkMockConfigurator.contextnow pinscontextMockModuleinstead of the real module.packages/modules/context/README.mdtrimmed to an entry point;docs/data-model.md,docs/lifecycle.md,docs/recipes.mdadded and mirrored in vue-press with an updated sidebar.What is the intended behavior or invariant?
The mock only substitutes the data source (
ContextClient) —validateContext,resolveContext, and parent/child context propagation always run through the realContextProvider/ContextModuleConfiguratorlogic, so a passing test against the mock reflects real resolution behavior, not a stubbed shortcut.enableContextMocknever requires@faker-js/faker; only importing/mock/fixturesdoes.Does this PR introduce a breaking change?
No public export names change on
@equinor/fusion-framework-module-context. On@equinor/fusion-framework,FrameworkMockConfigurator.context's return type changes fromContextModuleConfiguratortoContextMockConfigurator(which extends it) — code that only used documented configurator methods is unaffected; code relying on.contextperforming real I/O should switch to the new seeding methods.Impact assessment:
@equinor/fusion-framework-module-contextminor,@equinor/fusion-frameworkminor,@equinor/fusion-framework-docspatch (changesets included)/mockand/mock/fixturessubpaths;FrameworkMockConfigurator.contextbehavior improves (no more real I/O)Review guidance:
Five commits, each independently reviewable: (1)
BaseConfigBuilderrefactor/file reorg, (2) the new mock/fixtures entry points, (3) docs restructure, (4) framework's.contextaccessor switch, (5) changesets.Additional context
Full QA pass before pushing: Biome,
fusion-lint,tsc -b --force, and the full Vitest suite (767 passing; one pre-existing unrelated failure inreact-routerfrom a missing@equinor/fusion-importspackage).pnpm build:docs(vue-press) also builds cleanly.Related issues
Closes equinor/fusion-core-tasks#1658
Note: this PR's base is
feat/fusion-testing, notmain, so the closing keyword won't auto-fire on merge here — it needs to also appear on the eventualfeat/fusion-testing→mainPR (none open yet) for GitHub to close it automatically.Checklist