@trackunit/react-vite-test-setup 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js ADDED
@@ -0,0 +1,1201 @@
1
+ import 'vitest-canvas-mock';
2
+ import failOnConsole from 'vitest-fail-on-console';
3
+ import { jsx, Fragment } from 'react/jsx-runtime';
4
+ import { vi, beforeEach, afterEach } from 'vitest';
5
+ import '@testing-library/jest-dom/vitest';
6
+ import { cleanup, act } from '@testing-library/react';
7
+ import { TextEncoder, TextDecoder } from 'util';
8
+ import { isValidElement, cloneElement } from 'react';
9
+ import * as reactI18next from 'react-i18next';
10
+ import { Temporal } from '@js-temporal/polyfill';
11
+ import { Globals } from '@react-spring/web';
12
+ import { WritableStream, TransformStream } from 'web-streams-polyfill';
13
+
14
+ /**
15
+ * Sets up a mock implementation for HTML Canvas API in testing environments.
16
+ *
17
+ * This function uses vitest-canvas-mock to provide a mock implementation of the
18
+ * HTML Canvas API, allowing tests to run without requiring a real DOM canvas.
19
+ * Useful for testing components that use canvas rendering.
20
+ *
21
+ * Importing vitest-canvas-mock at module load time has the side effect of
22
+ * patching HTMLCanvasElement.prototype.getContext globally; calling this
23
+ * function is a no-op kept for API compatibility with the previous Jest
24
+ * setup.
25
+ *
26
+ * @example
27
+ * import { setupCanvasMock } from '@trackunit/react-vite-test-setup';
28
+ *
29
+ * setupCanvasMock();
30
+ */
31
+ const setupCanvasMock = () => {
32
+ // The canvas mock is registered via the side-effect import at module
33
+ // load time; nothing further is needed here.
34
+ };
35
+
36
+ /**
37
+ * This will make your tests fail if they log to console.error during the tests.
38
+ * See more details here: https://www.npmjs.com/package/vitest-fail-on-console
39
+ *
40
+ * This setup also automatically suppresses jsdom CSS parsing errors for modern CSS features
41
+ * that jsdom doesn't support (@container queries and :has() selector). Other CSS parsing
42
+ * errors will still cause tests to fail.
43
+ *
44
+ * If your tests logs to console.error on purpose, you should spy on the console like so:
45
+ * ```
46
+ * vi.spyOn(console, 'error').mockImplementation()
47
+ * // Do your logic ...
48
+ * expect(console.error).toHaveBeenCalledWith('your error message')
49
+ * ```
50
+ */
51
+ const setupFailOnConsole = (overrides = {}) => {
52
+ failOnConsole({
53
+ shouldFailOnError: true,
54
+ shouldFailOnWarn: false,
55
+ silenceMessage: (message) => {
56
+ // Suppress ONLY jsdom CSS parsing errors for specific modern CSS features
57
+ // that we know jsdom doesn't support (@container queries, :has() selector)
58
+ if (typeof message === "string" &&
59
+ message.includes("Could not parse CSS stylesheet") &&
60
+ (message.includes("@container") || message.includes(":has("))) {
61
+ return true;
62
+ }
63
+ return false;
64
+ },
65
+ ...overrides,
66
+ });
67
+ };
68
+
69
+ /**
70
+ * `@googlemaps/jest-mocks` still calls `jest.fn()` internally; aliasing the
71
+ * vitest namespace under the legacy `jest` global keeps it working without
72
+ * pulling in the real Jest runtime.
73
+ */
74
+ const ensureJestGlobalAlias = () => {
75
+ const globalAny = globalThis;
76
+ if (globalAny.jest === undefined) {
77
+ globalAny.jest = vi;
78
+ }
79
+ };
80
+ /**
81
+ * Sets up mocks for Google Maps API and @vis.gl/react-google-maps components
82
+ * in testing environments.
83
+ *
84
+ * Replaces the `@vis.gl/react-google-maps` hooks/components (`APIProvider`,
85
+ * `useMap`, `Map`, `Marker`, `AdvancedMarker`, `InfoWindow`,
86
+ * `useApiIsLoaded`, `useApiLoadingStatus`) with deterministic fakes and
87
+ * registers a `beforeEach` hook that re-populates `global.window.google.maps`
88
+ * with mocked `Geocoder` / `geometry` / `AutocompleteService` implementations
89
+ * via `@googlemaps/jest-mocks`.
90
+ *
91
+ * Opt-in: the mock is registered via `vi.doMock` so it is
92
+ * NOT hoisted and only takes effect once `setupGoogleMaps()` is called from
93
+ * a test setup file. Tests that need the real `@vis.gl/react-google-maps`
94
+ * (or their own mock) are unaffected unless they explicitly call this.
95
+ *
96
+ * The factory body uses `require()` rather than `import` for
97
+ * `@vis.gl/react-google-maps` and `@googlemaps/jest-mocks` so neither
98
+ * package is evaluated until the factory actually runs (the latter still
99
+ * uses Jest globals internally, so a static import would crash any
100
+ * consumer that doesn't actually use the Google Maps mock).
101
+ *
102
+ * @example
103
+ * import { setupGoogleMaps } from '@trackunit/react-vite-test-setup';
104
+ * setupGoogleMaps();
105
+ */
106
+ const setupGoogleMaps = () => {
107
+ ensureJestGlobalAlias();
108
+ const getPlacePredictionsMock = vi.fn();
109
+ vi.doMock("@vis.gl/react-google-maps", async () => {
110
+ const originalModule = await vi.importActual("@vis.gl/react-google-maps");
111
+ ensureJestGlobalAlias();
112
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
113
+ const { APILoadingStatus } = require("@vis.gl/react-google-maps");
114
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
115
+ const { initialize } = require("@googlemaps/jest-mocks");
116
+ // The Google Maps API needs to be initialized before constructing a map
117
+ // instance so the mocked constructors / classes exist on `global.google`.
118
+ initialize();
119
+ const mapOptions = { center: { lat: 0, lng: 0 }, zoom: 10 };
120
+ const mapContainer = document.createElement("div");
121
+ const map = new google.maps.Map(mapContainer, mapOptions);
122
+ vi.spyOn(map, "getDiv").mockReturnValue(mapContainer);
123
+ const AdvancedMarkerMock = vi.fn(function (props) {
124
+ return (jsx("div", { "data-position": JSON.stringify(props.position), "data-testid": "marker", children: props.children }));
125
+ });
126
+ const MapMock = vi.fn(function (props) {
127
+ return jsx("div", { "data-testid": "map", children: props.children });
128
+ });
129
+ const MapMarkerMock = vi.fn(function (props) {
130
+ return jsx("div", { "data-testid": "map", children: props.children });
131
+ });
132
+ const InfoWindowMock = vi.fn(function (props) {
133
+ return jsx("div", { "data-testid": "google-info-window", children: props.children });
134
+ });
135
+ const APIProviderMock = vi.fn(function (props) {
136
+ return jsx(Fragment, { children: props.children });
137
+ });
138
+ return {
139
+ ...originalModule,
140
+ APIProvider: APIProviderMock,
141
+ useApiLoadingStatus: () => APILoadingStatus.LOADED,
142
+ useApiIsLoaded: () => true,
143
+ useMap: () => map,
144
+ AdvancedMarker: AdvancedMarkerMock,
145
+ Map: MapMock,
146
+ Marker: MapMarkerMock,
147
+ InfoWindow: InfoWindowMock,
148
+ };
149
+ });
150
+ beforeEach(() => {
151
+ ensureJestGlobalAlias();
152
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
153
+ const { initialize } = require("@googlemaps/jest-mocks");
154
+ initialize();
155
+ global.window.google.maps = {
156
+ ...global.window.google.maps,
157
+ Geocoder: vi.fn(),
158
+ geometry: {
159
+ ...global.window.google.maps.geometry,
160
+ spherical: {
161
+ computeDistanceBetween: vi.fn(),
162
+ computeArea: vi.fn(),
163
+ computeHeading: vi.fn(),
164
+ computeLength: vi.fn(),
165
+ computeOffset: vi.fn(),
166
+ computeOffsetOrigin: vi.fn(),
167
+ computeSignedArea: vi.fn(),
168
+ interpolate: vi.fn(),
169
+ },
170
+ },
171
+ places: {
172
+ ...global.window.google.maps.places,
173
+ // `new google.maps.places.AutocompleteService()` invokes this mock
174
+ // with `[[Construct]]`. Vitest 4's spy enforces that the impl is
175
+ // a real function (not an arrow), so we use a `function` expression
176
+ // to expose the construct slot. Cast to never to satisfy the
177
+ // strict mocked-class type that vi.fn() infers for arrow impls.
178
+ AutocompleteService: vi.fn().mockImplementation(function () {
179
+ return {
180
+ ...global.window.google.maps.places,
181
+ getPlacePredictions: getPlacePredictionsMock,
182
+ };
183
+ }),
184
+ },
185
+ };
186
+ });
187
+ };
188
+
189
+ /**
190
+ * Replaces `react-helmet-async`'s `Helmet` and `HelmetProvider` with no-op
191
+ * components so head-mutating components rendered under test do not touch
192
+ * the jsdom document.
193
+ *
194
+ * Opt-in: the mock is registered via `vi.doMock` so it is
195
+ * NOT hoisted and only takes effect once `setupHelmetMock()` is called from
196
+ * a test setup file. Tests that need the real `react-helmet-async` (or
197
+ * their own mock) are unaffected unless they explicitly call this.
198
+ *
199
+ * See more details here: https://www.npmjs.com/package/react-helmet-async
200
+ *
201
+ * @example
202
+ * import { setupHelmetMock } from '@trackunit/react-vite-test-setup';
203
+ * setupHelmetMock();
204
+ */
205
+ const setupHelmetMock = () => {
206
+ vi.doMock("react-helmet-async", () => ({
207
+ Helmet: () => null,
208
+ HelmetProvider: () => null,
209
+ }));
210
+ };
211
+
212
+ /**
213
+ * Mocks the IntersectionObserver API for testing environments.
214
+ *
215
+ * This function adds a mock implementation of the IntersectionObserver API to the global window object.
216
+ * The mock implementation provides all the necessary methods (observe, unobserve, disconnect, takeRecords)
217
+ * but with empty implementations, allowing tests of components that use IntersectionObserver to run
218
+ * without errors in Jest's JSDOM environment.
219
+ *
220
+ * Useful for testing components that rely on:
221
+ * - Lazy loading
222
+ * - Infinite scrolling
223
+ * - Visibility-based rendering
224
+ * - Any other intersection-based functionality
225
+ *
226
+ * @example
227
+ * // In your jest setup file
228
+ * import { setupIntersectionObserver } from '@trackunit/react-vite-test-setup';
229
+ *
230
+ * setupIntersectionObserver();
231
+ */
232
+ const setupIntersectionObserver = () => (window.IntersectionObserver = MockIntersectionObserver);
233
+ class MockIntersectionObserver {
234
+ constructor() {
235
+ this.root = null;
236
+ this.rootMargin = "";
237
+ this.scrollMargin = "";
238
+ this.thresholds = [];
239
+ }
240
+ disconnect() {
241
+ // Empty
242
+ }
243
+ observe() {
244
+ // Empty
245
+ }
246
+ takeRecords() {
247
+ return [];
248
+ }
249
+ unobserve() {
250
+ // Empty
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Creates a new listener registry for tracking event subscriptions.
256
+ */
257
+ const createListenerRegistry = () => ({
258
+ regular: new Map(),
259
+ once: new Map(),
260
+ });
261
+ /**
262
+ * Mock LngLat class matching Mapbox GL's interface.
263
+ * Returns actual objects with lng/lat properties rather than class instances.
264
+ */
265
+ const createMockLngLat = (lng, lat) => ({
266
+ lng,
267
+ lat,
268
+ wrap: () => createMockLngLat(lng, lat),
269
+ toArray: () => [lng, lat],
270
+ distanceTo: () => 0,
271
+ toBounds: () => createMockLngLatBounds(lng - 1, lat - 1, lng + 1, lat + 1),
272
+ toString: () => `LngLat(${lng}, ${lat})`,
273
+ toEcef: () => [0, 0, 0],
274
+ });
275
+ /**
276
+ * Mock LngLatBounds matching Mapbox GL's interface.
277
+ */
278
+ const createMockLngLatBounds = (west, south, east, north) => {
279
+ const sw = createMockLngLat(west, south);
280
+ const ne = createMockLngLat(east, north);
281
+ const bounds = {
282
+ _sw: sw,
283
+ _ne: ne,
284
+ getSouthWest: () => sw,
285
+ getNorthEast: () => ne,
286
+ getNorthWest: () => createMockLngLat(west, north),
287
+ getSouthEast: () => createMockLngLat(east, south),
288
+ getWest: () => west,
289
+ getSouth: () => south,
290
+ getEast: () => east,
291
+ getNorth: () => north,
292
+ getCenter: () => createMockLngLat((west + east) / 2, (south + north) / 2),
293
+ toArray: () => [
294
+ [west, south],
295
+ [east, north],
296
+ ],
297
+ toString: () => `LngLatBounds(${sw.toString()}, ${ne.toString()})`,
298
+ isEmpty: () => false,
299
+ contains: () => true,
300
+ extend: () => bounds,
301
+ setNorthEast: () => bounds,
302
+ setSouthWest: () => bounds,
303
+ };
304
+ return bounds;
305
+ };
306
+ /**
307
+ * Creates a mock Mapbox map instance for unit testing adapter instances directly.
308
+ *
309
+ * Use this when you need fine-grained control over mock behavior, such as:
310
+ * - Testing adapter instance methods (connect, setCenter, etc.)
311
+ * - Simulating map events (idle, click, movestart)
312
+ * - Verifying method calls on the map
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * import { createMockMapboxMap } from "@trackunit/react-vite-test-setup";
317
+ *
318
+ * it("should connect to map", () => {
319
+ * const { map, triggerEvent } = createMockMapboxMap();
320
+ * const adapter = new MapboxAdapterInstance(config);
321
+ *
322
+ * adapter.connect(map as unknown as mapboxgl.Map);
323
+ * triggerEvent("idle");
324
+ *
325
+ * expect(adapter.getState().isReady).toBe(true);
326
+ * });
327
+ * ```
328
+ */
329
+ const createMockMapboxMap = () => {
330
+ const registry = createListenerRegistry();
331
+ const addListener = (store, event, handler) => {
332
+ const handlers = store.get(event);
333
+ if (handlers === undefined) {
334
+ store.set(event, new Set([handler]));
335
+ }
336
+ else {
337
+ handlers.add(handler);
338
+ }
339
+ return map;
340
+ };
341
+ const map = {
342
+ on: vi.fn((event, handler) => addListener(registry.regular, event, handler)),
343
+ once: vi.fn((event, handler) => addListener(registry.once, event, handler)),
344
+ off: vi.fn((event, handler) => {
345
+ registry.regular.get(event)?.delete(handler);
346
+ return map;
347
+ }),
348
+ remove: vi.fn(),
349
+ getCenter: vi.fn(() => createMockLngLat(0, 0)),
350
+ getZoom: vi.fn(() => 10),
351
+ getBounds: vi.fn(() => createMockLngLatBounds(-1, -1, 1, 1)),
352
+ setCenter: vi.fn((_center) => map),
353
+ setZoom: vi.fn((_zoom) => map),
354
+ panTo: vi.fn((_lngLat) => map),
355
+ panBy: vi.fn((_offset) => map),
356
+ fitBounds: vi.fn((_bounds, _options) => map),
357
+ setStyle: vi.fn((_style) => map),
358
+ isStyleLoaded: vi.fn(() => true),
359
+ isMoving: vi.fn(() => false),
360
+ addSource: vi.fn(),
361
+ removeSource: vi.fn(),
362
+ getSource: vi.fn(() => undefined),
363
+ addLayer: vi.fn(),
364
+ removeLayer: vi.fn(),
365
+ getLayer: vi.fn(() => undefined),
366
+ setPaintProperty: vi.fn(),
367
+ hasImage: vi.fn(() => false),
368
+ addImage: vi.fn(),
369
+ getCanvas: vi.fn(() => ({ style: {} })),
370
+ getCanvasContainer: vi.fn(() => document.createElement("div")),
371
+ };
372
+ const triggerEvent = (eventName, event) => {
373
+ // Fire regular listeners
374
+ const regularHandlers = registry.regular.get(eventName);
375
+ if (regularHandlers !== undefined) {
376
+ regularHandlers.forEach((handler) => handler(event));
377
+ }
378
+ // Fire and clear once listeners
379
+ const onceHandlers = registry.once.get(eventName);
380
+ if (onceHandlers !== undefined) {
381
+ onceHandlers.forEach((handler) => handler(event));
382
+ registry.once.delete(eventName);
383
+ }
384
+ };
385
+ return { map, triggerEvent };
386
+ };
387
+ /**
388
+ * Mock click event matching Mapbox GL's MapMouseEvent shape.
389
+ * Use with triggerEvent("click", createMockClickEvent(...))
390
+ *
391
+ * Note: The `point` property is intentionally omitted because:
392
+ * 1. It requires the full Point class from @mapbox/point-geometry with 30+ methods
393
+ * 2. No tests currently use the point property
394
+ * 3. Since this returns Partial<MapMouseEvent>, point is optional
395
+ */
396
+ const createMockClickEvent = (lng, lat) => ({
397
+ lngLat: createMockLngLat(lng, lat),
398
+ originalEvent: new MouseEvent("click"),
399
+ type: "click",
400
+ });
401
+ /**
402
+ * Sets up mocks for Mapbox GL JS in testing environments.
403
+ *
404
+ * Replaces `mapbox-gl`'s `Map` and `Marker` constructors with deterministic
405
+ * fakes (the `Map` mock auto-fires the `load` event so renderer tests don't
406
+ * have to wait for it) and registers a `beforeEach` hook that clears mock
407
+ * call records between tests.
408
+ *
409
+ * Opt-in: the mock is registered via `vi.doMock` so it is
410
+ * NOT hoisted and only takes effect once `setupMapbox()` is called from a
411
+ * test setup file. Tests that need the real `mapbox-gl` (or their own mock)
412
+ * are unaffected unless they explicitly call this.
413
+ *
414
+ * @example
415
+ * ```typescript
416
+ * // In your test file
417
+ * import { setupMapbox } from "@trackunit/react-vite-test-setup";
418
+ *
419
+ * setupMapbox();
420
+ *
421
+ * describe("MapboxRenderer", () => {
422
+ * it("renders map when loaded", async () => {
423
+ * // The mock will auto-fire "load" event
424
+ * render(<MapboxRenderer adapterInstance={instance} />);
425
+ * await waitFor(() => expect(screen.getByRole("application")).toBeInTheDocument());
426
+ * });
427
+ * });
428
+ * ```
429
+ */
430
+ const setupMapbox = () => {
431
+ vi.doMock("mapbox-gl", () => {
432
+ // SUT does `new mapboxgl.Map(...)` and `new mapboxgl.Marker(...)`. Vitest
433
+ // 4's spy invokes the implementation with `new` when the mock itself is
434
+ // called with `new`, and arrow functions are not constructable. Use
435
+ // `function` expressions so the implementation has a working
436
+ // `[[Construct]]` slot.
437
+ const createMockMapWithAutoLoad = function () {
438
+ const result = createMockMapboxMap();
439
+ const originalOn = result.map.on;
440
+ result.map.on = vi.fn((event, handler) => {
441
+ originalOn(event, handler);
442
+ if (event === "load") {
443
+ setTimeout(() => handler(undefined), 0);
444
+ }
445
+ return result.map;
446
+ });
447
+ return result.map;
448
+ };
449
+ const createMockMarker = function (options) {
450
+ const el = options?.element ?? document.createElement("div");
451
+ const marker = {
452
+ setLngLat: vi.fn(() => marker),
453
+ addTo: vi.fn(() => marker),
454
+ remove: vi.fn(),
455
+ getElement: vi.fn(() => el),
456
+ setOffset: vi.fn(() => marker),
457
+ getLngLat: vi.fn(() => createMockLngLat(0, 0)),
458
+ setPopup: vi.fn(() => marker),
459
+ getPopup: vi.fn(() => null),
460
+ togglePopup: vi.fn(() => marker),
461
+ setDraggable: vi.fn(() => marker),
462
+ isDraggable: vi.fn(() => false),
463
+ };
464
+ return marker;
465
+ };
466
+ return {
467
+ __esModule: true,
468
+ default: {
469
+ Map: vi.fn(createMockMapWithAutoLoad),
470
+ Marker: vi.fn(createMockMarker),
471
+ accessToken: "",
472
+ },
473
+ };
474
+ });
475
+ beforeEach(() => {
476
+ vi.clearAllMocks();
477
+ });
478
+ };
479
+
480
+ /**
481
+ * Mocks the window.matchMedia API for testing environments.
482
+ *
483
+ * This function creates a mock implementation of the window.matchMedia method that
484
+ * is commonly used for responsive design and media queries. The mock always returns
485
+ * a MediaQueryList-like object with standard methods and properties, but with default
486
+ * values (matches set to false).
487
+ *
488
+ * This allows tests of components that use media queries to run without errors in
489
+ * Jest's JSDOM environment, which doesn't implement matchMedia natively.
490
+ *
491
+ * Ideal for testing:
492
+ * - Responsive components
493
+ * - Components that adapt to screen size changes
494
+ * - Components that use CSS media query matching in JavaScript
495
+ *
496
+ * @example
497
+ * // In your jest setup file
498
+ * import { setupMatchMediaMock } from '@trackunit/react-vite-test-setup';
499
+ *
500
+ * setupMatchMediaMock();
501
+ */
502
+ const setupMatchMediaMock = () => {
503
+ Object.defineProperty(window, "matchMedia", {
504
+ writable: true,
505
+ value: vi.fn().mockImplementation(query => ({
506
+ matches: false,
507
+ media: query,
508
+ onchange: null,
509
+ addListener: vi.fn(), // Deprecated
510
+ removeListener: vi.fn(), // Deprecated
511
+ addEventListener: vi.fn(),
512
+ removeEventListener: vi.fn(),
513
+ dispatchEvent: vi.fn(),
514
+ })),
515
+ });
516
+ };
517
+
518
+ /**
519
+ * Default factory for `new ResizeObserver(...)` invocations. Returns a
520
+ * fresh object per call so tests can spy on / replace per-instance
521
+ * methods without affecting later constructions.
522
+ */
523
+ const buildResizeObserverInstance = () => ({
524
+ observe: vi.fn(),
525
+ unobserve: vi.fn(),
526
+ disconnect: vi.fn(),
527
+ });
528
+ /**
529
+ * Mock the ResizeObserver to be able to test components that use a resize
530
+ * observer (e.g. useMeasure, useContainerBreakpoints, and components that
531
+ * use them like BaseInput). Recommended for all React libs.
532
+ *
533
+ * Implementation note: under Vitest the mock was a single
534
+ * `vi.fn().mockImplementation(...)` so callers could:
535
+ * 1. `new global.ResizeObserver(cb)` from the SUT (the implementation
536
+ * function is callable with `new` because it's a `function` expression
537
+ * which has a working `[[Construct]]` slot), AND
538
+ * 2. `(global.ResizeObserver as Mock).mockImplementation(...)` from the
539
+ * spec to capture the callback / inspect constructor arguments.
540
+ *
541
+ * Vitest 4's `vi.fn()` is also constructable when the impl is a `function`
542
+ * expression (arrow functions are *not* constructable), so we keep the
543
+ * same shape - this preserves both call sites that the migration would
544
+ * otherwise have to rewrite.
545
+ */
546
+ const setupResizeObserver = () => {
547
+ global.ResizeObserver = vi.fn().mockImplementation(function (_callback) {
548
+ return buildResizeObserverInstance();
549
+ });
550
+ };
551
+
552
+ if (typeof global !== "undefined") {
553
+ global.TextEncoder = global.TextEncoder || TextEncoder;
554
+ global.TextDecoder = global.TextDecoder || TextDecoder;
555
+ }
556
+ /**
557
+ * Sets up internationalization and translation mocks for testing environments.
558
+ *
559
+ * Mock translations for react-i18next and @trackunit/i18n-library-translation. It creates
560
+ * simple mock implementations that return the key string as the translation, which allows
561
+ * for testing internationalized components without the complexity of actual translations.
562
+ *
563
+ * The mocks support common translation components and hooks like Trans, useTranslation,
564
+ * NamespaceTrans, and useNamespaceTranslation.
565
+ *
566
+ * @example
567
+ * // In your jest setup file
568
+ * import { setupTranslations } from '@trackunit/react-vite-test-setup';
569
+ *
570
+ * setupTranslations();
571
+ */
572
+ const setupTranslations = () => {
573
+ const hasChildren = (node) => node && (node.children || (node.props && node.props.children));
574
+ const getChildren = (node) => (node && node.children ? node.children : node.props && node.props.children);
575
+ const renderNodes = (reactNodes) => {
576
+ if (typeof reactNodes === "string") {
577
+ return reactNodes;
578
+ }
579
+ return Object.keys(reactNodes).map((key, i) => {
580
+ const child = reactNodes[key];
581
+ const isElement = isValidElement(child);
582
+ if (typeof child === "string") {
583
+ return child;
584
+ }
585
+ if (hasChildren(child)) {
586
+ const inner = renderNodes(getChildren(child));
587
+ return cloneElement(child, { ...child.props, key: i }, inner);
588
+ }
589
+ if (typeof child === "object" && !isElement) {
590
+ return Object.keys(child).reduce((str, childKey) => `${str}${child[childKey]}`, "");
591
+ }
592
+ return child;
593
+ });
594
+ };
595
+ const i18n = { language: "en", exists: () => true };
596
+ const useMock = [
597
+ (k, extra) => k + (extra ? " props: " + JSON.stringify(extra) : ""),
598
+ i18n,
599
+ ];
600
+ useMock.t = (k, extra) => k + (extra ? " props: " + JSON.stringify(extra) : "");
601
+ useMock.i18n = i18n;
602
+ const useTranslation = () => useMock;
603
+ const Trans = ({ i18nKey, components, children }) => {
604
+ const result = [];
605
+ result.push(renderNodes([i18nKey]));
606
+ if (Array.isArray(children)) {
607
+ result.push(...renderNodes(children));
608
+ }
609
+ else if (children && "toArray" in children) {
610
+ result.push(...renderNodes(children.toArray()));
611
+ }
612
+ else {
613
+ result.push(...renderNodes([children]));
614
+ }
615
+ if (components) {
616
+ result.push(...Object.keys(components).map(key => jsx("span", { children: components[key] }, key)));
617
+ }
618
+ return jsx(Fragment, { children: result });
619
+ };
620
+ const Translation = ({ children }) => children((k) => k, { i18n });
621
+ vi.doMock("react-i18next", () => {
622
+ return {
623
+ // this mock makes sure any components using the translate HoC receive the t function as a prop
624
+ Trans,
625
+ Translation,
626
+ useTranslation,
627
+ // mock if needed
628
+ I18nextProvider: reactI18next.I18nextProvider,
629
+ initReactI18next: reactI18next.initReactI18next,
630
+ setDefaults: reactI18next.setDefaults,
631
+ getDefaults: reactI18next.getDefaults,
632
+ setI18n: reactI18next.setI18n,
633
+ getI18n: reactI18next.getI18n,
634
+ };
635
+ });
636
+ vi.doMock("@trackunit/i18n-library-translation", async () => ({
637
+ ...(await vi.importActual("@trackunit/i18n-library-translation")),
638
+ NamespaceTrans: (props) => jsx(Trans, { ...props }),
639
+ useNamespaceTranslation: useTranslation,
640
+ }));
641
+ };
642
+
643
+ /**
644
+ * Flushes all promises in the queue.
645
+ * This is useful when testing async code.
646
+ *
647
+ * @param waitTimeInMS - The amount of time to wait before resolving the promise.
648
+ * @returns {Promise<void>} - Returns a promise that resolves after the wait time.
649
+ */
650
+ const flushPromisesInAct = (waitTimeInMS = 0) => {
651
+ return act(() => {
652
+ return new Promise(resolve => {
653
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
654
+ if (global.ORG_setTimeout) {
655
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
656
+ return global.ORG_setTimeout(() => global.ORG_setTimeout(resolve, waitTimeInMS), 1);
657
+ }
658
+ else {
659
+ setTimeout(() => setTimeout(resolve, waitTimeInMS), 1);
660
+ }
661
+ });
662
+ });
663
+ };
664
+ afterEach(async () => {
665
+ // Restore real timers BEFORE cleanup / flushPromisesInAct. Both rely on
666
+ // React's internal scheduler resolving microtasks; under Vitest 4 fake
667
+ // timers that scheduler waits on the fake clock, so an act-wrapped promise
668
+ // chained through `setTimeout` will never resolve until the clock is
669
+ // advanced. Putting `vi.useRealTimers()` first guarantees cleanup is not
670
+ // gated on whatever fake-timer state the test left installed.
671
+ // `vi.useRealTimers()` is a no-op when no fake timers are installed.
672
+ vi.useRealTimers();
673
+ vi.clearAllMocks();
674
+ cleanup();
675
+ await flushPromisesInAct();
676
+ });
677
+ const setupResponseForTanstackRouter = () => {
678
+ // Polyfill for Response global that TanStack Router v1+ requires
679
+ if (typeof globalThis.Response === "undefined") {
680
+ // Simple polyfill for Response in test environment
681
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
682
+ globalThis.Response = class Response {
683
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
684
+ constructor(body, init) {
685
+ this.status = init?.status ?? 200;
686
+ this.statusText = init?.statusText ?? "";
687
+ this.headers = new Headers(init?.headers);
688
+ this.body = body;
689
+ this.ok = this.status >= 200 && this.status < 300;
690
+ this.redirected = false;
691
+ this.type = "default";
692
+ this.url = "";
693
+ }
694
+ static redirect(url, status = 302) {
695
+ return new Response(null, { status, headers: { Location: url } });
696
+ }
697
+ static error() {
698
+ return new Response(null, { status: 0 });
699
+ }
700
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
701
+ static json(data, init) {
702
+ return new Response(JSON.stringify(data), {
703
+ ...init,
704
+ headers: {
705
+ "Content-Type": "application/json",
706
+ ...init?.headers,
707
+ },
708
+ });
709
+ }
710
+ clone() {
711
+ return new Response(this.body, {
712
+ status: this.status,
713
+ statusText: this.statusText,
714
+ headers: this.headers,
715
+ });
716
+ }
717
+ };
718
+ }
719
+ if (typeof globalThis.Headers === "undefined") {
720
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
721
+ globalThis.Headers = class Headers {
722
+ constructor(init) {
723
+ this.map = new Map();
724
+ if (init) {
725
+ if (Array.isArray(init)) {
726
+ init.forEach(([key, value]) => this.set(key, value));
727
+ }
728
+ else if (init instanceof Headers) {
729
+ init.forEach((value, key) => this.set(key, value));
730
+ }
731
+ else {
732
+ Object.entries(init).forEach(([key, value]) => this.set(key, value));
733
+ }
734
+ }
735
+ }
736
+ append(name, value) {
737
+ this.map.set(name.toLowerCase(), value);
738
+ }
739
+ delete(name) {
740
+ this.map.delete(name.toLowerCase());
741
+ }
742
+ get(name) {
743
+ return this.map.get(name.toLowerCase()) || null;
744
+ }
745
+ has(name) {
746
+ return this.map.has(name.toLowerCase());
747
+ }
748
+ set(name, value) {
749
+ this.map.set(name.toLowerCase(), value);
750
+ }
751
+ forEach(callback) {
752
+ this.map.forEach((value, key) => callback(value, key, this));
753
+ }
754
+ entries() {
755
+ return this.map.entries();
756
+ }
757
+ keys() {
758
+ return this.map.keys();
759
+ }
760
+ values() {
761
+ return this.map.values();
762
+ }
763
+ [Symbol.iterator]() {
764
+ return this.map.entries();
765
+ }
766
+ };
767
+ }
768
+ };
769
+ /**
770
+ * Sets up React Testing Library and Okta authentication mocks for testing.
771
+ *
772
+ * Behaviour:
773
+ * - The top-level `afterEach` further up this file (which clears mocks
774
+ * and runs RTL `cleanup()`) is registered as a side effect of importing
775
+ * this module - that's intentional and existed before this refactor.
776
+ * - Calling `setupReactTestingLibrary()` additionally:
777
+ * 1. Registers the `@okta/okta-react` mock via `vi.doMock` so
778
+ * `useOktaAuth()` returns a fixed authenticated session. Opt-in:
779
+ * the mock is NOT hoisted, so tests that need the real okta
780
+ * hook (or their own mock) are unaffected unless they explicitly
781
+ * call this.
782
+ * 2. Installs the tanstack-router Response polyfill (keeps it
783
+ * opt-in because it mutates `globalThis.Response`);
784
+ * 3. Calls `setupTranslations()` so the
785
+ * `useTranslation`/`useNamespaceTranslation` hooks always
786
+ * return a mocked i18next instance. Under Vitest, the
787
+ * shared jest-preset's `setupFilesAfterEnv` is no longer
788
+ * loaded, so projects that previously got translation
789
+ * setup "for free" otherwise blow up at the first
790
+ * `i18NextInstance.exists is not a function` call. Baking
791
+ * it in here means projects can't forget it - the cost is
792
+ * mocking i18n in tests that don't use it (harmless: the
793
+ * mock only kicks in if the spec imports react-i18next).
794
+ * 4. Installs `setupMatchMediaMock()`, `setupResizeObserver()`
795
+ * and `setupIntersectionObserver()`. Every component test
796
+ * needs these three under jsdom, and missing any of them
797
+ * causes React's render to throw, which `vitest-fail-on-console`
798
+ * then re-raises as "Expected test not to call console.error()".
799
+ * Under jest the workspace preset installed them via
800
+ * `setupFilesAfterEnv`; under vitest only what each project's
801
+ * setup file explicitly imports runs, so most setup files
802
+ * called `setupReactTestingLibrary()` plus `setupFailOnConsole()`
803
+ * but forgot the basic-DOM triplet. Folding the triplet in
804
+ * here means "set up RTL" reads naturally as "set up
805
+ * everything you need to render a component".
806
+ *
807
+ * @example
808
+ * import { setupReactTestingLibrary } from '@trackunit/react-vite-test-setup';
809
+ * setupReactTestingLibrary();
810
+ */
811
+ const setupReactTestingLibrary = () => {
812
+ const mockedClaims = {
813
+ sub: "sub",
814
+ name: "user",
815
+ };
816
+ const mockedIdToken = {
817
+ idToken: "idToken",
818
+ claims: mockedClaims,
819
+ expiresAt: Date.now(),
820
+ authorizeUrl: "authorize.url",
821
+ scopes: [],
822
+ issuer: "issuer",
823
+ clientId: "clientId",
824
+ };
825
+ const mockedAccessToken = {
826
+ accessToken: "accessToken",
827
+ claims: mockedClaims,
828
+ tokenType: "tokenType",
829
+ userinfoUrl: "userinfo.url",
830
+ expiresAt: Date.now(),
831
+ authorizeUrl: "authorize.url",
832
+ scopes: [],
833
+ };
834
+ const mockedAuthState = {
835
+ accessToken: mockedAccessToken,
836
+ idToken: mockedIdToken,
837
+ isAuthenticated: true,
838
+ };
839
+ const mockedOktaAuth = () => {
840
+ return {
841
+ oktaAuth: {
842
+ tokenManager: {
843
+ on: vi.fn(),
844
+ renew: vi.fn(),
845
+ setTokens: vi.fn(),
846
+ clear: vi.fn(),
847
+ },
848
+ getOriginalUri: vi.fn(),
849
+ signOut: vi.fn().mockResolvedValue({ postLogoutRedirectUri: "mocked-url", clearTokensBeforeRedirect: true }),
850
+ token: {
851
+ getWithRedirect: vi.fn(),
852
+ getWithoutPrompt: vi.fn(),
853
+ },
854
+ session: {
855
+ get: () => {
856
+ return { status: "ACTIVE" };
857
+ },
858
+ },
859
+ authStateManager: {
860
+ getAuthState: vi.fn(),
861
+ subscribe: vi.fn(),
862
+ unsubscribe: vi.fn(),
863
+ },
864
+ closeSession: vi.fn(),
865
+ options: {
866
+ restoreOriginalUri: "",
867
+ },
868
+ start: vi.fn(),
869
+ setOriginalUri: vi.fn(),
870
+ },
871
+ authState: mockedAuthState,
872
+ };
873
+ };
874
+ vi.doMock("@okta/okta-react", () => ({
875
+ useOktaAuth: () => mockedOktaAuth(),
876
+ }));
877
+ setupResponseForTanstackRouter();
878
+ setupTranslations();
879
+ setupMatchMediaMock();
880
+ setupResizeObserver();
881
+ setupIntersectionObserver();
882
+ };
883
+
884
+ /**
885
+ * Mocks the `react-virtualized-auto-sizer` component for testing
886
+ * environments.
887
+ *
888
+ * Provides fixed dimensions (600x600) so tests don't need real DOM
889
+ * measurements. Especially useful for components using virtualized lists or
890
+ * grids.
891
+ *
892
+ * Opt-in: the mock is registered via `vi.doMock` so it is
893
+ * NOT hoisted and only takes effect once `setupReactVirtualizedAutoSizer()`
894
+ * is called from a test setup file. Tests that need the real component (or
895
+ * their own mock) are unaffected unless they explicitly call this.
896
+ *
897
+ * @example
898
+ * import { setupReactVirtualizedAutoSizer } from '@trackunit/react-vite-test-setup';
899
+ * setupReactVirtualizedAutoSizer();
900
+ */
901
+ const setupReactVirtualizedAutoSizer = () => {
902
+ vi.doMock("react-virtualized-auto-sizer", () => ({ children }) => children({ height: 600, width: 600, scaledWidth: 600, scaledHeight: 600 }));
903
+ };
904
+
905
+ /**
906
+ * Mocks the `@tanstack/react-virtual` library for testing environments.
907
+ *
908
+ * Replaces `useVirtualizer` with a deterministic implementation that
909
+ * returns every item (no real DOM virtualisation), so tests can assert on
910
+ * the full rendered list without worrying about scroll-driven windowing.
911
+ *
912
+ * Opt-in: the mock is registered via `vi.doMock` so it is
913
+ * NOT hoisted and only takes effect once `setupTanstackReactVirtual()` is
914
+ * called from a test setup file. Tests that need the real virtualizer (or
915
+ * their own mock) are unaffected unless they explicitly call this.
916
+ *
917
+ * @example
918
+ * import { setupTanstackReactVirtual } from '@trackunit/react-vite-test-setup';
919
+ * setupTanstackReactVirtual();
920
+ */
921
+ const setupTanstackReactVirtual = () => {
922
+ vi.doMock("@tanstack/react-virtual", () => ({
923
+ useVirtualizer: ({ count }) => ({
924
+ getVirtualItems: () => {
925
+ const result = [];
926
+ for (let i = 0; i < count; i++) {
927
+ result.push({
928
+ index: i,
929
+ start: i * 40,
930
+ key: i,
931
+ measureRef: () => {
932
+ /* noop */
933
+ },
934
+ });
935
+ }
936
+ return result;
937
+ },
938
+ getTotalSize: () => count,
939
+ scrollToIndex: () => {
940
+ /* noop */
941
+ },
942
+ scrollToOffset: () => {
943
+ /* noop */
944
+ },
945
+ scrollToAlign: () => {
946
+ /* noop */
947
+ },
948
+ scrollToItem: () => {
949
+ /* noop */
950
+ },
951
+ resetAfterIndex: () => {
952
+ /* noop */
953
+ },
954
+ resetAfterItem: () => {
955
+ /* noop */
956
+ },
957
+ scrollTo: () => {
958
+ /* noop */
959
+ },
960
+ measure: () => {
961
+ /* noop */
962
+ },
963
+ measureElement: () => 42,
964
+ }),
965
+ }));
966
+ };
967
+
968
+ /* eslint-disable @typescript-eslint/no-explicit-any */
969
+ // Pin the test process timezone to UTC at module load (before the imports
970
+ // below evaluate, since ESM hoists `import` statements above this assignment
971
+ // only inside this module's body, and Node reads `process.env.TZ` lazily for
972
+ // each new Date). This mirrors what `vitest.preset.js` did for the Vitest setup
973
+ // and keeps Date#toISOString / time-of-day assertions stable across machines.
974
+ // Without this, dev environments with non-UTC TZ produce off-by-an-hour
975
+ // failures in tests that build dates from numeric components (e.g. the
976
+ // `InsightsHelper` ISO-string assertions).
977
+ process.env.TZ = "UTC";
978
+ /**
979
+ * Mocks the Temporal API to use a fixed time zone for testing.
980
+ *
981
+ * This function specifically mocks the Temporal.Now.timeZoneId method from the
982
+ * Temporal library to always return "Europe/Copenhagen", ensuring consistent
983
+ * time zone-dependent behavior in tests regardless of where they run.
984
+ *
985
+ * Note: the host process timezone is also pinned to UTC at module load via
986
+ * `process.env.TZ` above, so non-Temporal Date arithmetic is deterministic.
987
+ *
988
+ * @example
989
+ * // In your vitest setup file
990
+ * import { setupTimeZone } from '@trackunit/react-vite-test-setup';
991
+ *
992
+ * setupTimeZone();
993
+ */
994
+ const setupTimeZone = () => {
995
+ vi.spyOn(Temporal.Now, "timeZoneId").mockImplementation(function () {
996
+ return "Europe/Copenhagen";
997
+ });
998
+ };
999
+ /**
1000
+ * Sets up time, animation, and language-related mocks for testing environments.
1001
+ *
1002
+ * This function configures multiple aspects of the testing environment:
1003
+ * 1. Sets a fixed time zone using setupTimeZone()
1004
+ * 2. Disables React Spring animations for faster, deterministic tests
1005
+ * 3. Overrides setTimeout and setInterval to execute immediately (0ms delay)
1006
+ *
1007
+ * These changes make tests faster and more predictable by eliminating real-time
1008
+ * delays, animations, and time zone dependencies that could cause flaky tests.
1009
+ *
1010
+ * @example
1011
+ * // In your jest setup file
1012
+ * import { setupTimeAndLanguage } from '@trackunit/react-vite-test-setup';
1013
+ *
1014
+ * setupTimeAndLanguage();
1015
+ */
1016
+ const setupTimeAndLanguage = () => {
1017
+ setupTimeZone();
1018
+ Globals.assign({
1019
+ skipAnimation: true,
1020
+ });
1021
+ const globalSetTimeout = global.setTimeout;
1022
+ global.ORG_setTimeout = globalSetTimeout;
1023
+ const globalSetInterval = global.setInterval;
1024
+ global.ORG_setInterval = globalSetInterval;
1025
+ global.setTimeout = function testSetTimeout(callback,
1026
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1027
+ ms,
1028
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1029
+ ...args) {
1030
+ return globalSetTimeout.apply(this, [callback, 0]);
1031
+ };
1032
+ global.setInterval = function testSetInterval(callback,
1033
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1034
+ ms,
1035
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1036
+ ...args) {
1037
+ return globalSetInterval.apply(this, [callback, 0]);
1038
+ };
1039
+ };
1040
+
1041
+ /**
1042
+ * Sets up Web Streams API polyfills for testing environments.
1043
+ *
1044
+ * Web Streams API polyfills from web-streams-polyfill. These polyfills provide implementations
1045
+ * of modern streaming APIs that may not be available in the Jest/JSDOM testing environment.
1046
+ *
1047
+ * The setup enables testing of components and utilities that rely on the Web Streams API,
1048
+ * such as those that process streaming data or implement custom stream transformations.
1049
+ *
1050
+ * @example
1051
+ * // In your jest setup file
1052
+ * import { setupWebStreams } from '@trackunit/react-vite-test-setup';
1053
+ *
1054
+ * setupWebStreams();
1055
+ */
1056
+ const setupWebStreams = () => {
1057
+ Object.assign(global, { TransformStream, WritableStream });
1058
+ };
1059
+
1060
+ /**
1061
+ * Sets up all available testing mocks in a single function call.
1062
+ *
1063
+ * This convenience function sets up all the test mocks provided by the library:
1064
+ * - Canvas API mocks
1065
+ * - Console error reporting to fail tests (includes automatic CSS parser error suppression)
1066
+ * - Google Maps API and components mocks
1067
+ * - Mapbox GL JS mocks
1068
+ * - React Helmet mocks
1069
+ * - IntersectionObserver mocks
1070
+ * - MatchMedia API mocks
1071
+ * - React Testing Library and Okta authentication mocks
1072
+ * - React Virtualized AutoSizer mocks
1073
+ * - ResizeObserver mocks
1074
+ * - Tanstack React Virtual mocks
1075
+ * - Time and language related mocks (timezone, timers, etc.)
1076
+ * - Translation mocks (i18n)
1077
+ * - Web Streams API mocks
1078
+ *
1079
+ * Using this function is equivalent to calling each setup function individually.
1080
+ *
1081
+ * @param options Configuration options for individual mocks
1082
+ * @param options.failOnConsoleOptions Options for setupFailOnConsole
1083
+ * @example
1084
+ * // In your jest setup file
1085
+ * import { setupAllMocks } from '@trackunit/react-vite-test-setup';
1086
+ *
1087
+ * setupAllMocks();
1088
+ *
1089
+ * // Or with options for specific mocks:
1090
+ * setupAllMocks({
1091
+ * failOnConsoleOptions: { shouldFailOnWarn: true }
1092
+ * });
1093
+ */
1094
+ const setupAllMocks = (options = {}) => {
1095
+ setupFailOnConsole(options.failOnConsoleOptions);
1096
+ setupGoogleMaps();
1097
+ setupMapbox();
1098
+ setupHelmetMock();
1099
+ setupIntersectionObserver();
1100
+ setupMatchMediaMock();
1101
+ setupReactTestingLibrary();
1102
+ setupReactVirtualizedAutoSizer();
1103
+ setupResizeObserver();
1104
+ setupTanstackReactVirtual();
1105
+ setupTimeAndLanguage();
1106
+ setupTranslations();
1107
+ setupWebStreams();
1108
+ };
1109
+
1110
+ /**
1111
+ * Sets up essential testing mocks with no external library dependencies.
1112
+ *
1113
+ * @param options Configuration options for the mocks
1114
+ */
1115
+ const setupBasicMocks = (options = {}) => {
1116
+ setupIntersectionObserver();
1117
+ setupMatchMediaMock();
1118
+ setupResizeObserver();
1119
+ setupWebStreams();
1120
+ // Time and error handling
1121
+ setupTimeAndLanguage();
1122
+ setupFailOnConsole(options.failOnConsoleOptions);
1123
+ };
1124
+
1125
+ /**
1126
+ * Sets up default testing mocks that cover most common React testing needs.
1127
+ *
1128
+ * This convenience function provides a balanced set of mocks that cover most React
1129
+ * application testing needs. It includes all the basic mocks plus essential React
1130
+ * testing utilities. Specifically, it includes:
1131
+ *
1132
+ * From setupBasicMocks:
1133
+ * - Canvas API mocks
1134
+ * - Console error reporting to fail tests
1135
+ * - IntersectionObserver mocks
1136
+ * - MatchMedia API mocks
1137
+ * - ResizeObserver mocks
1138
+ * - Timer mocks (setTimeout, setInterval)
1139
+ * - Web Streams API mocks
1140
+ *
1141
+ * Plus these additional mocks:
1142
+ * - React Testing Library and Okta authentication mocks
1143
+ * - Translation mocks (i18n)
1144
+ *
1145
+ * This is ideal for most React applications that use i18n translations and need
1146
+ * standard testing environment setup, without requiring specialized mocks for things
1147
+ * like Google Maps or virtualized lists.
1148
+ *
1149
+ * @param options Configuration options for individual mocks
1150
+ * @param options.failOnConsoleOptions Options for setupFailOnConsole
1151
+ * @example
1152
+ * // In your jest setup file
1153
+ * import { setupDefaultMocks } from '@trackunit/react-vite-test-setup';
1154
+ *
1155
+ * setupDefaultMocks();
1156
+ *
1157
+ * // Or with options for specific mocks:
1158
+ * setupDefaultMocks({
1159
+ * failOnConsoleOptions: { shouldFailOnWarn: true }
1160
+ * });
1161
+ */
1162
+ /**
1163
+ * Sets up default testing mocks covering most common React testing scenarios.
1164
+ *
1165
+ * @param options Configuration options for the mocks
1166
+ */
1167
+ const setupDefaultMocks = (options = {}) => {
1168
+ // Set up all basic mocks first
1169
+ setupBasicMocks(options);
1170
+ // Add standard React testing utilities
1171
+ setupReactTestingLibrary();
1172
+ setupTranslations();
1173
+ };
1174
+
1175
+ /**
1176
+ * Mocks the `@tanstack/react-router` library for testing environments.
1177
+ *
1178
+ * Replaces `useNavigate` with a no-op so components that call the
1179
+ * navigation function during a test do not throw outside a router context.
1180
+ *
1181
+ * Opt-in: the mock is registered via `vi.doMock` so it is
1182
+ * NOT hoisted and only takes effect once `setupTanstackReactRouter()` is
1183
+ * called from a test setup file. Tests that need the real `useNavigate`
1184
+ * (or their own mock) are unaffected unless they explicitly call this.
1185
+ *
1186
+ * @example
1187
+ * // In your vitest setup file
1188
+ * import { setupTanstackReactRouter } from '@trackunit/react-vite-test-setup';
1189
+ *
1190
+ * setupTanstackReactRouter();
1191
+ */
1192
+ const setupTanstackReactRouter = () => {
1193
+ vi.doMock("@tanstack/react-router", async () => ({
1194
+ ...(await vi.importActual("@tanstack/react-router")),
1195
+ useNavigate: () => () => {
1196
+ /* noop */
1197
+ },
1198
+ }));
1199
+ };
1200
+
1201
+ export { createMockClickEvent, createMockMapboxMap, setupAllMocks, setupBasicMocks, setupCanvasMock, setupDefaultMocks, setupFailOnConsole, setupGoogleMaps, setupHelmetMock, setupIntersectionObserver, setupMapbox, setupMatchMediaMock, setupReactTestingLibrary, setupReactVirtualizedAutoSizer, setupResizeObserver, setupTanstackReactRouter, setupTanstackReactVirtual, setupTimeAndLanguage, setupTimeZone, setupTranslations, setupWebStreams };