diff --git a/.changeset/module-context_fix-empty-context-warning.md b/.changeset/module-context_fix-empty-context-warning.md
new file mode 100644
index 0000000000..d9e31e7d2a
--- /dev/null
+++ b/.changeset/module-context_fix-empty-context-warning.md
@@ -0,0 +1,5 @@
+---
+"@equinor/fusion-framework-module-context": patch
+---
+
+Fix `ContextModule.postInitialize` logging a `console.warn` for the valid "no initial context" case (no context in the path and no parent context). The default `resolveInitialContext` resolver used RxJS `first()` without a default value, so completing with no emissions threw an `EmptyError` that got logged as if resolution had actually failed. `first()` now falls back to `undefined`, so a genuinely empty result completes silently and only real resolution failures are logged.
diff --git a/.changeset/react-app_render-app-component-testing.md b/.changeset/react-app_render-app-component-testing.md
index f66703e71e..1b072efa35 100644
--- a/.changeset/react-app_render-app-component-testing.md
+++ b/.changeset/react-app_render-app-component-testing.md
@@ -2,10 +2,10 @@
"@equinor/fusion-framework-react-app": minor
---
-Add `renderAppComponent` to the `/testing` entry-point, a component-level counterpart to `renderAppHook` for testing components (not just hooks) against a real, mock-backed application module scope.
+Add `renderAppComponent` to the `/vitest` entry-point, a component-level counterpart to `renderAppHook` for testing components (not just hooks) against a real, mock-backed application module scope.
```tsx
-import { renderAppComponent } from '@equinor/fusion-framework-react-app/testing';
+import { renderAppComponent } from '@equinor/fusion-framework-react-app/vitest';
import { waitFor } from '@testing-library/react';
import { Apploader } from '../apploader/Apploader';
@@ -14,3 +14,13 @@ await waitFor(() => expect(container.textContent).toContain('mounted'));
```
`renderAppComponent` wraps `@testing-library/react`'s `render` with the same `FrameworkProvider` + `ModuleProvider` nesting `renderAppHook` uses, backed by `mockFramework` and `mockAppModules` (`@equinor/fusion-framework-app/mock`), so tests can render a real component tree without hand-wiring those mocks.
+
+The result also carries a nested `app` object (`app.modules`, `app.fusion`) — nested rather than spread directly onto the result so `@testing-library/react`'s own return shape stays free to evolve without ever colliding with it — so a test can drive a module directly after the initial render and assert the component re-renders, instead of hand-wiring `mockAppModules`/`ModuleProvider` itself to reach the same instance:
+
+```tsx
+const { getByText, app } = await renderAppComponent<[ContextModule]>(, {
+ configure: (configurator) => enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
+});
+await act(() => app.modules.context.setCurrentContextByIdAsync(projectB.id));
+await waitFor(() => expect(getByText(/project-b/)).toBeInTheDocument());
+```
diff --git a/.changeset/react-app_render-app-hook-testing.md b/.changeset/react-app_render-app-hook-testing.md
index 7e3fc2b980..36ff75689c 100644
--- a/.changeset/react-app_render-app-hook-testing.md
+++ b/.changeset/react-app_render-app-hook-testing.md
@@ -2,10 +2,10 @@
"@equinor/fusion-framework-react-app": minor
---
-Add a `/testing` entry-point with `renderAppHook`, a pre-wrapped `renderHook` for testing app-scoped hooks (`useAppModule`, `useAccessToken`, etc.) against a real, mock-backed module and framework instance.
+Add a `/vitest` entry-point with `renderAppHook`, a pre-wrapped `renderHook` for testing app-scoped hooks (`useAppModule`, `useAccessToken`, etc.) against a real, mock-backed module and framework instance.
```tsx
-import { renderAppHook } from '@equinor/fusion-framework-react-app/testing';
+import { renderAppHook } from '@equinor/fusion-framework-react-app/vitest';
import { waitFor } from '@testing-library/react';
const { result } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }));
@@ -14,4 +14,6 @@ await waitFor(() => expect(result.current.pending).toBe(false));
`renderAppHook` wraps the hook with the same `FrameworkProvider` + `ModuleProvider` nesting `renderApp` uses in production, backed by `mockFramework` and `mockAppModules` (`@equinor/fusion-framework-app/mock`), so app teams no longer need to hand-wire those mocks in every test.
+The result also carries a nested `app` object (`app.modules`, `app.fusion`) — the same instances the hook rendered against, for driving a module the hook itself doesn't return — nested rather than spread directly onto the result so `@testing-library/react`'s own return shape stays free to evolve without ever colliding with it.
+
Requires `@testing-library/react` (added as an optional peer dependency).
diff --git a/.changeset/react_fix-usemodule-warning.md b/.changeset/react_fix-usemodule-warning.md
new file mode 100644
index 0000000000..9468df4fdf
--- /dev/null
+++ b/.changeset/react_fix-usemodule-warning.md
@@ -0,0 +1,5 @@
+---
+"@equinor/fusion-framework-react": patch
+---
+
+Fix `useFrameworkModule`'s console warning to name the actually-requested module key instead of always printing `undefined`.
diff --git a/.gitignore b/.gitignore
index fa283e0715..47a22456b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,6 +57,10 @@ typings/
# Optional eslint cache
.eslintcache
+# Vitest browser mode failure screenshots
+__screenshots__/
+.vitest-attachments/
+
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
diff --git a/packages/modules/context/package.json b/packages/modules/context/package.json
index 76d9462df6..4843e560f1 100644
--- a/packages/modules/context/package.json
+++ b/packages/modules/context/package.json
@@ -72,6 +72,7 @@
"@equinor/fusion-framework-module-http": "workspace:^",
"@equinor/fusion-framework-module-navigation": "workspace:^",
"@equinor/fusion-framework-module-services": "workspace:^",
+ "@equinor/fusion-framework-module-telemetry": "workspace:^",
"@faker-js/faker": "^10.1.0",
"rxjs": "^7.8.1",
"typescript": "^7.0.2"
diff --git a/packages/modules/context/src/ContextProvider.ts b/packages/modules/context/src/ContextProvider.ts
index 629ed70d60..794b89c79b 100644
--- a/packages/modules/context/src/ContextProvider.ts
+++ b/packages/modules/context/src/ContextProvider.ts
@@ -497,7 +497,6 @@ export class ContextProvider
.pipe(
// resolve context item from queue
switchMap((next) => next),
- tap((x) => console.debug('ContextProvider::#contextQueue', x)),
)
.subscribe((context) => {
// set context from resolved context item from queue
diff --git a/packages/modules/context/src/__tests__/ContextModuleConfigurator.test.ts b/packages/modules/context/src/__tests__/ContextModuleConfigurator.test.ts
index 101ab6770d..649a5fceb6 100644
--- a/packages/modules/context/src/__tests__/ContextModuleConfigurator.test.ts
+++ b/packages/modules/context/src/__tests__/ContextModuleConfigurator.test.ts
@@ -309,4 +309,15 @@ describe('ContextProvider through the real module system (http mocked at the net
expect(resolved.id).toBe('ctx-3');
});
+
+ it('resolves silently, without warning, when there is no initial context to resolve', async () => {
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const provider = await initializeContextWith();
+
+ expect(provider.currentContext).toBeUndefined();
+ expect(warnSpy).not.toHaveBeenCalled();
+
+ warnSpy.mockRestore();
+ });
});
diff --git a/packages/modules/context/src/module.ts b/packages/modules/context/src/module.ts
index db803bb0a6..16c576dcb0 100644
--- a/packages/modules/context/src/module.ts
+++ b/packages/modules/context/src/module.ts
@@ -5,6 +5,11 @@ import type { Module, ModulesInstance } from '@equinor/fusion-framework-module';
import type { EventModule } from '@equinor/fusion-framework-module-event';
import type { ServicesModule } from '@equinor/fusion-framework-module-services';
import type { NavigationModule } from '@equinor/fusion-framework-module-navigation';
+import {
+ TelemetryLevel,
+ TelemetryScope,
+ type TelemetryModule,
+} from '@equinor/fusion-framework-module-telemetry';
import {
type IContextModuleConfigurator,
@@ -34,7 +39,7 @@ export const moduleKey: ContextModuleKey = 'context';
* @typeParam ContextModuleKey - The unique key identifying the context module.
* @typeParam IContextProvider - The provider interface for context-related services.
* @typeParam IContextModuleConfigurator - The configurator interface for customizing the context module.
- * @typeParam [ServicesModule, EventModule, NavigationModule] - The tuple of dependent modules required by the context module.
+ * @typeParam [ServicesModule, EventModule, NavigationModule, TelemetryModule] - The tuple of dependent modules required by the context module.
*
* @see Module
*/
@@ -42,7 +47,7 @@ export type ContextModule = Module<
ContextModuleKey,
IContextProvider,
IContextModuleConfigurator,
- [ServicesModule, EventModule, NavigationModule]
+ [ServicesModule, EventModule, NavigationModule, TelemetryModule]
>;
/**
@@ -76,11 +81,16 @@ export const module: ContextModule = {
// get event module if available
const event = args.hasModule('event') ? await args.requireInstance('event') : undefined;
+ // get telemetry module if available, for tracking context resolution outcomes
+ const telemetry = args.hasModule('telemetry')
+ ? await args.requireInstance('telemetry')
+ : undefined;
+
// get parent context provider if available
const parentProvider = (args.ref as ModulesInstance<[ContextModule]>)?.context;
- // create context provider
- const provider = new ContextProvider({ config, event, parentContext: parentProvider });
+ // create context provider; parent context is wired up later via connectParentContext, not the deprecated ctor arg
+ const provider = new ContextProvider({ config, event });
// create subscription for disposing the provider
const subscription = new Subscription(() => provider.dispose());
@@ -109,27 +119,34 @@ export const module: ContextModule = {
resolveInitialContext$
.pipe(
catchError((err) => {
- console.warn(
- 'ContextModule.postInitialize',
- 'failed to resolve initial context',
- err,
- );
+ telemetry?.trackException({
+ name: 'Context::postInitialize.resolveInitialContext',
+ exception: err instanceof Error ? err : new Error(String(err)),
+ level: TelemetryLevel.Warning,
+ scope: ['context', TelemetryScope.Framework],
+ });
// failed to resolve initial context, complete immediately
return EMPTY;
}),
)
.subscribe({
next: (item) => {
- console.debug(
- 'ContextModule.postInitialize',
- `initial context was resolved to [${item ? item.id : 'none'}]`,
- item,
- );
+ telemetry?.trackEvent({
+ name: 'Context::postInitialize.initialContextResolved',
+ level: TelemetryLevel.Debug,
+ scope: ['context', TelemetryScope.Framework],
+ properties: { contextId: item ? item.id : 'none' },
+ });
},
complete: () => {
// connect parent context if available when stream completes
if (config.connectParentContext !== false && parentProvider) {
provider.connectParentContext(parentProvider);
+ telemetry?.trackEvent({
+ name: 'Context::postInitialize.parentContextConnected',
+ level: TelemetryLevel.Debug,
+ scope: ['context', TelemetryScope.Framework],
+ });
}
subscriber.complete();
},
diff --git a/packages/modules/context/src/utils/resolve-initial-context.ts b/packages/modules/context/src/utils/resolve-initial-context.ts
index 90acbbafb0..01037aac24 100644
--- a/packages/modules/context/src/utils/resolve-initial-context.ts
+++ b/packages/modules/context/src/utils/resolve-initial-context.ts
@@ -48,7 +48,10 @@ export const resolveInitialContext =
return concat(
pathname ? pathResolver(pathname) : EMPTY,
resolveContextFromParent({ ref, modules }),
- ).pipe(first());
+ ).pipe(
+ // having no initial context is valid; a default avoids first() throwing EmptyError for it
+ first(undefined, undefined),
+ );
};
export default resolveInitialContext;
diff --git a/packages/modules/context/tsconfig.json b/packages/modules/context/tsconfig.json
index 1c9918a9a0..afbfba6950 100644
--- a/packages/modules/context/tsconfig.json
+++ b/packages/modules/context/tsconfig.json
@@ -23,6 +23,9 @@
},
{
"path": "../services"
+ },
+ {
+ "path": "../telemetry"
}
],
"include": ["src/**/*"],
diff --git a/packages/modules/msal/src/versioning/resolve-version.ts b/packages/modules/msal/src/versioning/resolve-version.ts
index 4968b0a3ba..1f3c8a9550 100644
--- a/packages/modules/msal/src/versioning/resolve-version.ts
+++ b/packages/modules/msal/src/versioning/resolve-version.ts
@@ -29,7 +29,6 @@ import { version as latestVersionString } from '../version';
* ```
*/
function mapVersionToEnumVersion(version: string | SemVer): MsalModuleVersion {
- console.log('Resolving version:', version);
const coercedVersion = semver.coerce(version);
// An uncoercible version string cannot be mapped to a module version
if (!coercedVersion) {
diff --git a/packages/react/app/README.md b/packages/react/app/README.md
index f606580068..3e6b8ce3a1 100644
--- a/packages/react/app/README.md
+++ b/packages/react/app/README.md
@@ -175,7 +175,7 @@ production, just with the network boundary faked for requests a seeded middlewar
network.
```tsx
-import { renderAppHook } from '@equinor/fusion-framework-react-app/testing';
+import { renderAppHook } from '@equinor/fusion-framework-react-app/vitest';
import { waitFor } from '@testing-library/react';
import { useAccessToken } from '@equinor/fusion-framework-react-app/msal';
@@ -218,7 +218,7 @@ test('resolves an access token', async () => {
| `/apploader` | `Apploader`, `useApploader` |
| `/framework` | `useFramework`, `useCurrentUser`, `useFrameworkHttpClient` |
| `/widget` | Widget entry-point |
-| `/testing` | `renderAppHook`, `renderAppComponent` — pre-wrapped `renderHook`/`render` for testing app-scoped hooks and components |
+| `/vitest` | `renderAppHook`, `renderAppComponent` — pre-wrapped `renderHook`/`render` for testing app-scoped hooks and components |
## Configuration
diff --git a/packages/react/app/docs/testing.md b/packages/react/app/docs/testing.md
index bcd8519d35..94f70ca89c 100644
--- a/packages/react/app/docs/testing.md
+++ b/packages/react/app/docs/testing.md
@@ -6,7 +6,7 @@ scope using `renderAppHook` and `renderAppComponent`.
**Import:**
```ts
-import { renderAppHook, renderAppComponent } from '@equinor/fusion-framework-react-app/testing';
+import { renderAppHook, renderAppComponent } from '@equinor/fusion-framework-react-app/vitest';
```
> [!IMPORTANT]
@@ -52,7 +52,7 @@ function renderAppHook, 'wrapper'>,
-): Promise>;
+): Promise & { fusion: { framework: Fusion; app: AppModulesInstance } }>;
```
| Option | Description |
@@ -61,12 +61,14 @@ function renderAppHook {
Pass `configure` to reach the msal mock's builder before the hook renders:
```tsx
-import { renderAppHook } from '@equinor/fusion-framework-react-app/testing';
+import { renderAppHook } from '@equinor/fusion-framework-react-app/vitest';
import { useCurrentAccount } from '@equinor/fusion-framework-react-app/msal';
test('reads the configured account', async () => {
@@ -103,7 +105,7 @@ calls:
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
import type { AppModule } from '@equinor/fusion-framework-module-app';
-import { renderAppHook } from '@equinor/fusion-framework-react-app/testing';
+import { renderAppHook } from '@equinor/fusion-framework-react-app/vitest';
import { useAccessToken } from '@equinor/fusion-framework-react-app/msal';
import { useCurrentAccount } from '@equinor/fusion-framework-react-app/msal';
@@ -135,11 +137,34 @@ function renderAppComponent(
env?: TEnv;
fusion?: Fusion;
} & Omit,
-): Promise;
+): Promise } }>;
```
Options are the same shape as `renderAppHook`'s — `configure`, `env`, `fusion` — plus any
-other `@testing-library/react` `render` option.
+other `@testing-library/react` `render` option. The result carries the usual `render` return
+values (`getByText`, `container`, `unmount`, ...) plus `fusion` — nested rather than spread
+directly onto the result, so `@testing-library/react`'s own return shape stays free to evolve
+without ever colliding with it. `fusion.app` is the same application module instance the
+rendered component reads through `useAppModule`/`useAppModules`, and `fusion.framework` is the
+parent Fusion instance. Drive a module directly through `fusion.app` to exercise a state
+change after the initial render, without hand-wiring `mockAppModules`/`ModuleProvider`:
+
+```tsx
+import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
+import type { ContextModule } from '@equinor/fusion-framework-module-context';
+import { act, waitFor } from '@testing-library/react';
+import { renderAppComponent } from '@equinor/fusion-framework-react-app/vitest';
+
+test('reacts when the current context switches', async () => {
+ const { getByText, fusion } = await renderAppComponent<[ContextModule]>(, {
+ configure: (configurator) =>
+ enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
+ });
+
+ await act(() => fusion.app.context.setCurrentContextByIdAsync(projectB.id));
+ await waitFor(() => expect(getByText(/project-b/)).toBeInTheDocument());
+});
+```
### Example: Asserting Loading and Error States
@@ -148,7 +173,7 @@ import { waitFor } from '@testing-library/react';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
import type { AppManifest, AppModule } from '@equinor/fusion-framework-module-app';
-import { renderAppComponent } from '@equinor/fusion-framework-react-app/testing';
+import { renderAppComponent } from '@equinor/fusion-framework-react-app/vitest';
import { Apploader } from '@equinor/fusion-framework-react-app/apploader';
test('mounts the child app once its script loads', async () => {
diff --git a/packages/react/app/package.json b/packages/react/app/package.json
index 45c36490f3..4654f2360e 100644
--- a/packages/react/app/package.json
+++ b/packages/react/app/package.json
@@ -65,9 +65,9 @@
"types": "./dist/types/routing/index.d.ts",
"import": "./dist/esm/routing/index.js"
},
- "./testing": {
- "types": "./dist/types/testing/index.d.ts",
- "import": "./dist/esm/testing/index.js"
+ "./vitest": {
+ "types": "./dist/types/vitest/index.d.ts",
+ "import": "./dist/esm/vitest/index.js"
}
},
"typesVersions": {
@@ -114,8 +114,8 @@
"routing": [
"dist/types/routing/index.d.ts"
],
- "testing": [
- "dist/types/testing/index.d.ts"
+ "vitest": [
+ "dist/types/vitest/index.d.ts"
]
}
},
@@ -149,34 +149,35 @@
"devDependencies": {
"@equinor/fusion-framework-module-ag-grid": "workspace:*",
"@equinor/fusion-framework-module-analytics": "workspace:*",
- "@equinor/fusion-framework-module-event": "workspace:*",
- "@equinor/fusion-framework-module-feature-flag": "workspace:*",
"@equinor/fusion-framework-module-bookmark": "workspace:*",
"@equinor/fusion-framework-module-context": "workspace:*",
+ "@equinor/fusion-framework-module-event": "workspace:*",
+ "@equinor/fusion-framework-module-feature-flag": "workspace:*",
"@equinor/fusion-framework-module-msal": "workspace:*",
"@equinor/fusion-framework-module-telemetry": "workspace:*",
"@equinor/fusion-framework-react-module-bookmark": "workspace:*",
"@equinor/fusion-framework-react-module-context": "workspace:*",
"@equinor/fusion-framework-react-router": "workspace:*",
"@equinor/fusion-observable": "workspace:*",
- "@testing-library/react": "^16.0.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
- "happy-dom": "^20.8.4",
+ "@vitest/browser-playwright": "^4.1.0",
+ "playwright": "^1.62.1",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"rxjs": "^7.8.1",
"typescript": "^7.0.2",
- "vitest": "^4.1.0"
+ "vitest": "^4.1.10",
+ "vitest-browser-react": "^2.2.0"
},
"peerDependencies": {
"@equinor/fusion-framework-module-msal": "workspace:^",
"@equinor/fusion-framework-react-router": "workspace:^",
- "@testing-library/react": "^16.0.0",
"@types/react": "^18.0.0 || ^19.0.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
- "rxjs": "^7.0.0"
+ "rxjs": "^7.0.0",
+ "vitest-browser-react": "^2.2.0"
},
"peerDependenciesMeta": {
"@equinor/fusion-framework-react-module-ag-grid": {
@@ -209,7 +210,7 @@
"@equinor/fusion-framework-react-router": {
"optional": true
},
- "@testing-library/react": {
+ "vitest-browser-react": {
"optional": true
}
}
diff --git a/packages/react/app/src/__tests__/Apploader.test.tsx b/packages/react/app/src/__tests__/Apploader.test.tsx
index fb2b6b8fd2..e29f1af1cd 100644
--- a/packages/react/app/src/__tests__/Apploader.test.tsx
+++ b/packages/react/app/src/__tests__/Apploader.test.tsx
@@ -1,12 +1,11 @@
-import { describe, expect, it } from 'vitest';
-import { waitFor } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
import type { AppManifest, AppModule } from '@equinor/fusion-framework-module-app';
import { Apploader } from '../apploader/Apploader';
-import { renderAppComponent } from '../testing/render-app-component';
+import { renderAppComponent } from '../vitest/render-app-component';
// resolved from this test file rather than hardcoded, so it survives a package move
const fixturesUri = new URL('./fixtures', import.meta.url).pathname;
@@ -29,7 +28,7 @@ describe('Apploader', () => {
// the loading state renders synchronously, before the script's dynamic import resolves
expect(container.textContent).toContain('Loading child-app');
- await waitFor(() => expect(container.textContent).toContain('mounted: child-app'));
+ await vi.waitFor(() => expect(container.textContent).toContain('mounted: child-app'));
});
it('surfaces the load error instead of throwing when the app’s script fails to import', async () => {
@@ -47,6 +46,6 @@ describe('Apploader', () => {
const { container } = await renderAppComponent(, { fusion });
- await waitFor(() => expect(container.textContent).toContain('Error loading broken-app'));
+ await vi.waitFor(() => expect(container.textContent).toContain('Error loading broken-app'));
});
});
diff --git a/packages/react/app/src/__tests__/useAccessToken.test.tsx b/packages/react/app/src/__tests__/useAccessToken.test.tsx
index 3212a38241..899d13a1ab 100644
--- a/packages/react/app/src/__tests__/useAccessToken.test.tsx
+++ b/packages/react/app/src/__tests__/useAccessToken.test.tsx
@@ -1,10 +1,9 @@
import { describe, expect, it, vi } from 'vitest';
-import { waitFor } from '@testing-library/react';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { useAccessToken } from '../msal/useAccessToken';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
describe('useAccessToken', () => {
it('resolves an access token from the app scope’s real, mock-backed auth module', async () => {
@@ -14,10 +13,9 @@ describe('useAccessToken', () => {
useAccessToken({ scopes: ['User.Read'] }),
);
- expect(result.current.pending).toBe(true);
- expect(result.current.token).toBeUndefined();
-
- await waitFor(() => expect(result.current.pending).toBe(false));
+ // `renderAppHook` awaits the render, and the mock resolves near-instantly,
+ // so the pending state may have already settled by the time we can observe it
+ await vi.waitFor(() => expect(result.current.pending).toBe(false));
expect(result.current.error).toBeNull();
// a structurally valid JWT has three dot-separated segments
@@ -25,7 +23,7 @@ describe('useAccessToken', () => {
// unmount before the next test's environment tears down, so no acquisition
// effect can flush a state update against an already-destroyed `window`
- unmount();
+ await unmount();
});
it('surfaces an acquisition error instead of throwing', async () => {
@@ -43,11 +41,11 @@ describe('useAccessToken', () => {
},
);
- await waitFor(() => expect(result.current.pending).toBe(false));
+ await vi.waitFor(() => expect(result.current.pending).toBe(false));
expect(result.current.token).toBeUndefined();
expect(result.current.error).toBeInstanceOf(Error);
- unmount();
+ await unmount();
});
});
diff --git a/packages/react/app/src/__tests__/useAppSetting.test.tsx b/packages/react/app/src/__tests__/useAppSetting.test.tsx
index 26004aee22..308a934116 100644
--- a/packages/react/app/src/__tests__/useAppSetting.test.tsx
+++ b/packages/react/app/src/__tests__/useAppSetting.test.tsx
@@ -1,4 +1,4 @@
-import { act, waitFor } from '@testing-library/react';
+import { act } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { mockFramework } from '@equinor/fusion-framework/mock';
@@ -7,7 +7,7 @@ import type { AppModule } from '@equinor/fusion-framework-module-app';
import { createRouterMiddleware } from '@equinor/fusion-framework-module-http/mock';
import { useAppSetting } from '../settings/useAppSetting';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
const env = {
manifest: {
@@ -59,7 +59,7 @@ describe('useAppSetting', () => {
{ env, fusion },
);
- await waitFor(() => expect(result.current[0]).toBe('dark'));
+ await vi.waitFor(() => expect(result.current[0]).toBe('dark'));
});
it('falls back to the default value until the persisted settings resolve', async () => {
@@ -71,7 +71,7 @@ describe('useAppSetting', () => {
);
expect(result.current[0]).toBe('light');
- await waitFor(() => expect(result.current[0]).toBe('dark'));
+ await vi.waitFor(() => expect(result.current[0]).toBe('dark'));
});
it('persists an updated value and notifies onUpdated', async () => {
@@ -83,14 +83,14 @@ describe('useAppSetting', () => {
{ env, fusion },
);
- await waitFor(() => expect(result.current[0]).toBe('dark'));
+ await vi.waitFor(() => expect(result.current[0]).toBe('dark'));
act(() => {
result.current[1]('light');
});
- await waitFor(() => expect(result.current[0]).toBe('light'));
- await waitFor(() => expect(onUpdated).toHaveBeenCalled());
+ await vi.waitFor(() => expect(result.current[0]).toBe('light'));
+ await vi.waitFor(() => expect(onUpdated).toHaveBeenCalled());
});
it('resolves the next value from the previous one when given an updater function', async () => {
@@ -101,13 +101,13 @@ describe('useAppSetting', () => {
{ env, fusion },
);
- await waitFor(() => expect(result.current[0]).toBe(1));
+ await vi.waitFor(() => expect(result.current[0]).toBe(1));
act(() => {
result.current[1]((current) => (current ?? 0) + 1);
});
- await waitFor(() => expect(result.current[0]).toBe(2));
+ await vi.waitFor(() => expect(result.current[0]).toBe(2));
});
it('surfaces a persistence failure through onError instead of throwing', async () => {
@@ -122,12 +122,12 @@ describe('useAppSetting', () => {
{ env, fusion },
);
- await waitFor(() => expect(result.current[0]).toBe('dark'));
+ await vi.waitFor(() => expect(result.current[0]).toBe('dark'));
act(() => {
result.current[1]('light');
});
- await waitFor(() => expect(onError).toHaveBeenCalledWith(expect.any(Error)));
+ await vi.waitFor(() => expect(onError).toHaveBeenCalledWith(expect.any(Error)));
});
});
diff --git a/packages/react/app/src/__tests__/useAppSettings.test.tsx b/packages/react/app/src/__tests__/useAppSettings.test.tsx
index 1bd8687a58..4ecfee259b 100644
--- a/packages/react/app/src/__tests__/useAppSettings.test.tsx
+++ b/packages/react/app/src/__tests__/useAppSettings.test.tsx
@@ -1,4 +1,4 @@
-import { act, waitFor } from '@testing-library/react';
+import { act } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { mockFramework } from '@equinor/fusion-framework/mock';
@@ -7,7 +7,7 @@ import type { AppModule } from '@equinor/fusion-framework-module-app';
import { createRouterMiddleware } from '@equinor/fusion-framework-module-http/mock';
import { useAppSettings } from '../settings/useAppSettings';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
const env = {
manifest: {
@@ -61,7 +61,9 @@ describe('useAppSettings', () => {
const { result } = await renderAppHook(() => useAppSettings(), { env, fusion });
- await waitFor(() => expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }));
+ await vi.waitFor(() =>
+ expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }),
+ );
});
it('falls back to the default value until the persisted settings resolve', async () => {
@@ -73,7 +75,9 @@ describe('useAppSettings', () => {
);
expect(result.current[0]).toMatchObject({ theme: 'light', layout: 'list' });
- await waitFor(() => expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }));
+ await vi.waitFor(() =>
+ expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }),
+ );
});
it('persists an updated settings object and notifies onUpdated', async () => {
@@ -85,16 +89,18 @@ describe('useAppSettings', () => {
{ env, fusion },
);
- await waitFor(() => expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }));
+ await vi.waitFor(() =>
+ expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }),
+ );
act(() => {
result.current[1]({ theme: 'light', layout: 'grid' });
});
- await waitFor(() =>
+ await vi.waitFor(() =>
expect(result.current[0]).toMatchObject({ theme: 'light', layout: 'grid' }),
);
- await waitFor(() => expect(onUpdated).toHaveBeenCalled());
+ await vi.waitFor(() => expect(onUpdated).toHaveBeenCalled());
});
it('resolves the next settings object from the previous one when given an updater function', async () => {
@@ -102,14 +108,18 @@ describe('useAppSettings', () => {
const { result } = await renderAppHook(() => useAppSettings(), { env, fusion });
- await waitFor(() => expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }));
+ await vi.waitFor(() =>
+ expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }),
+ );
act(() => {
// `current` is typed as possibly undefined even though it's already resolved by this point
result.current[1]((current) => ({ theme: current?.theme ?? 'dark', layout: 'list' }));
});
- await waitFor(() => expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'list' }));
+ await vi.waitFor(() =>
+ expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'list' }),
+ );
});
it('surfaces a persistence failure through onError instead of throwing', async () => {
@@ -124,12 +134,14 @@ describe('useAppSettings', () => {
{ env, fusion },
);
- await waitFor(() => expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }));
+ await vi.waitFor(() =>
+ expect(result.current[0]).toMatchObject({ theme: 'dark', layout: 'grid' }),
+ );
act(() => {
result.current[1]({ theme: 'light', layout: 'grid' });
});
- await waitFor(() => expect(onError).toHaveBeenCalledWith(expect.any(Error)));
+ await vi.waitFor(() => expect(onError).toHaveBeenCalledWith(expect.any(Error)));
});
});
diff --git a/packages/react/app/src/__tests__/useCurrentAccount.test.tsx b/packages/react/app/src/__tests__/useCurrentAccount.test.tsx
index 43df9fe815..efa6cd3445 100644
--- a/packages/react/app/src/__tests__/useCurrentAccount.test.tsx
+++ b/packages/react/app/src/__tests__/useCurrentAccount.test.tsx
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import { useCurrentAccount } from '../msal/useCurrentAccount';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
describe('useCurrentAccount', () => {
it('returns the default mock user signed in by the app scope’s auth module', async () => {
diff --git a/packages/react/app/src/__tests__/useCurrentBookmark.test.tsx b/packages/react/app/src/__tests__/useCurrentBookmark.test.tsx
index 9f56fa0080..1fb3ec18d0 100644
--- a/packages/react/app/src/__tests__/useCurrentBookmark.test.tsx
+++ b/packages/react/app/src/__tests__/useCurrentBookmark.test.tsx
@@ -1,5 +1,4 @@
import { describe, expect, it, vi } from 'vitest';
-import { waitFor } from '@testing-library/react';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
@@ -9,7 +8,7 @@ import { enableBookmarkMock } from '@equinor/fusion-framework-module-bookmark/mo
import type { Bookmark, BookmarkModule } from '@equinor/fusion-framework-module-bookmark';
import { useCurrentBookmark } from '../bookmark/useCurrentBookmark';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
import { useAppModules } from '../useAppModules';
const env = {
@@ -47,7 +46,9 @@ describe('useCurrentBookmark', () => {
const { result } = await renderAppHook(() => useCurrentBookmark(), { env, fusion, configure });
- await waitFor(() => expect(result.current.currentBookmark).toMatchObject({ id: bookmark.id }));
+ await vi.waitFor(() =>
+ expect(result.current.currentBookmark).toMatchObject({ id: bookmark.id }),
+ );
});
it('hides the current bookmark once it belongs to a different app', async () => {
@@ -73,7 +74,7 @@ describe('useCurrentBookmark', () => {
{ env, fusion, configure },
);
- await waitFor(() =>
+ await vi.waitFor(() =>
expect(result.current.provider.currentBookmark).toMatchObject({ id: bookmark.id }),
);
expect(result.current.bookmark.currentBookmark).toBeNull();
@@ -94,7 +95,9 @@ describe('useCurrentBookmark', () => {
const { result } = await renderAppHook(() => useCurrentBookmark(), { env, fusion });
- await waitFor(() => expect(result.current.currentBookmark).toMatchObject({ id: bookmark.id }));
+ await vi.waitFor(() =>
+ expect(result.current.currentBookmark).toMatchObject({ id: bookmark.id }),
+ );
expect(warnSpy).toHaveBeenCalledWith(
'@deprecation',
expect.stringContaining('has not enabled bookmarks'),
diff --git a/packages/react/app/src/__tests__/useCurrentContext.test.tsx b/packages/react/app/src/__tests__/useCurrentContext.test.tsx
index f93ca82495..df4ced9d1f 100644
--- a/packages/react/app/src/__tests__/useCurrentContext.test.tsx
+++ b/packages/react/app/src/__tests__/useCurrentContext.test.tsx
@@ -1,5 +1,5 @@
-import { act, waitFor } from '@testing-library/react';
-import { describe, expect, it } from 'vitest';
+import { act } from 'react';
+import { describe, expect, it, vi } from 'vitest';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
@@ -9,7 +9,7 @@ import { enableContextMock } from '@equinor/fusion-framework-module-context/mock
import type { ContextItem, ContextModule } from '@equinor/fusion-framework-module-context';
import { useCurrentContext } from '../context/useCurrentContext';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
const env = {
manifest: {
@@ -45,7 +45,7 @@ describe('useCurrentContext', () => {
const { result } = await renderAppHook(() => useCurrentContext(), { env, fusion, configure });
- await waitFor(() => expect(result.current.currentContext).toMatchObject({ id: project.id }));
+ await vi.waitFor(() => expect(result.current.currentContext).toMatchObject({ id: project.id }));
});
it('switches the current context when setCurrentContext is called with another seeded item', async () => {
@@ -59,12 +59,14 @@ describe('useCurrentContext', () => {
});
const { result } = await renderAppHook(() => useCurrentContext(), { env, fusion, configure });
- await waitFor(() => expect(result.current.currentContext).toMatchObject({ id: project.id }));
+ await vi.waitFor(() => expect(result.current.currentContext).toMatchObject({ id: project.id }));
await act(async () => {
await result.current.setCurrentContext(facility.id);
});
- await waitFor(() => expect(result.current.currentContext).toMatchObject({ id: facility.id }));
+ await vi.waitFor(() =>
+ expect(result.current.currentContext).toMatchObject({ id: facility.id }),
+ );
});
});
diff --git a/packages/react/app/src/__tests__/useFeature.test.tsx b/packages/react/app/src/__tests__/useFeature.test.tsx
index 0d5f4cb01e..eed953c0e7 100644
--- a/packages/react/app/src/__tests__/useFeature.test.tsx
+++ b/packages/react/app/src/__tests__/useFeature.test.tsx
@@ -1,5 +1,5 @@
-import { act, waitFor } from '@testing-library/react';
-import { describe, expect, it } from 'vitest';
+import { act } from 'react';
+import { describe, expect, it, vi } from 'vitest';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
@@ -9,7 +9,7 @@ import { enableFeatureFlagMock } from '@equinor/fusion-framework-module-feature-
import type { FeatureFlagModule } from '@equinor/fusion-framework-module-feature-flag';
import { useFeature } from '../feature-flag/useFeature';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
const env = {
manifest: {
@@ -37,7 +37,7 @@ describe('useFeature', () => {
configure,
});
- await waitFor(() => expect(result.current.feature?.enabled).toBe(true));
+ await vi.waitFor(() => expect(result.current.feature?.enabled).toBe(true));
});
it('lets an app-scoped flag override a framework-scoped flag with the same key', async () => {
@@ -59,7 +59,7 @@ describe('useFeature', () => {
configure,
});
- await waitFor(() => expect(result.current.feature?.enabled).toBe(true));
+ await vi.waitFor(() => expect(result.current.feature?.enabled).toBe(true));
});
it('exposes a framework-only flag through the merged stream when the app has not seeded it', async () => {
@@ -79,7 +79,7 @@ describe('useFeature', () => {
configure,
});
- await waitFor(() => expect(result.current.feature?.enabled).toBe(true));
+ await vi.waitFor(() => expect(result.current.feature?.enabled).toBe(true));
});
it('inverts the current value when toggleFeature is called without an explicit value', async () => {
@@ -93,12 +93,12 @@ describe('useFeature', () => {
};
const { result } = await renderAppHook(() => useFeature('my-flag'), { env, fusion, configure });
- await waitFor(() => expect(result.current.feature?.enabled).toBe(false));
+ await vi.waitFor(() => expect(result.current.feature?.enabled).toBe(false));
act(() => {
result.current.toggleFeature();
});
- await waitFor(() => expect(result.current.feature?.enabled).toBe(true));
+ await vi.waitFor(() => expect(result.current.feature?.enabled).toBe(true));
});
});
diff --git a/packages/react/app/src/__tests__/useHelpCenter.test.tsx b/packages/react/app/src/__tests__/useHelpCenter.test.tsx
index 2723b1cda9..b7026809fe 100644
--- a/packages/react/app/src/__tests__/useHelpCenter.test.tsx
+++ b/packages/react/app/src/__tests__/useHelpCenter.test.tsx
@@ -1,5 +1,4 @@
-import { waitFor } from '@testing-library/react';
-import { describe, expect, it } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
@@ -8,7 +7,7 @@ import type { AppModule } from '@equinor/fusion-framework-module-app';
import useAppModule from '../useAppModule';
import { useHelpCenter } from '../help-center/useHelpCenter';
import { EVENT_NAME } from '../help-center/event-name.js';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
const env = {
manifest: {
@@ -36,22 +35,22 @@ describe('useHelpCenter', () => {
});
result.current.helpCenter.openHelp();
- await waitFor(() => expect(received).toHaveLength(1));
+ await vi.waitFor(() => expect(received).toHaveLength(1));
result.current.helpCenter.openArticle('my-article');
- await waitFor(() => expect(received).toHaveLength(2));
+ await vi.waitFor(() => expect(received).toHaveLength(2));
result.current.helpCenter.openFaqs();
- await waitFor(() => expect(received).toHaveLength(3));
+ await vi.waitFor(() => expect(received).toHaveLength(3));
result.current.helpCenter.openSearch('my search');
- await waitFor(() => expect(received).toHaveLength(4));
+ await vi.waitFor(() => expect(received).toHaveLength(4));
result.current.helpCenter.openGovernance();
- await waitFor(() => expect(received).toHaveLength(5));
+ await vi.waitFor(() => expect(received).toHaveLength(5));
result.current.helpCenter.openReleaseNotes();
- await waitFor(() => expect(received).toHaveLength(6));
+ await vi.waitFor(() => expect(received).toHaveLength(6));
expect(received).toEqual([
{ page: 'home' },
diff --git a/packages/react/app/src/__tests__/useToken.test.tsx b/packages/react/app/src/__tests__/useToken.test.tsx
index 8a2aff1295..73926d94ab 100644
--- a/packages/react/app/src/__tests__/useToken.test.tsx
+++ b/packages/react/app/src/__tests__/useToken.test.tsx
@@ -1,19 +1,17 @@
import { describe, expect, it, vi } from 'vitest';
-import { waitFor } from '@testing-library/react';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { useToken } from '../msal/useToken';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
describe('useToken', () => {
it('resolves a full AuthenticationResult from the app scope’s real, mock-backed auth module', async () => {
const { result, unmount } = await renderAppHook(() => useToken({ scopes: ['User.Read'] }));
- expect(result.current.pending).toBe(true);
- expect(result.current.token).toBeUndefined();
-
- await waitFor(() => expect(result.current.pending).toBe(false));
+ // `renderAppHook` awaits the render, and the mock resolves near-instantly,
+ // so the pending state may have already settled by the time we can observe it
+ await vi.waitFor(() => expect(result.current.pending).toBe(false));
expect(result.current.error).toBeNull();
expect(result.current.token?.scopes).toEqual(['User.Read']);
@@ -23,7 +21,7 @@ describe('useToken', () => {
// unmount before the next test's environment tears down, so no acquisition
// effect can flush a state update against an already-destroyed `window`
- unmount();
+ await unmount();
});
it('surfaces an acquisition error instead of throwing', async () => {
@@ -38,11 +36,11 @@ describe('useToken', () => {
fusion,
});
- await waitFor(() => expect(result.current.pending).toBe(false));
+ await vi.waitFor(() => expect(result.current.pending).toBe(false));
expect(result.current.token).toBeUndefined();
expect(result.current.error).toBeInstanceOf(Error);
- unmount();
+ await unmount();
});
});
diff --git a/packages/react/app/src/__tests__/useTrackFeature.test.tsx b/packages/react/app/src/__tests__/useTrackFeature.test.tsx
index ea8deb2c8d..e44be3af91 100644
--- a/packages/react/app/src/__tests__/useTrackFeature.test.tsx
+++ b/packages/react/app/src/__tests__/useTrackFeature.test.tsx
@@ -8,7 +8,7 @@ import { MockAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics
import type { MockTelemetryAdapter } from '@equinor/fusion-framework-module-telemetry/mock';
import { useTrackFeature } from '../analytics/useTrackFeature';
-import { renderAppHook } from '../testing/render-app-hook';
+import { renderAppHook } from '../vitest/render-app-hook';
const env = {
manifest: {
diff --git a/packages/react/app/src/msal/useToken.ts b/packages/react/app/src/msal/useToken.ts
index 3078deea93..04a3ec4a73 100644
--- a/packages/react/app/src/msal/useToken.ts
+++ b/packages/react/app/src/msal/useToken.ts
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import type { AuthenticationResult } from '@equinor/fusion-framework-module-msal';
@@ -32,11 +32,20 @@ export const useToken = (req: {
const [token, setToken] = useState(undefined);
const [pending, setPending] = useState(false);
const [error, setError] = useState(null);
+
+ // `req` is typically a fresh object literal each render; key the effect on its
+ // scopes' content instead of identity, so it doesn't re-run (and reset `pending`
+ // to `true` forever) every time the caller re-renders.
+ const scopesKey = req.scopes.join(',');
+ const reqRef = useRef(req);
+ reqRef.current = req;
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: scopesKey is a re-run trigger, not read in the body
useEffect(() => {
setPending(true);
setToken(undefined);
msalProvider
- .acquireToken({ request: req })
+ .acquireToken({ request: reqRef.current })
.then((result) => {
// Only update state when a token was actually acquired
if (result) {
@@ -45,7 +54,7 @@ export const useToken = (req: {
})
.catch(setError)
.finally(() => setPending(false));
- }, [msalProvider, req]);
+ }, [msalProvider, scopesKey]);
return { token, pending, error };
};
diff --git a/packages/react/app/src/testing/index.ts b/packages/react/app/src/testing/index.ts
deleted file mode 100644
index 32f2cd51fd..0000000000
--- a/packages/react/app/src/testing/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { renderAppHook, type RenderAppHookOptions } from './render-app-hook';
-export { renderAppComponent, type RenderAppComponentOptions } from './render-app-component';
diff --git a/packages/react/app/src/vitest/index.ts b/packages/react/app/src/vitest/index.ts
new file mode 100644
index 0000000000..4b205900ee
--- /dev/null
+++ b/packages/react/app/src/vitest/index.ts
@@ -0,0 +1,14 @@
+export {
+ renderAppHook,
+ type RenderAppHookOptions,
+ type RenderAppHookResult,
+} from './render-app-hook';
+export {
+ renderAppComponent,
+ type RenderAppComponentOptions,
+ type RenderAppComponentResult,
+} from './render-app-component';
+export { testApp } from './test-app';
+export { test } from './test';
+export { render } from './render';
+export type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
diff --git a/packages/react/app/src/testing/render-app-component.tsx b/packages/react/app/src/vitest/render-app-component.tsx
similarity index 50%
rename from packages/react/app/src/testing/render-app-component.tsx
rename to packages/react/app/src/vitest/render-app-component.tsx
index 99d9210054..cfcc6fcf91 100644
--- a/packages/react/app/src/testing/render-app-component.tsx
+++ b/packages/react/app/src/vitest/render-app-component.tsx
@@ -1,24 +1,13 @@
import type { ReactElement } from 'react';
-import { render } from '@testing-library/react';
-import type { RenderOptions, RenderResult } from '@testing-library/react';
+import { render } from 'vitest-browser-react';
+import type { RenderOptions, RenderResult } from 'vitest-browser-react';
-import { mockAppModules, enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
-import type { AppEnv } from '@equinor/fusion-framework-app';
+import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
import type { Fusion } from '@equinor/fusion-framework';
-import { mockFramework } from '@equinor/fusion-framework/mock';
import type { AnyModule } from '@equinor/fusion-framework-module';
-import type { AppModule } from '@equinor/fusion-framework-module-app';
-import { FrameworkProvider } from '@equinor/fusion-framework-react';
-import { ModuleProvider } from '@equinor/fusion-framework-react-module';
-/** Manifest used when a test does not care about its own app identity. */
-const defaultManifest: AppEnv['manifest'] = {
- appKey: 'test-app',
- displayName: 'Test App',
- description: 'A test application',
- type: 'standalone',
-};
+import { resolveAppScope, createAppScopeWrapper } from './scope';
/**
* Options for {@link renderAppComponent}.
@@ -43,11 +32,37 @@ export interface RenderAppComponentOptions<
fusion?: Fusion;
}
+/**
+ * The result of {@link renderAppComponent}: the `vitest-browser-react` render result,
+ * plus the resolved application module scope and its parent Fusion instance.
+ *
+ * @template TModules - Module descriptors beyond the default set.
+ */
+export interface RenderAppComponentResult | unknown = unknown>
+ extends RenderResult {
+ /**
+ * The Fusion instances backing the rendered component, nested under this single key so
+ * `vitest-browser-react`'s own `RenderResult` fields stay free to evolve without ever
+ * colliding with it.
+ */
+ fusion: {
+ /** The parent Fusion instance the component's `FrameworkProvider` was given. */
+ framework: Fusion;
+ /**
+ * The resolved application module instance backing the rendered component — the same
+ * instance a real app would read via `useAppModule`/`useAppModules`. Drive a module
+ * directly (e.g. `fusion.app.context.setCurrentContextByIdAsync(id)`) to exercise a
+ * state change after the initial render, then assert the component re-rendered accordingly.
+ */
+ app: AppModulesInstance;
+ };
+}
+
/**
* Renders a component inside a real, mock-backed application module scope.
*
* @remarks
- * Wraps `@testing-library/react`'s `render` with the same provider nesting
+ * Wraps `vitest-browser-react`'s `render` with the same provider nesting
* `createComponent` uses in production — a `FrameworkProvider` (the parent Fusion
* instance, from `mockFramework`) around a `ModuleProvider` (this app's own modules,
* from `mockAppModules`, `@equinor/fusion-framework-app/mock`). See {@link renderAppHook}
@@ -57,7 +72,7 @@ export interface RenderAppComponentOptions<
* @template TEnv - The application environment descriptor.
* @param ui - The component to render.
* @param options - A `configure` callback and `env` for `mockAppModules`, plus any other `render` option.
- * @returns The `render` result, once the mocked application module scope resolves.
+ * @returns The `render` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
*
* @example
* ```tsx
@@ -66,32 +81,36 @@ export interface RenderAppComponentOptions<
* // register the child app's own manifest against the `app` module
* }),
* });
- * await waitFor(() => expect(getByText(/mounted/)).toBeInTheDocument());
+ * await expect.element(getByText(/mounted/)).toBeInTheDocument();
+ * ```
+ *
+ * @example Drive a module directly and assert the re-render
+ * ```tsx
+ * const { getByText, fusion } = await renderAppComponent<[ContextModule]>(, {
+ * configure: (configurator) => enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
+ * });
+ * await fusion.app.context.setCurrentContextByIdAsync(projectB.id);
+ * await expect.element(getByText(/project-b/)).toBeInTheDocument();
* ```
*/
export async function renderAppComponent<
TModules extends Array | unknown = unknown,
TEnv extends AppEnv = AppEnv,
->(ui: ReactElement, options?: RenderAppComponentOptions): Promise {
+>(
+ ui: ReactElement,
+ options?: RenderAppComponentOptions,
+): Promise> {
const { configure, env, fusion: providedFusion, ...renderOptions } = options ?? {};
- const resolvedEnv = env ?? ({ manifest: defaultManifest } as TEnv);
- // Built explicitly (rather than left to `mockAppModules`'s own default) so the same
- // instance can also be handed to `FrameworkProvider` below — mirroring `createComponent`'s
- // `enableAppManifestMock` + `mockAppModules(cb, env, fusion)` composition.
- const fusion =
- providedFusion ??
- (await mockFramework<[AppModule]>((configurator) =>
- enableAppManifestMock(configurator, resolvedEnv),
- ));
- const modules = await mockAppModules(configure, resolvedEnv, fusion);
- return render(ui, {
+ const { framework, app } = await resolveAppScope({
+ configure,
+ env,
+ fusion: providedFusion,
+ });
+ const result = await render(ui, {
...renderOptions,
- wrapper: ({ children }) => (
-
- {children}
-
- ),
+ wrapper: createAppScopeWrapper({ framework, app }),
});
+ return { ...result, fusion: { framework, app } };
}
export default renderAppComponent;
diff --git a/packages/react/app/src/testing/render-app-hook.tsx b/packages/react/app/src/vitest/render-app-hook.tsx
similarity index 61%
rename from packages/react/app/src/testing/render-app-hook.tsx
rename to packages/react/app/src/vitest/render-app-hook.tsx
index 02534c76e3..b08d1abb4f 100644
--- a/packages/react/app/src/testing/render-app-hook.tsx
+++ b/packages/react/app/src/vitest/render-app-hook.tsx
@@ -1,23 +1,12 @@
-import { renderHook } from '@testing-library/react';
-import type { RenderHookOptions, RenderHookResult } from '@testing-library/react';
+import { renderHook } from 'vitest-browser-react';
+import type { RenderHookOptions, RenderHookResult } from 'vitest-browser-react';
-import { mockAppModules, enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
-import type { AppEnv } from '@equinor/fusion-framework-app';
+import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
import type { Fusion } from '@equinor/fusion-framework';
-import { mockFramework } from '@equinor/fusion-framework/mock';
import type { AnyModule } from '@equinor/fusion-framework-module';
-import type { AppModule } from '@equinor/fusion-framework-module-app';
-import { FrameworkProvider } from '@equinor/fusion-framework-react';
-import { ModuleProvider } from '@equinor/fusion-framework-react-module';
-/** Manifest used when a test does not care about its own app identity. */
-const defaultManifest: AppEnv['manifest'] = {
- appKey: 'test-app',
- displayName: 'Test App',
- description: 'A test application',
- type: 'standalone',
-};
+import { resolveAppScope, createAppScopeWrapper } from './scope';
/**
* Options for {@link renderAppHook}.
@@ -44,11 +33,42 @@ export interface RenderAppHookOptions<
fusion?: Fusion;
}
+/**
+ * The result of {@link renderAppHook}: the `vitest-browser-react` `renderHook` result,
+ * plus the resolved application module scope and its parent Fusion instance.
+ *
+ * @template Result - The value returned by the rendered hook.
+ * @template Props - The props accepted by the rendered hook.
+ * @template TModules - Module descriptors beyond the default set.
+ */
+export interface RenderAppHookResult<
+ Result,
+ Props,
+ TModules extends Array | unknown = unknown,
+> extends RenderHookResult {
+ /**
+ * The Fusion instances backing the rendered hook, nested under this single key so
+ * `vitest-browser-react`'s own `RenderHookResult` fields stay free to evolve without
+ * ever colliding with it.
+ */
+ fusion: {
+ /** The parent Fusion instance the hook's `FrameworkProvider` was given. */
+ framework: Fusion;
+ /**
+ * The resolved application module instance backing the rendered hook — the same
+ * instance a real app would read via `useAppModule`/`useAppModules`. Drive a module
+ * not returned by the hook itself (e.g. `fusion.app.context.setCurrentContextByIdAsync(id)`)
+ * to exercise a state change after the initial render.
+ */
+ app: AppModulesInstance;
+ };
+}
+
/**
* Renders a hook inside a real, mock-backed application module scope.
*
* @remarks
- * Wraps `@testing-library/react`'s `renderHook` with the same provider nesting
+ * Wraps `vitest-browser-react`'s `renderHook` with the same provider nesting
* `createComponent` uses in production — a `FrameworkProvider` (the parent Fusion
* instance, from `mockFramework`) around a `ModuleProvider` (this app's own modules,
* from `mockAppModules`, `@equinor/fusion-framework-app/mock`) — the real
@@ -65,12 +85,12 @@ export interface RenderAppHookOptions<
* @param render - The hook to render, receiving `initialProps`.
* @param options - A `configure` callback and `env` for `mockAppModules`, plus any other
* `renderHook` option.
- * @returns The `renderHook` result, once the mocked application module scope resolves.
+ * @returns The `renderHook` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
*
* @example
* ```tsx
* const { result } = await renderAppHook(() => useAccessToken({ scopes: ['User.Read'] }));
- * await waitFor(() => expect(result.current.pending).toBe(false));
+ * await vi.waitFor(() => expect(result.current.pending).toBe(false));
* ```
*
* @example Sign in a named user
@@ -96,28 +116,20 @@ export async function renderAppHook<
TModules extends Array | unknown = unknown,
TEnv extends AppEnv = AppEnv,
>(
- render: (initialProps: Props) => Result,
+ render: (initialProps?: Props) => Result,
options?: RenderAppHookOptions,
-): Promise> {
+): Promise> {
const { configure, env, fusion: providedFusion, ...renderHookOptions } = options ?? {};
- const resolvedEnv = env ?? ({ manifest: defaultManifest } as TEnv);
- // Built explicitly (rather than left to `mockAppModules`'s own default) so the same
- // instance can also be handed to `FrameworkProvider` below — mirroring `createComponent`'s
- // `enableAppManifestMock` + `mockAppModules(cb, env, fusion)` composition.
- const fusion =
- providedFusion ??
- (await mockFramework<[AppModule]>((configurator) =>
- enableAppManifestMock(configurator, resolvedEnv),
- ));
- const modules = await mockAppModules(configure, resolvedEnv, fusion);
- return renderHook(render, {
+ const { framework, app } = await resolveAppScope({
+ configure,
+ env,
+ fusion: providedFusion,
+ });
+ const result = await renderHook(render, {
...renderHookOptions,
- wrapper: ({ children }) => (
-
- {children}
-
- ),
+ wrapper: createAppScopeWrapper({ framework, app }),
});
+ return { ...result, fusion: { framework, app } };
}
export default renderAppHook;
diff --git a/packages/react/app/src/vitest/render.tsx b/packages/react/app/src/vitest/render.tsx
new file mode 100644
index 0000000000..cf3caf0a2a
--- /dev/null
+++ b/packages/react/app/src/vitest/render.tsx
@@ -0,0 +1,64 @@
+import type { ReactElement } from 'react';
+
+import type { AnyModule } from '@equinor/fusion-framework-module';
+import type { AppEnv } from '@equinor/fusion-framework-app';
+import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
+
+import {
+ renderAppComponent,
+ type RenderAppComponentOptions,
+ type RenderAppComponentResult,
+} from './render-app-component';
+
+// resolved at test-time by `ffc app test`'s Vite plugin (@equinor/fusion-framework-cli/testing);
+// see virtual-modules.d.ts for the ambient module declarations
+import { manifest, config } from 'virtual:fusion-app-test-env';
+import { configure } from 'virtual:fusion-app-test-configure';
+
+/**
+ * Renders a component inside the application's own module scope for use in plain `describe`/`it`
+ * tests, using the manifest, config, and module-configurator `ffc app test` resolved for this
+ * application — no per-test wiring, and no custom `test` fixture required.
+ *
+ * @remarks
+ * Only works when the test file is run via `ffc app test` (see
+ * `@equinor/fusion-framework-cli/testing`'s `appTestVitePlugin`), which registers the virtual
+ * modules backing the resolved `env`/`configure`. Running the same test file through a plain
+ * `vitest` invocation that doesn't register the plugin fails to resolve those imports.
+ *
+ * Pass `env` or `configure` in `options` to override the resolved values for a single render.
+ *
+ * @template TModules - Module descriptors beyond the default set.
+ * @param ui - The component to render.
+ * @param options - Overrides for `env`/`configure`, plus any other `renderAppComponent` option.
+ * @returns The `render` result plus `fusion.framework` and `fusion.app`, once the mocked application module scope resolves.
+ * @example
+ * ```tsx
+ * import { describe, expect, it } from 'vitest';
+ * import { render } from '@equinor/fusion-framework-react-app/vitest';
+ * import { App } from '../App';
+ *
+ * describe('App', () => {
+ * it('renders the app', async () => {
+ * const { getByRole } = await render();
+ * await expect.element(getByRole('heading')).toBeVisible();
+ * });
+ * });
+ * ```
+ */
+export async function render | unknown = unknown>(
+ ui: ReactElement,
+ options?: RenderAppComponentOptions,
+): Promise> {
+ const { configure: configureOverride, env: envOverride, ...renderOptions } = options ?? {};
+ return renderAppComponent(ui, {
+ ...renderOptions,
+ env: envOverride ?? { manifest, config },
+ // the resolved `configure` is generic over the app's own module set (`unknown`), while
+ // `TModules` here is caller-supplied *extra* modules — safe to widen for the common case
+ // where callers don't override it with a `TModules`-specific configurator
+ configure: (configureOverride ?? configure) as AppMockConfigureFn | undefined,
+ });
+}
+
+export default render;
diff --git a/packages/react/app/src/vitest/scope/create-app-scope-wrapper.tsx b/packages/react/app/src/vitest/scope/create-app-scope-wrapper.tsx
new file mode 100644
index 0000000000..76614ca45f
--- /dev/null
+++ b/packages/react/app/src/vitest/scope/create-app-scope-wrapper.tsx
@@ -0,0 +1,26 @@
+import type { ReactNode } from 'react';
+
+import { FrameworkProvider } from '@equinor/fusion-framework-react';
+import { ModuleProvider } from '@equinor/fusion-framework-react-module';
+import type { AnyModule } from '@equinor/fusion-framework-module';
+
+import type { AppScope } from './resolve-app-scope';
+
+/**
+ * The provider nesting `createComponent` uses in production, for wrapping a component or
+ * hook under test in its resolved {@link AppScope}.
+ *
+ * @template TModules - Module descriptors beyond the default set.
+ * @param scope - The resolved parent Fusion instance and application module scope.
+ * @returns A wrapper component nesting a `FrameworkProvider` around a `ModuleProvider`.
+ */
+export function createAppScopeWrapper | unknown = unknown>({
+ framework,
+ app,
+}: AppScope) {
+ return ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ );
+}
diff --git a/packages/react/app/src/vitest/scope/default-app-env.ts b/packages/react/app/src/vitest/scope/default-app-env.ts
new file mode 100644
index 0000000000..f80986671d
--- /dev/null
+++ b/packages/react/app/src/vitest/scope/default-app-env.ts
@@ -0,0 +1,13 @@
+import type { AppEnv } from '@equinor/fusion-framework-app';
+
+/**
+ * The application environment used when a test does not care about its own app identity.
+ */
+export const defaultAppEnv: AppEnv = {
+ manifest: {
+ appKey: 'test-app',
+ displayName: 'Test App',
+ description: 'A test application',
+ type: 'standalone',
+ },
+};
diff --git a/packages/react/app/src/vitest/scope/index.ts b/packages/react/app/src/vitest/scope/index.ts
new file mode 100644
index 0000000000..700764a8bf
--- /dev/null
+++ b/packages/react/app/src/vitest/scope/index.ts
@@ -0,0 +1,4 @@
+export { defaultAppEnv } from './default-app-env';
+export { resolveFusion } from './resolve-fusion';
+export { resolveAppScope, type AppScope } from './resolve-app-scope';
+export { createAppScopeWrapper } from './create-app-scope-wrapper';
diff --git a/packages/react/app/src/vitest/scope/resolve-app-scope.ts b/packages/react/app/src/vitest/scope/resolve-app-scope.ts
new file mode 100644
index 0000000000..4db49d96e1
--- /dev/null
+++ b/packages/react/app/src/vitest/scope/resolve-app-scope.ts
@@ -0,0 +1,46 @@
+import { mockAppModules } from '@equinor/fusion-framework-app/mock';
+import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
+import type { AppEnv, AppModulesInstance } from '@equinor/fusion-framework-app';
+import type { Fusion } from '@equinor/fusion-framework';
+import type { AnyModule } from '@equinor/fusion-framework-module';
+
+import { resolveFusion } from './resolve-fusion';
+import { defaultAppEnv } from './default-app-env';
+
+/**
+ * The parent Fusion instance and resolved application module scope shared by every
+ * `packages/react/app` testing helper.
+ *
+ * @template TModules - Module descriptors beyond the default set.
+ */
+export interface AppScope | unknown = unknown> {
+ /** The parent Fusion instance the application scope was resolved against. */
+ framework: Fusion;
+ /** The resolved application module instance. */
+ app: AppModulesInstance;
+}
+
+/**
+ * Resolves the parent Fusion instance and application module scope shared by every
+ * `packages/react/app` testing helper.
+ *
+ * @template TModules - Module descriptors beyond the default set.
+ * @template TEnv - The application environment descriptor.
+ * @param options - A `configure` callback and `env` for {@link mockAppModules}, plus an
+ * already-built `fusion` instance to reuse instead of a fresh one.
+ * @returns The resolved {@link AppScope}.
+ */
+export async function resolveAppScope<
+ TModules extends Array | unknown = unknown,
+ TEnv extends AppEnv = AppEnv,
+>(options?: {
+ configure?: AppMockConfigureFn;
+ env?: TEnv;
+ fusion?: Fusion;
+}): Promise> {
+ const { configure, env, fusion: providedFusion } = options ?? {};
+ const resolvedEnv = env ?? (defaultAppEnv as TEnv);
+ const framework = await resolveFusion(resolvedEnv, providedFusion);
+ const app = await mockAppModules(configure, resolvedEnv, framework);
+ return { framework, app };
+}
diff --git a/packages/react/app/src/vitest/scope/resolve-fusion.ts b/packages/react/app/src/vitest/scope/resolve-fusion.ts
new file mode 100644
index 0000000000..f32dccbb7c
--- /dev/null
+++ b/packages/react/app/src/vitest/scope/resolve-fusion.ts
@@ -0,0 +1,29 @@
+import type { AppEnv } from '@equinor/fusion-framework-app';
+import type { Fusion } from '@equinor/fusion-framework';
+import { mockFramework } from '@equinor/fusion-framework/mock';
+import { enableAppManifestMock } from '@equinor/fusion-framework-app/mock';
+import type { AppModule } from '@equinor/fusion-framework-module-app';
+
+import { defaultAppEnv } from './default-app-env';
+
+/**
+ * Resolves the parent Fusion instance backing an application module scope, building a
+ * fresh {@link mockFramework} instance with this app's own manifest served when none is
+ * given.
+ *
+ * @template TEnv - The application environment descriptor.
+ * @param env - The application environment; defaults to {@link defaultAppEnv}.
+ * @param fusion - An already-built parent Fusion instance to reuse instead.
+ * @returns The given `fusion`, or a fresh mocked parent Fusion instance.
+ */
+export async function resolveFusion(
+ env?: TEnv,
+ fusion?: Fusion,
+): Promise {
+ return (
+ fusion ??
+ mockFramework<[AppModule]>((configurator) =>
+ enableAppManifestMock(configurator, env ?? (defaultAppEnv as TEnv)),
+ )
+ );
+}
diff --git a/packages/react/app/src/vitest/test-app.tsx b/packages/react/app/src/vitest/test-app.tsx
new file mode 100644
index 0000000000..333b2fabfe
--- /dev/null
+++ b/packages/react/app/src/vitest/test-app.tsx
@@ -0,0 +1,71 @@
+import type { ReactElement } from 'react';
+import { test as baseTest } from 'vitest';
+import { render, renderHook } from 'vitest-browser-react';
+import type { RenderOptions, RenderHookOptions } from 'vitest-browser-react';
+
+import { mockAppModules } from '@equinor/fusion-framework-app/mock';
+import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
+import type { AppEnv } from '@equinor/fusion-framework-app';
+
+import { defaultAppEnv, resolveFusion, createAppScopeWrapper } from './scope';
+
+/**
+ * `vitest`'s `test`, extended with an application module scope fixture.
+ *
+ * @remarks
+ * An alternative to {@link renderAppComponent}/{@link renderAppHook} for a test file whose
+ * cases share one mocked scope: `env`/`configure` become suite-level concerns, overridden
+ * once per file (or per `describe` block) with `testApp.extend(...)`, rather than an
+ * options object repeated on every call. `fusion`/`app` resolve lazily — a test that only
+ * destructures `app` never pays for rendering anything, and one that only destructures
+ * `render`/`renderHook` gets the same mocked scope wired in automatically.
+ *
+ * Both entry points stay supported: reach for `renderAppComponent`/`renderAppHook` for a
+ * one-off test whose configuration is not shared by the rest of the file; reach for
+ * `testApp` when several cases in a file share one seeded scope.
+ *
+ * @example
+ * ```tsx
+ * testApp('resolves current context', async ({ app, render }) => {
+ * const screen = await render();
+ * expect(app.context).toBeDefined();
+ * });
+ * ```
+ *
+ * @example Seed a module for every test in a suite
+ * ```tsx
+ * describe('with a seeded context module', () => {
+ * const test = testApp.extend('configure', { injected: true }, () =>
+ * (configurator) => enableContextMock(configurator, (mock) => mock.setCurrentContext(projectA)),
+ * );
+ *
+ * test('starts on the seeded context', async ({ render }) => {
+ * const screen = await render();
+ * await expect.element(screen.getByText(projectA.title)).toBeVisible();
+ * });
+ * });
+ * ```
+ */
+export const testApp = baseTest
+ .extend('env', { injected: true }, defaultAppEnv)
+ // `test.extend`'s plain-`value` overload rejects function types (ambiguous with the
+ // resolver-`fn` overload), so a function-typed fixture default must go through `fn` instead.
+ .extend('configure', { injected: true }, () => undefined as AppMockConfigureFn | undefined)
+ .extend('fusion', async ({ env }) => resolveFusion(env))
+ .extend('app', async ({ configure, env, fusion }) =>
+ mockAppModules(configure, env as AppEnv, fusion),
+ )
+ .extend('render', ({ fusion, app }) => {
+ const wrapper = createAppScopeWrapper({ framework: fusion, app });
+ return (ui: ReactElement, options?: Omit) =>
+ render(ui, { ...options, wrapper });
+ })
+ .extend('renderHook', ({ fusion, app }) => {
+ const wrapper = createAppScopeWrapper({ framework: fusion, app });
+ return (
+ cb: (initialProps?: Props) => Result,
+ options?: Omit, 'wrapper'>,
+ ) => renderHook(cb, { ...options, wrapper });
+ });
+
+export default testApp;
diff --git a/packages/react/app/src/vitest/test.tsx b/packages/react/app/src/vitest/test.tsx
new file mode 100644
index 0000000000..cc75a8c137
--- /dev/null
+++ b/packages/react/app/src/vitest/test.tsx
@@ -0,0 +1,37 @@
+import { testApp as baseTestApp } from './test-app';
+
+// resolved at test-time by `ffc app test`'s Vite plugin (@equinor/fusion-framework-cli/testing);
+// see virtual-modules.d.ts for the ambient module declarations
+import { manifest, config } from 'virtual:fusion-app-test-env';
+import { configure } from 'virtual:fusion-app-test-configure';
+
+/**
+ * `vitest`'s `test`, pre-seeded with the application's own manifest, config, and
+ * module-configurator, resolved by `ffc app test` the same way `ffc app build`/`ffc app dev`
+ * resolve them.
+ *
+ * @remarks
+ * Only works when the test file is run via `ffc app test` (see
+ * `@equinor/fusion-framework-cli/testing`'s `appTestVitePlugin`), which registers the virtual
+ * modules backing this fixture. Running the same test file through a plain `vitest` invocation
+ * that doesn't register the plugin fails to resolve those imports.
+ *
+ * Per-test mocking still works exactly like the base `testApp`: `.extend('configure', ...)` or
+ * a per-case `test.override('env', ...)` layers on top of the resolved values.
+ *
+ * @example
+ * ```tsx
+ * import { test } from '@equinor/fusion-framework-react-app/vitest';
+ * import { App } from '../App';
+ *
+ * test('renders the app', async ({ render }) => {
+ * const screen = await render();
+ * await expect.element(screen.getByRole('heading')).toBeVisible();
+ * });
+ * ```
+ */
+export const test = baseTestApp
+ .extend('env', { injected: true }, { manifest, config })
+ .extend('configure', { injected: true }, () => configure);
+
+export default test;
diff --git a/packages/react/app/src/vitest/virtual-modules.d.ts b/packages/react/app/src/vitest/virtual-modules.d.ts
new file mode 100644
index 0000000000..72342316cc
--- /dev/null
+++ b/packages/react/app/src/vitest/virtual-modules.d.ts
@@ -0,0 +1,12 @@
+declare module 'virtual:fusion-app-test-env' {
+ import type { AppEnv } from '@equinor/fusion-framework-app';
+
+ export const manifest: AppEnv['manifest'];
+ export const config: AppEnv['config'];
+}
+
+declare module 'virtual:fusion-app-test-configure' {
+ import type { AppMockConfigureFn } from '@equinor/fusion-framework-app/mock';
+
+ export const configure: AppMockConfigureFn | undefined;
+}
diff --git a/packages/react/app/vitest.config.ts b/packages/react/app/vitest.config.ts
index 86cce7962d..68048f767f 100644
--- a/packages/react/app/vitest.config.ts
+++ b/packages/react/app/vitest.config.ts
@@ -1,11 +1,17 @@
import { defineProject } from 'vitest/config';
+import { playwright } from '@vitest/browser-playwright';
-import { name, version } from './package.json';
+import { name, version } from './package.json' with { type: 'json' };
export default defineProject({
test: {
include: ['src/__tests__/**/*.{test,spec}.{ts,tsx}'],
name: `${name}@${version}`,
- environment: 'happy-dom',
+ browser: {
+ enabled: true,
+ provider: playwright(),
+ headless: true,
+ instances: [{ browser: 'chromium' }],
+ },
},
});
diff --git a/packages/react/framework/src/useFrameworkModule.ts b/packages/react/framework/src/useFrameworkModule.ts
index 47046c52ea..8bb86fea49 100644
--- a/packages/react/framework/src/useFrameworkModule.ts
+++ b/packages/react/framework/src/useFrameworkModule.ts
@@ -38,7 +38,7 @@ export const useFrameworkModule = <
const module = framework.modules[name];
// Warn when the requested module key does not resolve to a registered module
if (!module) {
- console.warn(`the requested module [${module}] is not included in the framework instance`);
+ console.warn(`the requested module [${name}] is not included in the framework instance`);
}
return module;
};
diff --git a/packages/utils/log/src/static.ts b/packages/utils/log/src/static.ts
index 9897e8a6e6..8a57f24e80 100644
--- a/packages/utils/log/src/static.ts
+++ b/packages/utils/log/src/static.ts
@@ -47,7 +47,9 @@ export enum LogLevel {
* @returns The resolved {@link LogLevel}.
*/
const resolveDefaultLogLevel = (): LogLevel => {
- const envLogLevel = process.env.FUSION_LOG_LEVEL;
+ // `process` is unavailable in real browser environments (e.g. Vitest Browser Mode)
+ const env = typeof process !== 'undefined' ? process.env : undefined;
+ const envLogLevel = env?.FUSION_LOG_LEVEL;
// Only attempt to parse the environment variable when it's actually set
if (envLogLevel) {
@@ -55,7 +57,7 @@ const resolveDefaultLogLevel = (): LogLevel => {
return resolveLogLevel(envLogLevel);
} catch {
// Unparsable value — degrade gracefully based on environment
- return process.env.NODE_ENV === 'development' ? LogLevel.Debug : LogLevel.Error;
+ return env?.NODE_ENV === 'development' ? LogLevel.Debug : LogLevel.Error;
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4894c05744..32e2ef0afc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -75,7 +75,7 @@ importers:
version: 24.12.2
'@vitest/coverage-v8':
specifier: ^4.1.10
- version: 4.1.10(vitest@4.1.10)
+ version: 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
agent-browser:
specifier: ^0.33.1
version: 0.33.1
@@ -96,7 +96,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
cookbooks/app-react:
devDependencies:
@@ -979,7 +979,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/cli:
dependencies:
@@ -1091,7 +1091,7 @@ importers:
version: 7.7.1
'@vitest/coverage-v8':
specifier: ^4.1.10
- version: 4.1.10(vitest@4.1.10)
+ version: 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
adm-zip:
specifier: ^0.6.0
version: 0.6.0
@@ -1124,7 +1124,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/cli-plugins/ai-base:
dependencies:
@@ -1152,7 +1152,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/cli-plugins/ai-chat:
dependencies:
@@ -1183,7 +1183,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/cli-plugins/ai-index:
dependencies:
@@ -1253,7 +1253,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/cli-plugins/copilot:
dependencies:
@@ -1423,7 +1423,7 @@ importers:
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/framework:
dependencies:
@@ -1460,7 +1460,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/linting/cli:
dependencies:
@@ -1516,7 +1516,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/linting/lsp:
dependencies:
@@ -1557,7 +1557,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/linting/vscode:
dependencies:
@@ -1603,7 +1603,7 @@ importers:
dependencies:
'@langchain/community':
specifier: ^1.1.8
- version: 1.1.29(@azure/search-documents@13.0.0)(@langchain/core@1.2.4(@opentelemetry/api@1.9.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1))(@opentelemetry/api@1.9.1)(cheerio@1.2.0)(d3-dsv@3.0.1)(ignore@7.0.6)(jsdom@30.0.1)(jsonwebtoken@9.0.3)(lodash@4.18.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1)
+ version: 1.1.29(@azure/search-documents@13.0.0)(@langchain/core@1.2.4(@opentelemetry/api@1.9.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1))(@opentelemetry/api@1.9.1)(cheerio@1.2.0)(d3-dsv@3.0.1)(ignore@7.0.6)(jsdom@30.0.1)(jsonwebtoken@9.0.3)(lodash@4.18.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(playwright@1.62.1)(ws@8.21.1)
'@langchain/core':
specifier: ^1.0.1
version: 1.2.4(@opentelemetry/api@1.9.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1)
@@ -1640,7 +1640,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/analytics:
dependencies:
@@ -1698,7 +1698,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/app:
dependencies:
@@ -1747,7 +1747,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/azure-identity:
dependencies:
@@ -1763,7 +1763,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
optionalDependencies:
'@azure/identity-cache-persistence':
specifier: ^1.3.0
@@ -1819,7 +1819,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
zod:
specifier: 4.4.3
version: 4.4.3
@@ -1848,6 +1848,9 @@ importers:
'@equinor/fusion-framework-module-services':
specifier: workspace:^
version: link:../services
+ '@equinor/fusion-framework-module-telemetry':
+ specifier: workspace:^
+ version: link:../telemetry
'@faker-js/faker':
specifier: ^10.1.0
version: 10.5.0
@@ -1872,7 +1875,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^3.2.7
- version: 3.2.7(@types/debug@4.1.13)(@types/node@26.1.2)(happy-dom@20.11.1)(jsdom@30.0.1)(lightningcss@1.33.0)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ version: 3.2.7(@types/debug@4.1.13)(@types/node@26.1.2)(@vitest/browser@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.1)(lightningcss@1.33.0)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/feature-flag:
dependencies:
@@ -1906,7 +1909,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/http:
dependencies:
@@ -1928,7 +1931,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/module:
dependencies:
@@ -1947,7 +1950,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/msal:
dependencies:
@@ -1991,7 +1994,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
optionalDependencies:
'@azure/msal-node-extensions':
specifier: ^5.0.2
@@ -2032,7 +2035,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/service-discovery:
dependencies:
@@ -2085,7 +2088,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/signalr:
dependencies:
@@ -2138,7 +2141,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/modules/widget:
dependencies:
@@ -2278,18 +2281,18 @@ importers:
'@equinor/fusion-observable':
specifier: workspace:*
version: link:../../utils/observable
- '@testing-library/react':
- specifier: ^16.0.0
- version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@types/react':
specifier: ^19.2.7
version: 19.2.18
'@types/react-dom':
specifier: ^19.2.3
version: 19.2.4(@types/react@19.2.18)
- happy-dom:
- specifier: ^20.8.4
- version: 20.11.1
+ '@vitest/browser-playwright':
+ specifier: ^4.1.0
+ version: 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)
+ playwright:
+ specifier: ^1.62.1
+ version: 1.62.1
react:
specifier: ^19.2.1
version: 19.2.8
@@ -2303,8 +2306,11 @@ importers:
specifier: ^7.0.2
version: 7.0.2
vitest:
- specifier: ^4.1.0
- version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ specifier: ^4.1.10
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ vitest-browser-react:
+ specifier: ^2.2.0
+ version: 2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10)
packages/react/components/bookmark:
dependencies:
@@ -2356,7 +2362,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/react/components/people-resolver:
dependencies:
@@ -2655,7 +2661,7 @@ importers:
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/utils/imports:
dependencies:
@@ -2680,7 +2686,7 @@ importers:
version: 4.6.0
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/utils/load-env:
dependencies:
@@ -2696,7 +2702,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/utils/log:
dependencies:
@@ -2712,7 +2718,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/utils/observable:
dependencies:
@@ -2749,7 +2755,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/utils/openapi-mock:
dependencies:
@@ -2771,7 +2777,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
packages/utils/query:
dependencies:
@@ -2802,7 +2808,7 @@ importers:
version: 7.0.2
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/vite-plugins/api-service:
dependencies:
@@ -2830,7 +2836,7 @@ importers:
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/vite-plugins/markdown:
devDependencies:
@@ -2863,7 +2869,7 @@ importers:
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
vitest:
specifier: ^4.1.10
- version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
packages/vite-plugins/routes-dsl:
devDependencies:
@@ -3386,6 +3392,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@blazediff/core@1.9.1':
+ resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==}
+
'@braintree/sanitize-url@7.1.2':
resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
@@ -5424,6 +5433,9 @@ packages:
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ '@polka/url@1.0.0-next.29':
+ resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
'@radix-ui/number@1.1.3':
resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==}
@@ -7161,6 +7173,17 @@ packages:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
vue: ^3.2.25
+ '@vitest/browser-playwright@4.1.10':
+ resolution: {integrity: sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==}
+ peerDependencies:
+ playwright: '*'
+ vitest: 4.1.10
+
+ '@vitest/browser@4.1.10':
+ resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==}
+ peerDependencies:
+ vitest: 4.1.10
+
'@vitest/coverage-v8@4.1.10':
resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==}
peerDependencies:
@@ -7176,9 +7199,6 @@ packages:
'@vitest/expect@4.1.10':
resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
- '@vitest/expect@4.1.4':
- resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==}
-
'@vitest/mocker@3.2.7':
resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==}
peerDependencies:
@@ -7201,62 +7221,36 @@ packages:
vite:
optional: true
- '@vitest/mocker@4.1.4':
- resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==}
- peerDependencies:
- msw: ^2.4.9
- vite: ^6.0.0 || ^7.0.0 || ^8.0.0
- peerDependenciesMeta:
- msw:
- optional: true
- vite:
- optional: true
-
'@vitest/pretty-format@3.2.7':
resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==}
'@vitest/pretty-format@4.1.10':
resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
- '@vitest/pretty-format@4.1.4':
- resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==}
-
'@vitest/runner@3.2.7':
resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==}
'@vitest/runner@4.1.10':
resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
- '@vitest/runner@4.1.4':
- resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==}
-
'@vitest/snapshot@3.2.7':
resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==}
'@vitest/snapshot@4.1.10':
resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
- '@vitest/snapshot@4.1.4':
- resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==}
-
'@vitest/spy@3.2.7':
resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==}
'@vitest/spy@4.1.10':
resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
- '@vitest/spy@4.1.4':
- resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==}
-
'@vitest/utils@3.2.7':
resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==}
'@vitest/utils@4.1.10':
resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
- '@vitest/utils@4.1.4':
- resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==}
-
'@vscode/vsce-sign-alpine-arm64@2.0.6':
resolution: {integrity: sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==}
cpu: [arm64]
@@ -8660,6 +8654,11 @@ packages:
fs-monkey@1.1.0:
resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==}
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -9638,6 +9637,10 @@ packages:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -10005,6 +10008,16 @@ packages:
pkg-types@2.3.1:
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
+ playwright-core@1.62.1:
+ resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ playwright@1.62.1:
+ resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==}
+ engines: {node: '>=20'}
+ hasBin: true
+
pluralize@2.0.0:
resolution: {integrity: sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==}
@@ -10016,6 +10029,10 @@ packages:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
+ pngjs@7.0.0:
+ resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==}
+ engines: {node: '>=14.19.0'}
+
points-on-curve@0.2.0:
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
@@ -10651,6 +10668,10 @@ packages:
simple-git@3.36.0:
resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==}
+ sirv@3.0.2:
+ resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
+ engines: {node: '>=18'}
+
sitemap@9.0.1:
resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==}
engines: {node: '>=20.19.5', npm: '>=10.8.2'}
@@ -10916,6 +10937,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ totalist@3.0.1:
+ resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+ engines: {node: '>=6'}
+
tough-cookie@4.1.4:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
@@ -11343,6 +11368,20 @@ packages:
yaml:
optional: true
+ vitest-browser-react@2.2.0:
+ resolution: {integrity: sha512-oY3KM6305kwJMa6nHo92vVtkOsih7mjEf12dLKuphaF+9ywWPEc+qanIBd394SZ6m5LadVEaG6dicvvizOzmjA==}
+ peerDependencies:
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ vitest: ^4.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
vitest@3.2.7:
resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -11411,46 +11450,6 @@ packages:
jsdom:
optional: true
- vitest@4.1.4:
- resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==}
- engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
- hasBin: true
- peerDependencies:
- '@edge-runtime/vm': '*'
- '@opentelemetry/api': ^1.9.0
- '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
- '@vitest/browser-playwright': 4.1.4
- '@vitest/browser-preview': 4.1.4
- '@vitest/browser-webdriverio': 4.1.4
- '@vitest/coverage-istanbul': 4.1.4
- '@vitest/coverage-v8': 4.1.4
- '@vitest/ui': 4.1.4
- happy-dom: '*'
- jsdom: '*'
- peerDependenciesMeta:
- '@edge-runtime/vm':
- optional: true
- '@opentelemetry/api':
- optional: true
- '@types/node':
- optional: true
- '@vitest/browser-playwright':
- optional: true
- '@vitest/browser-preview':
- optional: true
- '@vitest/browser-webdriverio':
- optional: true
- '@vitest/coverage-istanbul':
- optional: true
- '@vitest/coverage-v8':
- optional: true
- '@vitest/ui':
- optional: true
- happy-dom:
- optional: true
- jsdom:
- optional: true
-
vscode-jsonrpc@8.2.1:
resolution: {integrity: sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==}
engines: {node: '>=14.0.0'}
@@ -12377,6 +12376,8 @@ snapshots:
'@biomejs/cli-win32-x64@2.5.6':
optional: true
+ '@blazediff/core@1.9.1': {}
+
'@braintree/sanitize-url@7.1.2': {}
'@bramus/specificity@2.4.2':
@@ -13480,7 +13481,7 @@ snapshots:
- openai
- ws
- '@langchain/community@1.1.29(@azure/search-documents@13.0.0)(@langchain/core@1.2.4(@opentelemetry/api@1.9.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1))(@opentelemetry/api@1.9.1)(cheerio@1.2.0)(d3-dsv@3.0.1)(ignore@7.0.6)(jsdom@30.0.1)(jsonwebtoken@9.0.3)(lodash@4.18.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1)':
+ '@langchain/community@1.1.29(@azure/search-documents@13.0.0)(@langchain/core@1.2.4(@opentelemetry/api@1.9.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1))(@opentelemetry/api@1.9.1)(cheerio@1.2.0)(d3-dsv@3.0.1)(ignore@7.0.6)(jsdom@30.0.1)(jsonwebtoken@9.0.3)(lodash@4.18.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(playwright@1.62.1)(ws@8.21.1)':
dependencies:
'@langchain/classic': 1.0.34(@langchain/core@1.2.4(@opentelemetry/api@1.9.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1))(@opentelemetry/api@1.9.1)(cheerio@1.2.0)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1)
'@langchain/core': 1.2.4(@opentelemetry/api@1.9.1)(openai@6.49.0(ws@8.21.1)(zod@4.4.3))(ws@8.21.1)
@@ -13501,6 +13502,7 @@ snapshots:
jsdom: 30.0.1
jsonwebtoken: 9.0.3
lodash: 4.18.1
+ playwright: 1.62.1
ws: 8.21.1
transitivePeerDependencies:
- '@opentelemetry/api'
@@ -14422,6 +14424,8 @@ snapshots:
'@pkgr/core@0.2.9': {}
+ '@polka/url@1.0.0-next.29': {}
+
'@radix-ui/number@1.1.3': {}
'@radix-ui/primitive@1.1.7': {}
@@ -16106,7 +16110,101 @@ snapshots:
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
vue: 3.5.40(typescript@7.0.2)
- '@vitest/coverage-v8@4.1.10(vitest@4.1.10)':
+ '@vitest/browser-playwright@4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(playwright@1.62.1)(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)':
+ dependencies:
+ '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)
+ '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
+ playwright: 1.62.1
+ tinyrainbow: 3.1.0
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ transitivePeerDependencies:
+ - bufferutil
+ - msw
+ - utf-8-validate
+ - vite
+ optional: true
+
+ '@vitest/browser-playwright@4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))(vitest@4.1.10)':
+ dependencies:
+ '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))(vitest@4.1.10)
+ '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
+ playwright: 1.62.1
+ tinyrainbow: 3.1.0
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ transitivePeerDependencies:
+ - bufferutil
+ - msw
+ - utf-8-validate
+ - vite
+ optional: true
+
+ '@vitest/browser-playwright@4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)':
+ dependencies:
+ '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)
+ '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
+ playwright: 1.62.1
+ tinyrainbow: 3.1.0
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ transitivePeerDependencies:
+ - bufferutil
+ - msw
+ - utf-8-validate
+ - vite
+
+ '@vitest/browser@4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)':
+ dependencies:
+ '@blazediff/core': 1.9.1
+ '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
+ '@vitest/utils': 4.1.10
+ magic-string: 0.30.21
+ pngjs: 7.0.0
+ sirv: 3.0.2
+ tinyrainbow: 3.1.0
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ ws: 8.21.1
+ transitivePeerDependencies:
+ - bufferutil
+ - msw
+ - utf-8-validate
+ - vite
+ optional: true
+
+ '@vitest/browser@4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))(vitest@4.1.10)':
+ dependencies:
+ '@blazediff/core': 1.9.1
+ '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
+ '@vitest/utils': 4.1.10
+ magic-string: 0.30.21
+ pngjs: 7.0.0
+ sirv: 3.0.2
+ tinyrainbow: 3.1.0
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
+ ws: 8.21.1
+ transitivePeerDependencies:
+ - bufferutil
+ - msw
+ - utf-8-validate
+ - vite
+ optional: true
+
+ '@vitest/browser@4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)':
+ dependencies:
+ '@blazediff/core': 1.9.1
+ '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
+ '@vitest/utils': 4.1.10
+ magic-string: 0.30.21
+ pngjs: 7.0.0
+ sirv: 3.0.2
+ tinyrainbow: 3.1.0
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ ws: 8.21.1
+ transitivePeerDependencies:
+ - bufferutil
+ - msw
+ - utf-8-validate
+ - vite
+
+ '@vitest/coverage-v8@4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)':
dependencies:
'@bcoe/v8-coverage': 1.0.2
'@vitest/utils': 4.1.10
@@ -16118,7 +16216,9 @@ snapshots:
obug: 2.1.1
std-env: 4.0.0
tinyrainbow: 3.1.0
- vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ optionalDependencies:
+ '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)
'@vitest/expect@3.2.7':
dependencies:
@@ -16137,15 +16237,6 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.1.0
- '@vitest/expect@4.1.4':
- dependencies:
- '@standard-schema/spec': 1.1.0
- '@types/chai': 5.2.3
- '@vitest/spy': 4.1.4
- '@vitest/utils': 4.1.4
- chai: 6.2.2
- tinyrainbow: 3.1.0
-
'@vitest/mocker@3.2.7(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@7.3.6(@types/node@26.1.2)(lightningcss@1.33.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))':
dependencies:
'@vitest/spy': 3.2.7
@@ -16173,15 +16264,6 @@ snapshots:
msw: 2.15.0(@types/node@26.1.2)(typescript@7.0.2)
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
- '@vitest/mocker@4.1.4(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))':
- dependencies:
- '@vitest/spy': 4.1.4
- estree-walker: 3.0.3
- magic-string: 0.30.21
- optionalDependencies:
- msw: 2.15.0(@types/node@26.1.2)(typescript@7.0.2)
- vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
-
'@vitest/pretty-format@3.2.7':
dependencies:
tinyrainbow: 2.0.0
@@ -16190,10 +16272,6 @@ snapshots:
dependencies:
tinyrainbow: 3.1.0
- '@vitest/pretty-format@4.1.4':
- dependencies:
- tinyrainbow: 3.1.0
-
'@vitest/runner@3.2.7':
dependencies:
'@vitest/utils': 3.2.7
@@ -16205,11 +16283,6 @@ snapshots:
'@vitest/utils': 4.1.10
pathe: 2.0.3
- '@vitest/runner@4.1.4':
- dependencies:
- '@vitest/utils': 4.1.4
- pathe: 2.0.3
-
'@vitest/snapshot@3.2.7':
dependencies:
'@vitest/pretty-format': 3.2.7
@@ -16223,21 +16296,12 @@ snapshots:
magic-string: 0.30.21
pathe: 2.0.3
- '@vitest/snapshot@4.1.4':
- dependencies:
- '@vitest/pretty-format': 4.1.4
- '@vitest/utils': 4.1.4
- magic-string: 0.30.21
- pathe: 2.0.3
-
'@vitest/spy@3.2.7':
dependencies:
tinyspy: 4.0.4
'@vitest/spy@4.1.10': {}
- '@vitest/spy@4.1.4': {}
-
'@vitest/utils@3.2.7':
dependencies:
'@vitest/pretty-format': 3.2.7
@@ -16250,12 +16314,6 @@ snapshots:
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
- '@vitest/utils@4.1.4':
- dependencies:
- '@vitest/pretty-format': 4.1.4
- convert-source-map: 2.0.0
- tinyrainbow: 3.1.0
-
'@vscode/vsce-sign-alpine-arm64@2.0.6':
optional: true
@@ -18057,6 +18115,9 @@ snapshots:
fs-monkey@1.1.0: {}
+ fsevents@2.3.2:
+ optional: true
+
fsevents@2.3.3:
optional: true
@@ -19273,6 +19334,8 @@ snapshots:
mri@1.2.0: {}
+ mrmime@2.0.1: {}
+
ms@2.1.3: {}
msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2):
@@ -19645,12 +19708,22 @@ snapshots:
exsolve: 1.1.0
pathe: 2.0.3
+ playwright-core@1.62.1: {}
+
+ playwright@1.62.1:
+ dependencies:
+ playwright-core: 1.62.1
+ optionalDependencies:
+ fsevents: 2.3.2
+
pluralize@2.0.0: {}
pluralize@8.0.0: {}
pngjs@5.0.0: {}
+ pngjs@7.0.0: {}
+
points-on-curve@0.2.0: {}
points-on-path@0.2.1:
@@ -20460,6 +20533,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ sirv@3.0.2:
+ dependencies:
+ '@polka/url': 1.0.0-next.29
+ mrmime: 2.0.1
+ totalist: 3.0.1
+
sitemap@9.0.1:
dependencies:
'@types/node': 24.13.3
@@ -20704,6 +20783,8 @@ snapshots:
dependencies:
is-number: 7.0.0
+ totalist@3.0.1: {}
+
tough-cookie@4.1.4:
dependencies:
psl: 1.15.0
@@ -21095,7 +21176,16 @@ snapshots:
tsx: 4.23.1
yaml: 2.9.0
- vitest@3.2.7(@types/debug@4.1.13)(@types/node@26.1.2)(happy-dom@20.11.1)(jsdom@30.0.1)(lightningcss@1.33.0)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1):
+ vitest-browser-react@2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10):
+ dependencies:
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ vitest@3.2.7(@types/debug@4.1.13)(@types/node@26.1.2)(@vitest/browser@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.1)(lightningcss@1.33.0)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.7
@@ -21123,6 +21213,7 @@ snapshots:
optionalDependencies:
'@types/debug': 4.1.13
'@types/node': 26.1.2
+ '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)
happy-dom: 20.11.1
jsdom: 30.0.1
transitivePeerDependencies:
@@ -21139,7 +21230,7 @@ snapshots:
- tsx
- yaml
- vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1):
+ vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1):
dependencies:
'@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
@@ -21164,7 +21255,8 @@ snapshots:
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/node': 24.12.2
- '@vitest/coverage-v8': 4.1.10(vitest@4.1.10)
+ '@vitest/browser-playwright': 4.1.10(msw@2.15.0(@types/node@24.12.2)(typescript@7.0.2))(playwright@1.62.1)(vite@8.2.0(@types/node@24.12.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)
+ '@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
happy-dom: 20.11.1
jsdom: 30.0.1
transitivePeerDependencies:
@@ -21181,7 +21273,7 @@ snapshots:
- tsx
- yaml
- vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1):
+ vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1):
dependencies:
'@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
@@ -21206,7 +21298,8 @@ snapshots:
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/node': 26.1.2
- '@vitest/coverage-v8': 4.1.10(vitest@4.1.10)
+ '@vitest/browser-playwright': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))(vitest@4.1.10)
+ '@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
happy-dom: 20.11.1
jsdom: 30.0.1
transitivePeerDependencies:
@@ -21223,7 +21316,7 @@ snapshots:
- tsx
- yaml
- vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0):
+ vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0):
dependencies:
'@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
@@ -21248,49 +21341,8 @@ snapshots:
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/node': 26.1.2
- '@vitest/coverage-v8': 4.1.10(vitest@4.1.10)
- happy-dom: 20.11.1
- jsdom: 30.0.1
- transitivePeerDependencies:
- - '@vitejs/devtools'
- - esbuild
- - jiti
- - less
- - msw
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - terser
- - tsx
- - yaml
-
- vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(happy-dom@20.11.1)(jsdom@30.0.1)(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1):
- dependencies:
- '@vitest/expect': 4.1.4
- '@vitest/mocker': 4.1.4(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1))
- '@vitest/pretty-format': 4.1.4
- '@vitest/runner': 4.1.4
- '@vitest/snapshot': 4.1.4
- '@vitest/spy': 4.1.4
- '@vitest/utils': 4.1.4
- es-module-lexer: 2.0.0
- expect-type: 1.3.0
- magic-string: 0.30.21
- obug: 2.1.1
- pathe: 2.0.3
- picomatch: 4.0.5
- std-env: 4.0.0
- tinybench: 2.9.0
- tinyexec: 1.2.4
- tinyglobby: 0.2.17
- tinyrainbow: 3.1.0
- vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)
- why-is-node-running: 2.3.0
- optionalDependencies:
- '@opentelemetry/api': 1.9.1
- '@types/node': 26.1.2
- '@vitest/coverage-v8': 4.1.10(vitest@4.1.10)
+ '@vitest/browser-playwright': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@7.0.2))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))(vitest@4.1.10)
+ '@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
happy-dom: 20.11.1
jsdom: 30.0.1
transitivePeerDependencies: