Skip to content

Commit

Permalink
Merge pull request #715 from thebuilder/feat/restore-beforeach
Browse files Browse the repository at this point in the history
  • Loading branch information
thebuilder authored Jan 14, 2025
2 parents f3213dd + 18de17e commit f098c1e
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 8 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,13 @@ will emulate the real IntersectionObserver, allowing you to validate that your
components are behaving as expected.
| Method | Description |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `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. |
| `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. |
| `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. |
| `setupIntersectionMocking(mockFn)` | Mock the `IntersectionObserver`, so we can interact with them in tests - Should be called in `beforeEach`. (**Done automatically in Jest environment**) |
| `resetIntersectionMocking()` | Reset the mocks on `IntersectionObserver` - Should be called in `afterEach`. (**Done automatically in Jest environment**) |
| `resetIntersectionMocking()` | Reset the mocks on `IntersectionObserver` - Should be called in `afterEach`. (**Done automatically in Jest/Vitest environment**) |
| `destroyIntersectionMocking()` | Destroy the mocked `IntersectionObserver` function, and return `window.IntersectionObserver` to the original browser implementation |
### Testing Libraries
Expand Down
12 changes: 12 additions & 0 deletions src/__tests__/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { render, screen } from "@testing-library/react";
import React, { useCallback } from "react";
import { type IntersectionOptions, defaultFallbackInView } from "../index";
import {
destroyIntersectionMocking,
intersectionMockInstance,
mockAllIsIntersecting,
mockIsIntersecting,
Expand Down Expand Up @@ -342,6 +343,7 @@ test("should set intersection ratio as the largest threshold smaller than trigge
});

test("should handle fallback if unsupported", () => {
destroyIntersectionMocking();
// @ts-ignore
window.IntersectionObserver = undefined;
const { rerender } = render(
Expand All @@ -363,6 +365,7 @@ test("should handle fallback if unsupported", () => {
});

test("should handle defaultFallbackInView if unsupported", () => {
destroyIntersectionMocking();
// @ts-ignore
window.IntersectionObserver = undefined;
defaultFallbackInView(true);
Expand All @@ -383,3 +386,12 @@ test("should handle defaultFallbackInView if unsupported", () => {
`[TypeError: IntersectionObserver is not a constructor]`,
);
});

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

// This should restore the original IntersectionObserver
expect(window.IntersectionObserver).toBeDefined();
expect(vi.isMockFunction(window.IntersectionObserver)).toBe(false);
});
30 changes: 24 additions & 6 deletions src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,32 @@ 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;

/*
** 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"
) {
beforeAll(() => {
// Use the exposed mock function. Currently, only supports Jest (`jest.fn`) and Vitest with globals (`vi.fn`).
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);
}
});
};

afterEach(() => {
resetIntersectionMocking();
});
beforeAll(initMocking);
beforeEach(initMocking);
afterEach(resetIntersectionMocking);
}

function getActFn() {
Expand Down Expand Up @@ -76,6 +82,7 @@ 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 @@ -122,6 +129,17 @@ export function resetIntersectionMocking() {
observers.clear();
}

/**
* Destroy the IntersectionObserver mock function, and restore the original browser implementation of `IntersectionObserver`.
* You can use this to opt of mocking in a specific test.
**/
export function destroyIntersectionMocking() {
resetIntersectionMocking();
// @ts-ignore
window.IntersectionObserver = originalIntersectionObserver;
isMocking = false;
}

function triggerIntersection(
elements: Element[],
trigger: boolean | number,
Expand Down

0 comments on commit f098c1e

Please sign in to comment.