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: