Skip to content

Commit

Permalink
hooks: More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tbantle22 committed Jan 18, 2024
1 parent a162314 commit 954fcf3
Show file tree
Hide file tree
Showing 9 changed files with 514 additions and 2 deletions.
77 changes: 77 additions & 0 deletions packages/hooks/src/__tests__/useElementIsVisible.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { act, renderHook } from "@testing-library/react-hooks";
import useElementIsVisible from "../useElementIsVisible";

describe("useElementIsVisible", () => {
let mockObserve: jest.Mock;
let mockDisconnect: jest.Mock;
let mockTriggerIntersect: (entries: IntersectionObserverEntry[]) => void;

// Mock IntersectionObserver
beforeAll(() => {
mockObserve = jest.fn();
mockDisconnect = jest.fn();

global.IntersectionObserver = jest.fn((callback, opts = {}) => {
mockTriggerIntersect = (entries: IntersectionObserverEntry[]) =>
callback(entries, new IntersectionObserver(() => null));

return {
root: opts.root ?? null,
rootMargin: opts.rootMargin ?? "",
observe: mockObserve,
disconnect: mockDisconnect,
unobserve: jest.fn(),
thresholds: [],
takeRecords: jest.fn(),
};
});

jest
.spyOn(document, "querySelector")
.mockImplementation((sel: string) =>
sel === "#notFound" ? null : document.createElement("div"),
);
});

afterEach(() => {
jest.clearAllMocks();
});

it("initializes with isIntersecting as false", () => {
const { result } = renderHook(() => useElementIsVisible("#testElement"));
expect(result.current).toBe(false);
});

it("does nothing if ID not found", () => {
const { result, unmount } = renderHook(() =>
useElementIsVisible("#notFound"),
);
expect(result.current).toBe(false);
expect(mockObserve).not.toHaveBeenCalled();

unmount();

expect(mockDisconnect).not.toHaveBeenCalled();
});

it("updates isIntersecting when element becomes visible", () => {
const { result } = renderHook(() => useElementIsVisible("#testElement"));

act(() => {
mockTriggerIntersect([
{ isIntersecting: true } as IntersectionObserverEntry,
]);
});

expect(result.current).toBe(true);
expect(mockObserve).toHaveBeenCalled();
});

it("disconnects IntersectionObserver on unmount", () => {
const { unmount } = renderHook(() => useElementIsVisible("#testElement"));

unmount();

expect(mockDisconnect).toHaveBeenCalled();
});
});
52 changes: 52 additions & 0 deletions packages/hooks/src/__tests__/useFocus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { act, renderHook } from "@testing-library/react-hooks";
import useFocus from "../useFocus";

describe("useFocus", () => {
// Mocking DOM methods
const mockScrollIntoView = jest.fn();
const mockScrollTo = jest.fn();
const mockFocus = jest.fn();

beforeAll(() => {
jest.spyOn(document, "getElementById").mockImplementation(
(id: string) =>
({
scrollIntoView: mockScrollIntoView,
scrollTo: mockScrollTo,
focus: mockFocus,
id,
}) as any, // Type casting to any to simplify mock structure

Check warning on line 18 in packages/hooks/src/__tests__/useFocus.test.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
);
});

afterEach(() => {
jest.clearAllMocks();
});

it("initializes with default states", () => {
const { result } = renderHook(() => useFocus());
expect(result.current.setScrollToTop).toBeDefined();
expect(result.current.setRefocus).toBeDefined();
});

it("handles scrollToTop", () => {
const { result } = renderHook(() => useFocus());

act(() => {
result.current.setScrollToTop(true);
});

expect(mockScrollIntoView).toHaveBeenCalled();
expect(mockScrollTo).toHaveBeenCalledWith({ top: 0, behavior: "smooth" });
});

it("handles refocus", () => {
const { result } = renderHook(() => useFocus());

act(() => {
result.current.setRefocus(true);
});

expect(mockFocus).toHaveBeenCalled();
});
});
27 changes: 27 additions & 0 deletions packages/hooks/src/__tests__/useHotKeys.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { act, renderHook } from "@testing-library/react-hooks";
import { useHotKeysForToggle } from "../useHotKeys";

describe("useHotKeysForToggle", () => {
it("initializes with default keyMap and handlers", () => {
const { result } = renderHook(() => useHotKeysForToggle(jest.fn()));

expect(result.current.keyMap).toEqual({
TOGGLE_EDITOR: ["meta+shift+enter", "ctrl+shift+enter"],
});
expect(result.current.handlers).toBeDefined();
expect(result.current.handlers.TOGGLE_EDITOR).toBeDefined();
});

it("toggles when handler is invoked", () => {
const mockToggle = jest.fn();
const { result } = renderHook(() => useHotKeysForToggle(mockToggle));

// Simulate the handler being called
act(() => {
result.current.handlers.TOGGLE_EDITOR();
});

// The toggle function should be called
expect(mockToggle).toHaveBeenCalled();
});
});
30 changes: 30 additions & 0 deletions packages/hooks/src/__tests__/useIsSignedIn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { renderHook } from "@testing-library/react-hooks";
import cookie from "js-cookie";
import useIsSignedIn from "../useIsSignedIn";

