Files
strapi/packages/admin-test-utils/src/setup.ts
Ben Irvin a2be8c63d1 test(front): fix flaky tests (#25651)
* test: ignore not wrapped in act warnings

* test(front): fix waitfor
2026-03-05 15:36:30 +01:00

284 lines
8.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { format } from 'util';
import { ResizeObserver } from '@juggle/resize-observer';
/* -------------------------------------------------------------------------------------------------
* IntersectionObserver
* -----------------------------------------------------------------------------------------------*/
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
observe: () => null,
unobserve: () => null,
disconnect: () => null,
});
window.IntersectionObserver = mockIntersectionObserver;
/* -------------------------------------------------------------------------------------------------
* ResizeObserver
* -----------------------------------------------------------------------------------------------*/
window.ResizeObserver = ResizeObserver;
/* -------------------------------------------------------------------------------------------------
* requestIdleCallback
* -----------------------------------------------------------------------------------------------*/
type SetTimeout = typeof window.setTimeout;
window.setImmediate =
window.setImmediate ||
((fn: Parameters<SetTimeout>[0], ...args: any[]) => window.setTimeout(fn, 0, ...args));
window.clearImmediate = window.clearImmediate || window.clearTimeout;
// @ts-expect-error mocking for testing
window.requestIdleCallback = setImmediate;
// @ts-expect-error mocking for testing
window.cancelIdleCallback = clearImmediate;
/* -------------------------------------------------------------------------------------------------
* ResizeObserver
* -----------------------------------------------------------------------------------------------*/
/**
* If there's a prop type error then we want to throw an
* error so that the test fails.
*
* In the CI we fail the entire process if anything is logged to the console.
* This is to stop pollution, you shouldn't need to log _anything_ to the console
* for tests.
*/
const patchedConsole = {
...window.console,
warn(...args: any[]) {
const msg = args
.map((arg) => {
if (arg && typeof arg === 'object' && 'message' in arg) {
return String((arg as any).message);
}
return String(arg);
})
.join(' ');
/**
* React Router emits dev-only warnings about upcoming v7 behavior when `future`
* flags are left undefined. We don't want test log pollution or flaky failures
* from these informational warnings, so we drop them entirely.
*
* Everything else remains strict: any other warn fails the test.
*/
if (/Future Flag Warning/i.test(msg)) {
return;
}
throw new Error(format(...args));
},
error(...args: any[]) {
const message = format(...args);
if (
/React does not recognize the .* prop on a DOM element/.test(message) ||
/Unknown event handler property/.test(message)
) {
return;
}
// Ignore act() warnings from sonner components which update state asynchronously
if (/Support for defaultProps will be removed/gi.test(message)) {
return;
}
// React warns when state updates run outside act(). That often happens when
// store/RTK Query (or other async) updates complete after the test has already
// found an element. Suppressing so tests don't fail for that; however, we should
// fix this in each test by waiting for stable UI or mocking async.
if (/inside a test was not wrapped in act/i.test(message)) {
return;
}
throw new Error(message);
},
};
window.console = patchedConsole;
// Ensure `console.warn` (global) is patched too
globalThis.console = patchedConsole as any;
/* -------------------------------------------------------------------------------------------------
* Strapi
* -----------------------------------------------------------------------------------------------*/
window.strapi = {
backendURL: 'http://localhost:1337',
isEE: false,
features: {
SSO: 'sso',
isEnabled: () => false,
},
future: {
isEnabled: () => false,
},
projectType: 'Community',
telemetryDisabled: true,
flags: {
nps: true,
promoteEE: true,
},
};
/* -------------------------------------------------------------------------------------------------
* matchMedia
* -----------------------------------------------------------------------------------------------*/
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
/**
* @deprecated
*/
addListener: jest.fn(),
/**
* @deprecated
*/
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
/* -------------------------------------------------------------------------------------------------
* scrollTo
* -----------------------------------------------------------------------------------------------*/
Object.defineProperty(window, 'scrollTo', {
writable: true,
value: jest.fn(),
});
/* -------------------------------------------------------------------------------------------------
* prompt
* -----------------------------------------------------------------------------------------------*/
Object.defineProperty(window, 'prompt', {
writable: true,
value: jest.fn(),
});
/* -------------------------------------------------------------------------------------------------
* URL
* -----------------------------------------------------------------------------------------------*/
window.URL.createObjectURL = jest
.fn()
.mockImplementation((file) => `http://localhost:4000/assets/${file.name}`);
/* -------------------------------------------------------------------------------------------------
* createRange
* -----------------------------------------------------------------------------------------------*/
document.createRange = () => {
const range = new Range();
// @ts-expect-error we don't need to implement all the methods
range.getClientRects = jest.fn(() => ({
item: () => null,
length: 0,
}));
return range;
};
/* -------------------------------------------------------------------------------------------------
* localStorage
* -----------------------------------------------------------------------------------------------*/
class LocalStorageMock {
store: Map<string, string>;
constructor() {
this.store = new Map();
}
clear() {
this.store.clear();
}
getItem(key: string) {
/**
* We return null to avoid returning `undefined`
* because `undefined` is not a valid JSON value.
*/
return this.store.get(key) ?? null;
}
setItem(key: string, value: unknown) {
this.store.set(key, String(value));
}
removeItem(key: string) {
this.store.delete(key);
}
get length() {
return this.store.size;
}
}
Object.defineProperty(window, 'localStorage', {
writable: true,
value: new LocalStorageMock(),
});
/* -------------------------------------------------------------------------------------------------
* PointerEvents
* -----------------------------------------------------------------------------------------------*/
/**
* JSDOM doesn't implement PointerEvent so we need to mock our own implementation
* Default to mouse left click interaction
* https://github.com/radix-ui/primitives/issues/1822
* https://github.com/jsdom/jsdom/pull/2666
*/
class MockPointerEvent extends Event {
button: number;
ctrlKey: boolean;
pointerType: string;
constructor(
type: string,
props: EventInit & { button?: number; ctrlKey?: boolean; pointerType?: string }
) {
super(type, props);
this.button = props.button || 0;
this.ctrlKey = props.ctrlKey || false;
this.pointerType = props.pointerType || 'mouse';
}
}
Object.defineProperty(window, 'PointerEvent', {
writable: true,
value: MockPointerEvent,
});
window.HTMLElement.prototype.scrollIntoView = jest.fn();
window.HTMLElement.prototype.releasePointerCapture = jest.fn();
window.HTMLElement.prototype.hasPointerCapture = jest.fn();
/* -------------------------------------------------------------------------------------------------
* Navigator
* -----------------------------------------------------------------------------------------------*/
/**
* Navigator is a large object so we only mock the properties we need.
*/
Object.assign(navigator, {
clipboard: {
writeText: jest.fn(),
},
});