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