// @vitest-environment jsdom // // The owner's pick: the channel page's chrome is fixed, and its banner + identity block kept ~40% of a // 133% screen before the first video for as long as the page was open. Once the grid scrolls they fold // away and the name rides in the tab row; back at the top they return. The Back button is one element in // both states, so a focused Back keeps its focus through the fold. import type { ReactNode } from "react"; import { describe, it, expect, vi, beforeAll, afterEach } from "vitest"; import { render, cleanup, screen, act, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; vi.mock("react-i18next", async (importOriginal) => ({ ...(await importOriginal()), useTranslation: () => ({ t: (key: string) => key, i18n: { language: "en" } }), })); vi.mock("./navigationContext", () => ({ useNavigationActions: () => ({ closeChannel: vi.fn(), openChannel: vi.fn() }), })); vi.mock("./confirmContext", () => ({ useConfirm: () => async () => true })); vi.mock("./feedFiltersContext", () => ({ useFeedFilters: () => ({ includeNormal: true, includeShorts: false, includeLive: false }), })); vi.mock("./Feed", () => ({ default: () => null })); vi.mock("./ChannelAbout", () => ({ default: () => null })); vi.mock("./ui/PageShell", async (importOriginal) => ({ ...(await importOriginal()), PageToolbar: ({ children }: { children?: ReactNode }) => <>{children}, })); import ChannelPage from "./ChannelPage"; import { CONTENT_ID } from "./ui/landmarks"; import { api, type ChannelDetail, type Me } from "../lib/api"; beforeAll(() => { class Stub { observe() {} unobserve() {} disconnect() {} takeRecords() { return []; } } globalThis.ResizeObserver ??= Stub as unknown as typeof ResizeObserver; globalThis.IntersectionObserver ??= Stub as unknown as typeof IntersectionObserver; }); afterEach(() => { cleanup(); document.getElementById(CONTENT_ID)?.remove(); vi.restoreAllMocks(); }); const CH = "UCqoAEDirJPjEUFcF2FklnBA"; const BANNER = 'img[src^="https://yt3."]'; // The page's one scroller (PageScroller's
), with a scroll position jsdom does not lay out. function scroller() { const main = document.createElement("main"); main.id = CONTENT_ID; let top = 0; Object.defineProperty(main, "scrollTop", { get: () => top, configurable: true }); Object.defineProperty(main, "scrollHeight", { value: 6000, configurable: true }); Object.defineProperty(main, "clientHeight", { value: 600, configurable: true }); document.body.appendChild(main); return (to: number) => act(() => { top = to; main.dispatchEvent(new Event("scroll")); }); } // Renders the page and waits for the detail to land (the banner shows). async function renderPage() { vi.spyOn(api, "channelDetail").mockResolvedValue({ id: CH, title: "StarTalk", banner_url: "https://yt3.googleusercontent.com/banner", subscribed: true, blocked: false, hidden: false, deep_requested: false, backfill_done: true, stored_videos: 10, tag_ids: [], } as unknown as ChannelDetail); vi.spyOn(api, "tags").mockResolvedValue([] as never); const view = render( {}} onShowInFeed={() => {}} /> , ); await waitFor(() => expect(view.container.querySelector(BANNER)).not.toBeNull()); return view; } describe("ChannelPage — the header folds while the grid scrolls", () => { it("drops the banner and puts the name in the tab row once scrolled, and brings them back at the top", async () => { const scrollTo = scroller(); const { container } = await renderPage(); const banner = () => container.querySelector(BANNER); expect(banner()).not.toBeNull(); expect(screen.queryByTestId("channel-folded")).toBeNull(); scrollTo(400); expect(banner()).toBeNull(); expect(screen.getByTestId("channel-folded").textContent).toContain("StarTalk"); scrollTo(10); expect(screen.queryByTestId("channel-folded")).not.toBeNull(); scrollTo(0); expect(banner()).not.toBeNull(); expect(screen.queryByTestId("channel-folded")).toBeNull(); }); it("keeps a focused Back focused through the fold", async () => { const scrollTo = scroller(); await renderPage(); const back = screen.getByTestId("channel-back"); back.focus(); scrollTo(400); expect(screen.getByTestId("channel-folded")).toBeTruthy(); expect(document.activeElement).toBe(back); }); });