// @vitest-environment jsdom // // The floating header knows no page: a page fills it through PageHeader (a portal into the header's // root box), and the shell's PageHeaderDefault shows only while no page does. These pin the slot's // three promises: the page's pills land IN the header, the default steps aside for them and comes // back when the next page has none, and a shell without a header (the channel page) renders the // page's pills nowhere. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { render, cleanup, screen } from "@testing-library/react"; import PageShell, { PageHeader, PageHeaderDefault } from "./PageShell"; vi.mock("./PageScroller", () => ({ default: ({ children }: { children: React.ReactNode }) =>
{children}
, })); beforeEach(() => { vi.stubGlobal( "ResizeObserver", class { observe() {} disconnect() {} }, ); }); afterEach(() => { cleanup(); vi.unstubAllGlobals(); }); const hdr =
; const fallback = ( default ); describe("the header slot", () => { it("puts a page's pills inside the header box", () => { render( search , ); expect(screen.getByTestId("hdr").textContent).toBe("search"); }); it("shows the default on a page with no header of its own", () => { render({fallback}); expect(screen.getByTestId("hdr").textContent).toBe("default"); }); it("hides the default while a page fills the header, and brings it back on the next page", () => { const page = (own: boolean) => ( {own && ( search )} {fallback} ); const { rerender } = render(page(true)); expect(screen.getByTestId("hdr").textContent).toBe("search"); rerender(page(false)); expect(screen.getByTestId("hdr").textContent).toBe("default"); rerender(page(true)); expect(screen.getByTestId("hdr").textContent).toBe("search"); }); it("keeps the default hidden while one of two PageHeaders on a page remains", () => { const page = (both: boolean) => ( search {both && ( toggle )} {fallback} ); const { rerender } = render(page(true)); rerender(page(false)); expect(screen.getByTestId("hdr").textContent).toBe("search"); }); it("pads the content by the filled header before paint, not when the observer catches up", () => { // jsdom does no layout: the header's height here is one 40px row per pill in it. The observer // never fires in this test, so only a measurement made as the pills arrive can see them. const height = (el: HTMLElement) => 40 * el.childElementCount; const page = (own: boolean) => ( {own && ( search scope )} {fallback} ); const { rerender, container } = render({null}); Object.defineProperty(screen.getByTestId("hdr"), "offsetHeight", { get() { return height(this as HTMLElement); }, configurable: true, }); const pad = () => (container.querySelector('[class*="pt-(--hdr-h)"]') as HTMLElement).style.getPropertyValue( "--hdr-h", ); rerender(page(false)); expect(pad()).toBe("44px"); // the default alone: one row rerender(page(true)); expect(pad()).toBe("84px"); // the page's two pills, the default gone }); it("re-measures when one page's pills replace another's in the same commit", () => { // Feed → Playlists: the leaving page's claim and the arriving page's land in one commit, so the // number of claims never changes — the pad must follow the new pills all the same. const height = (el: HTMLElement) => 40 * el.childElementCount; const Feed = () => ( search scope ); const Playlists = () => ( search ); const page = (which: "feed" | "playlists") => ( {which === "feed" ? : } {fallback} ); const { rerender, container } = render({null}); Object.defineProperty(screen.getByTestId("hdr"), "offsetHeight", { get() { return height(this as HTMLElement); }, configurable: true, }); const pad = () => (container.querySelector('[class*="pt-(--hdr-h)"]') as HTMLElement).style.getPropertyValue( "--hdr-h", ); rerender(page("feed")); expect(pad()).toBe("84px"); rerender(page("playlists")); expect(pad()).toBe("44px"); rerender(page("feed")); expect(pad()).toBe("84px"); }); it("renders a page's pills nowhere in a shell without a header", () => { render( search {fallback} , ); expect(screen.queryByText("search")).toBeNull(); expect(screen.queryByText("default")).toBeNull(); }); });