Skip to content

Commit

Permalink
Merge pull request #716 from thebuilder/fix/improve-is-mocking
Browse files Browse the repository at this point in the history
  • Loading branch information
thebuilder authored Jan 21, 2025
2 parents f098c1e + 4e71f09 commit 27681db
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/__tests__/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ test("should handle defaultFallbackInView if unsupported", () => {
);
});

test("should restore the browser IntersectingObserver", () => {
test("should restore the browser IntersectionObserver", () => {
expect(vi.isMockFunction(window.IntersectionObserver)).toBe(true);
destroyIntersectionMocking();

Expand Down
48 changes: 31 additions & 17 deletions src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,53 @@ type Item = {
created: number;
};

let isMocking = false;

const observers = new Map<IntersectionObserver, Item>();

// Store a reference to the original `IntersectionObserver` so we can restore it later.
// This can be relevant if testing in a browser environment, where you actually have a native `IntersectionObserver`.
const originalIntersectionObserver =
typeof window !== "undefined" ? window.IntersectionObserver : undefined;

/**
* Get the test utility object, depending on the environment. This could be either `vi` (Vitest) or `jest`.
* Type is mapped to Vitest, so we don't mix in Jest types when running in Vitest.
*/
function testLibraryUtil(): typeof vi | undefined {
if (typeof vi !== "undefined") return vi;
// @ts-expect-error We don't include the Jest types
if (typeof jest !== "undefined") return jest;
return undefined;
}

/**
* Check if the IntersectionObserver is currently being mocked.
* @return boolean
*/
function isMocking() {
const util = testLibraryUtil();
if (util && typeof util.isMockFunction === "function") {
return util.isMockFunction(window.IntersectionObserver);
}
return false;
}

/*
** If we are running in a valid testing environment, we can automate mocking the IntersectionObserver.
*/
if (
typeof window !== "undefined" &&
typeof beforeAll !== "undefined" &&
typeof beforeEach !== "undefined" &&
typeof afterEach !== "undefined"
) {
const initMocking = () => {
// Use the exposed mock function. Currently, it supports Jest (`jest.fn`) and Vitest with globals (`vi.fn`).
// @ts-ignore
if (typeof jest !== "undefined") setupIntersectionMocking(jest.fn);
else if (typeof vi !== "undefined") {
setupIntersectionMocking(vi.fn);
beforeEach(() => {
const util = testLibraryUtil();
if (util) {
setupIntersectionMocking(util.fn);
}
};
// Ensure there's no observers from previous tests
observers.clear();
});

beforeAll(initMocking);
beforeEach(initMocking);
afterEach(resetIntersectionMocking);
}

Expand All @@ -57,7 +75,7 @@ function getActFn() {
}

function warnOnMissingSetup() {
if (isMocking) return;
if (isMocking()) return;
console.error(
`React Intersection Observer was not configured to handle mocking.
Outside Jest and Vitest, you might need to manually configure it by calling setupIntersectionMocking() and resetIntersectionMocking() in your test setup file.
Expand All @@ -82,7 +100,6 @@ afterEach(() => {
* @param mockFn The mock function to use. Defaults to `vi.fn`.
*/
export function setupIntersectionMocking(mockFn: typeof vi.fn) {
if (isMocking) return;
window.IntersectionObserver = mockFn((cb, options = {}) => {
const item = {
callback: cb,
Expand Down Expand Up @@ -111,8 +128,6 @@ export function setupIntersectionMocking(mockFn: typeof vi.fn) {

return instance;
});

isMocking = true;
}

/**
Expand All @@ -137,7 +152,6 @@ export function destroyIntersectionMocking() {
resetIntersectionMocking();
// @ts-ignore
window.IntersectionObserver = originalIntersectionObserver;
isMocking = false;
}

function triggerIntersection(
Expand Down

0 comments on commit 27681db

Please sign in to comment.