Skip to content

Commit f098c1e

Browse files
authored
Merge pull request #715 from thebuilder/feat/restore-beforeach
2 parents f3213dd + 18de17e commit f098c1e

File tree

3 files changed

+39
-8
lines changed

3 files changed

+39
-8
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,13 @@ will emulate the real IntersectionObserver, allowing you to validate that your
251251
components are behaving as expected.
252252
253253
| Method | Description |
254-
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
254+
|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
255255
| `mockAllIsIntersecting(isIntersecting)` | Set `isIntersecting` on all current Intersection Observer instances. The value of `isIntersecting` should be either a `boolean` or a threshold between 0 and 1. |
256256
| `mockIsIntersecting(element, isIntersecting)` | Set `isIntersecting` for the Intersection Observer of a specific `element`. The value of `isIntersecting` should be either a `boolean` or a threshold between 0 and 1. |
257257
| `intersectionMockInstance(element)` | Call the `intersectionMockInstance` method with an element, to get the (mocked) `IntersectionObserver` instance. You can use this to spy on the `observe` and`unobserve` methods. |
258258
| `setupIntersectionMocking(mockFn)` | Mock the `IntersectionObserver`, so we can interact with them in tests - Should be called in `beforeEach`. (**Done automatically in Jest environment**) |
259-
| `resetIntersectionMocking()` | Reset the mocks on `IntersectionObserver` - Should be called in `afterEach`. (**Done automatically in Jest environment**) |
259+
| `resetIntersectionMocking()` | Reset the mocks on `IntersectionObserver` - Should be called in `afterEach`. (**Done automatically in Jest/Vitest environment**) |
260+
| `destroyIntersectionMocking()` | Destroy the mocked `IntersectionObserver` function, and return `window.IntersectionObserver` to the original browser implementation |
260261
261262
### Testing Libraries
262263

src/__tests__/hooks.test.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { render, screen } from "@testing-library/react";
22
import React, { useCallback } from "react";
33
import { type IntersectionOptions, defaultFallbackInView } from "../index";
44
import {
5+
destroyIntersectionMocking,
56
intersectionMockInstance,
67
mockAllIsIntersecting,
78
mockIsIntersecting,
@@ -342,6 +343,7 @@ test("should set intersection ratio as the largest threshold smaller than trigge
342343
});
343344

344345
test("should handle fallback if unsupported", () => {
346+
destroyIntersectionMocking();
345347
// @ts-ignore
346348
window.IntersectionObserver = undefined;
347349
const { rerender } = render(
@@ -363,6 +365,7 @@ test("should handle fallback if unsupported", () => {
363365
});
364366

365367
test("should handle defaultFallbackInView if unsupported", () => {
368+
destroyIntersectionMocking();
366369
// @ts-ignore
367370
window.IntersectionObserver = undefined;
368371
defaultFallbackInView(true);
@@ -383,3 +386,12 @@ test("should handle defaultFallbackInView if unsupported", () => {
383386
`[TypeError: IntersectionObserver is not a constructor]`,
384387
);
385388
});
389+
390+
test("should restore the browser IntersectingObserver", () => {
391+
expect(vi.isMockFunction(window.IntersectionObserver)).toBe(true);
392+
destroyIntersectionMocking();
393+
394+
// This should restore the original IntersectionObserver
395+
expect(window.IntersectionObserver).toBeDefined();
396+
expect(vi.isMockFunction(window.IntersectionObserver)).toBe(false);
397+
});

src/test-utils.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,32 @@ let isMocking = false;
1111

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

14+
// Store a reference to the original `IntersectionObserver` so we can restore it later.
15+
// This can be relevant if testing in a browser environment, where you actually have a native `IntersectionObserver`.
16+
const originalIntersectionObserver =
17+
typeof window !== "undefined" ? window.IntersectionObserver : undefined;
18+
1419
/*
1520
** If we are running in a valid testing environment, we can automate mocking the IntersectionObserver.
1621
*/
1722
if (
1823
typeof window !== "undefined" &&
1924
typeof beforeAll !== "undefined" &&
25+
typeof beforeEach !== "undefined" &&
2026
typeof afterEach !== "undefined"
2127
) {
22-
beforeAll(() => {
23-
// Use the exposed mock function. Currently, only supports Jest (`jest.fn`) and Vitest with globals (`vi.fn`).
28+
const initMocking = () => {
29+
// Use the exposed mock function. Currently, it supports Jest (`jest.fn`) and Vitest with globals (`vi.fn`).
2430
// @ts-ignore
2531
if (typeof jest !== "undefined") setupIntersectionMocking(jest.fn);
2632
else if (typeof vi !== "undefined") {
2733
setupIntersectionMocking(vi.fn);
2834
}
29-
});
35+
};
3036

31-
afterEach(() => {
32-
resetIntersectionMocking();
33-
});
37+
beforeAll(initMocking);
38+
beforeEach(initMocking);
39+
afterEach(resetIntersectionMocking);
3440
}
3541

3642
function getActFn() {
@@ -76,6 +82,7 @@ afterEach(() => {
7682
* @param mockFn The mock function to use. Defaults to `vi.fn`.
7783
*/
7884
export function setupIntersectionMocking(mockFn: typeof vi.fn) {
85+
if (isMocking) return;
7986
window.IntersectionObserver = mockFn((cb, options = {}) => {
8087
const item = {
8188
callback: cb,
@@ -122,6 +129,17 @@ export function resetIntersectionMocking() {
122129
observers.clear();
123130
}
124131

132+
/**
133+
* Destroy the IntersectionObserver mock function, and restore the original browser implementation of `IntersectionObserver`.
134+
* You can use this to opt of mocking in a specific test.
135+
**/
136+
export function destroyIntersectionMocking() {
137+
resetIntersectionMocking();
138+
// @ts-ignore
139+
window.IntersectionObserver = originalIntersectionObserver;
140+
isMocking = false;
141+
}
142+
125143
function triggerIntersection(
126144
elements: Element[],
127145
trigger: boolean | number,

0 commit comments

Comments
 (0)