jest.mock("js-cookie");

describe("useIsSignedIn", () => {
const tokenKey = "authToken";

it("initializes as not signed in", () => {
(cookie.get as jest.Mock).mockReturnValue(null);
const { result } = renderHook(() => useIsSignedIn(tokenKey));

expect(result.current).toBe(false);
});

it("returns true when token is present", () => {
(cookie.get as jest.Mock).mockReturnValue("tokenValue");
const { result } = renderHook(() => useIsSignedIn(tokenKey));

expect(result.current).toBe(true);
});

it("returns false when token is not present", () => {
(cookie.get as jest.Mock).mockReturnValue(null);
const { result } = renderHook(() => useIsSignedIn(tokenKey));

expect(result.current).toBe(false);
});
});
28 changes: 28 additions & 0 deletions packages/hooks/src/__tests__/useOnClickOutside.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { renderHook } from "@testing-library/react-hooks";
import useOnClickOutside from "../useOnClickOutside";

describe("useOnClickOutside", () => {
it("calls action on outside click", () => {
const mockAction = jest.fn();
const excludingRef = { current: document.createElement("div") };

renderHook(() => useOnClickOutside(excludingRef, mockAction));

const event = new MouseEvent("mousedown", { bubbles: true });
document.dispatchEvent(event);

expect(mockAction).toHaveBeenCalled();
});

it("does not call action on inside click", () => {
const mockAction = jest.fn();
const excludingRef = { current: document.createElement("div") };

renderHook(() => useOnClickOutside(excludingRef, mockAction));

const event = new MouseEvent("mousedown", { bubbles: true });
excludingRef.current.dispatchEvent(event);

expect(mockAction).not.toHaveBeenCalled();
});
});
115 changes: 114 additions & 1 deletion packages/hooks/src/__tests__/useReactiveSize.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { act, renderHook } from "@testing-library/react-hooks";
import { replaceRaf } from "raf-stub";
import { useReactiveHeight, useReactiveWidth } from "..";
import {
useReactiveElementHeight,
useReactiveHeight,
useReactiveScrollWidth,
useReactiveWidth,
} from "..";

declare let requestAnimationFrame: {
reset: () => void;
Expand All @@ -17,6 +22,19 @@ function triggerResize(dimension: "width" | "height", value: number) {
window.dispatchEvent(new Event("resize"));
}

function createElement(): HTMLElement {
const element = document.createElement("div");
Object.defineProperty(element, "clientHeight", {
configurable: true,
value: 500,
});
Object.defineProperty(element, "scrollWidth", {
configurable: true,
value: 300,
});
return element;
}

describe("useReactiveWidth", () => {
beforeAll(() => {
replaceRaf();
Expand Down Expand Up @@ -106,3 +124,98 @@ describe("useReactiveHeight", () => {
expect(result.current).toBe(2048);
});
});

describe("useReactiveElementHeight", () => {
beforeAll(() => {
replaceRaf();
});

beforeEach(() => {
Object.defineProperty(window, "innerHeight", {
configurable: true,
value: 800,
});
});

afterEach(() => {
requestAnimationFrame.reset();
});

it("initializes with element clientHeight", () => {
const element = createElement();
const { result } = renderHook(() => useReactiveElementHeight(element));

expect(result.current).toBe(500);
});

it("initializes with window.innerHeight when element is undefined", () => {
const { result } = renderHook(() => useReactiveElementHeight());

expect(result.current).toBe(800);
});

it("updates on window resize for no element", () => {
const { result } = renderHook(() => useReactiveElementHeight());

act(() => {
triggerResize("height", 360);
requestAnimationFrame.step();
});

expect(result.current).toBe(360);
});
});

describe("useReactiveScrollWidth", () => {
beforeEach(() => {
Object.defineProperty(window, "innerWidth", {
configurable: true,
value: 1024,
});
});

it("initializes with element scrollWidth and window innerWidth", () => {
const element = createElement();
const { result } = renderHook(() => useReactiveScrollWidth(element));

expect(result.current).toEqual({
scrollWidth: 300,
windowInnerWidth: 1024,
});
});

it("updates element and window on window resize", () => {
const element = createElement();
const { result, rerender } = renderHook(() =>
useReactiveScrollWidth(element),
);

act(() => {
Object.defineProperty(element, "scrollWidth", {
configurable: true,
value: 500,
});
triggerResize("width", 1000);
requestAnimationFrame.step();
});

rerender();

expect(result.current.windowInnerWidth).toBe(1000);
expect(result.current.scrollWidth).toBe(500);
});

it("updates window on window resize with no element", () => {
const { result, rerender } = renderHook(() => useReactiveScrollWidth());

act(() => {
triggerResize("width", 2000);
requestAnimationFrame.step();
});

rerender();

expect(result.current.windowInnerWidth).toBe(2000);
expect(result.current.scrollWidth).toBe(0);
});
});
Loading

0 comments on commit 954fcf3

Please sign in to comment.