feat: initial commit — storefront, convex backend, and shared packages
Completes the first milestone of The Pet Loft ecommerce platform: - apps/storefront: full customer-facing Next.js app with HeroUI (cart, checkout, orders, wishlist, product detail, shop, search, auth) - convex/: serverless backend with schema, queries, mutations, actions, HTTP routes, Stripe/Shippo integrations, and co-located tests - packages/types, packages/utils, packages/convex: shared workspace packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
145
apps/storefront/src/lib/search/useClickOutside.test.tsx
Normal file
145
apps/storefront/src/lib/search/useClickOutside.test.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import { useRef } from "react";
|
||||
import { useClickOutside } from "./useClickOutside";
|
||||
|
||||
function fireMouseDown(target: EventTarget) {
|
||||
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
||||
}
|
||||
|
||||
describe("useClickOutside", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("does not call handler when enabled is false", () => {
|
||||
const handler = vi.fn();
|
||||
const { result } = renderHook(() => {
|
||||
const ref = useRef<HTMLDivElement>(document.createElement("div"));
|
||||
useClickOutside([ref], handler, false);
|
||||
return ref;
|
||||
});
|
||||
|
||||
act(() => fireMouseDown(document.body));
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls handler when clicking outside all refs", () => {
|
||||
const handler = vi.fn();
|
||||
const el = document.createElement("div");
|
||||
document.body.appendChild(el);
|
||||
|
||||
renderHook(() => {
|
||||
const ref = useRef<HTMLDivElement>(el);
|
||||
useClickOutside([ref], handler, true);
|
||||
});
|
||||
|
||||
act(() => fireMouseDown(document.body));
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
document.body.removeChild(el);
|
||||
});
|
||||
|
||||
it("does not call handler when clicking inside the ref element", () => {
|
||||
const handler = vi.fn();
|
||||
const el = document.createElement("div");
|
||||
document.body.appendChild(el);
|
||||
|
||||
renderHook(() => {
|
||||
const ref = useRef<HTMLDivElement>(el);
|
||||
useClickOutside([ref], handler, true);
|
||||
});
|
||||
|
||||
act(() => fireMouseDown(el));
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
document.body.removeChild(el);
|
||||
});
|
||||
|
||||
it("does not call handler when clicking inside a child of the ref element", () => {
|
||||
const handler = vi.fn();
|
||||
const parent = document.createElement("div");
|
||||
const child = document.createElement("button");
|
||||
parent.appendChild(child);
|
||||
document.body.appendChild(parent);
|
||||
|
||||
renderHook(() => {
|
||||
const ref = useRef<HTMLDivElement>(parent);
|
||||
useClickOutside([ref], handler, true);
|
||||
});
|
||||
|
||||
act(() => fireMouseDown(child));
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
document.body.removeChild(parent);
|
||||
});
|
||||
|
||||
it("does not call handler when clicking inside any of multiple refs", () => {
|
||||
const handler = vi.fn();
|
||||
const el1 = document.createElement("div");
|
||||
const el2 = document.createElement("div");
|
||||
document.body.appendChild(el1);
|
||||
document.body.appendChild(el2);
|
||||
|
||||
renderHook(() => {
|
||||
const ref1 = useRef<HTMLDivElement>(el1);
|
||||
const ref2 = useRef<HTMLDivElement>(el2);
|
||||
useClickOutside([ref1, ref2], handler, true);
|
||||
});
|
||||
|
||||
act(() => fireMouseDown(el1));
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
|
||||
act(() => fireMouseDown(el2));
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
|
||||
document.body.removeChild(el1);
|
||||
document.body.removeChild(el2);
|
||||
});
|
||||
|
||||
it("stops calling handler after enabled becomes false", () => {
|
||||
const handler = vi.fn();
|
||||
const el = document.createElement("div");
|
||||
document.body.appendChild(el);
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ enabled }: { enabled: boolean }) => {
|
||||
const ref = useRef<HTMLDivElement>(el);
|
||||
useClickOutside([ref], handler, enabled);
|
||||
},
|
||||
{ initialProps: { enabled: true } },
|
||||
);
|
||||
|
||||
act(() => fireMouseDown(document.body));
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender({ enabled: false });
|
||||
|
||||
act(() => fireMouseDown(document.body));
|
||||
expect(handler).toHaveBeenCalledTimes(1); // no additional calls
|
||||
|
||||
document.body.removeChild(el);
|
||||
});
|
||||
|
||||
it("removes listener on unmount", () => {
|
||||
const handler = vi.fn();
|
||||
const el = document.createElement("div");
|
||||
document.body.appendChild(el);
|
||||
|
||||
const { unmount } = renderHook(() => {
|
||||
const ref = useRef<HTMLDivElement>(el);
|
||||
useClickOutside([ref], handler, true);
|
||||
});
|
||||
|
||||
unmount();
|
||||
|
||||
act(() => fireMouseDown(document.body));
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
|
||||
document.body.removeChild(el);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user