import { test, expect } from "@playwright/test"; import { clickVisibleChannelLink, expectContentType, openApp, toggleContentType } from "./helpers"; // Locks channel-view entry + exit: opening a channel from a feed card replaces the view, and // both the in-page Back and the browser Back return to the feed. (This orthogonal channel branch // is exactly what the Platform refactor folds into the PageShell contract.) test.describe("channel view", () => { test("open from a card, then in-page Back returns to the feed", async ({ page }) => { await openApp(page); await expect(page.getByTestId("video-card").first()).toBeVisible(); await page.getByTestId("video-card-channel").first().click(); await expect(page.getByTestId("channel-title")).toBeVisible(); await expect(page.getByTestId("channel-back")).toBeVisible(); await page.getByTestId("channel-back").click(); await expect(page.getByTestId("channel-title")).toHaveCount(0); await expect(page.getByTestId("video-card").first()).toBeVisible(); }); test("browser Back also closes the channel page", async ({ page }) => { await openApp(page); await expect(page.getByTestId("video-card").first()).toBeVisible(); await page.getByTestId("video-card-channel").first().click(); await expect(page.getByTestId("channel-title")).toBeVisible(); await page.goBack(); await expect(page.getByTestId("channel-title")).toHaveCount(0); await expect(page.getByTestId("video-card").first()).toBeVisible(); }); // Opening a channel and coming Back restores the feed's scroll position // rather than dumping you at the top. The shell owns the scroller and remembers each view's offset. test("Back restores the feed scroll position", async ({ page }) => { await openApp(page); await expect(page.getByTestId("video-card").first()).toBeVisible(); const main = page.getByRole("main"); await main.evaluate((el) => el.scrollBy(0, 3000)); // Let any page-fetch triggered by the scroll settle, so the same content is cached for the // return trip (a fetch still in flight at navigation would leave a shorter list to restore into). await page.waitForTimeout(600); const saved = await main.evaluate((el) => el.scrollTop); expect(saved).toBeGreaterThan(1500); // Click a channel card that is actually ON SCREEN — a real user clicks what they see. Clicking // `.first()` here would grab an overscan card ABOVE the viewport, and Playwright's scroll-into- // view would move the feed before navigating, so Back would restore that moved position, not // the one under test. (This is what made the test fail while the app was in fact correct.) expect(await clickVisibleChannelLink(page)).toBe(true); await expect(page.getByTestId("channel-title")).toBeVisible(); await page.getByTestId("channel-back").click(); await expect(page.getByTestId("video-card").first()).toBeVisible(); // Restored to roughly where we were, not reset to the top. Tolerance absorbs the virtualizer's // estimate-vs-measured row heights differing slightly between the two mounts. await expect .poll(() => page.getByRole("main").evaluate((el) => el.scrollTop), { timeout: 5000 }) .toBeGreaterThan(saved - 500); }); // Opening a channel no longer FORCES Live/Upcoming on — the channel view // seeds its content-type prefs (normal / shorts / live) from the global feed. Locked both ways so // a future refactor can't silently re-hardcode it. test("the channel view inherits the feed's content filters (no forced Live/Upcoming)", async ({ page, }) => { await openApp(page); await expect(page.getByTestId("video-card").first()).toBeVisible(); await expectContentType(page, "includeLive", false); // Inherit OFF: the channel toolbar's Live/Upcoming stays off, not forced on. await page.getByTestId("video-card-channel").first().click(); await expect(page.getByTestId("channel-title")).toBeVisible(); await expectContentType(page, "includeLive", false); await page.getByTestId("channel-back").click(); await expect(page.getByTestId("channel-title")).toHaveCount(0); // Inherit ON: flip the feed's Live/Upcoming on, reopen the channel → it inherits the on state. await toggleContentType(page, "includeLive"); await expectContentType(page, "includeLive", true); await expect(page.getByTestId("video-card").first()).toBeVisible(); await page.getByTestId("video-card-channel").first().click(); await expect(page.getByTestId("channel-title")).toBeVisible(); await expectContentType(page, "includeLive", true); }); // The channel view is its OWN filter scope — a content toggle made there // must NOT leak back into the global feed. (FeedFiltersProvider owns the feed's filters; the // channel page keeps a separate local cell.) test("a channel's content toggle does not leak into the feed", async ({ page }) => { await openApp(page); await expect(page.getByTestId("video-card").first()).toBeVisible(); await expectContentType(page, "includeLive", false); // Turn Live/Upcoming ON inside the channel view. await page.getByTestId("video-card-channel").first().click(); await expect(page.getByTestId("channel-title")).toBeVisible(); await toggleContentType(page, "includeLive"); await expectContentType(page, "includeLive", true); // Back on the feed, its Live/Upcoming is still off — the channel's change stayed local. await page.getByTestId("channel-back").click(); await expect(page.getByTestId("channel-title")).toHaveCount(0); await expectContentType(page, "includeLive", false); }); // The channel chrome sits outside the scroller. Scrolling into the videos // folds the banner and identity once (the name moves into the tab row); from then on the folded // chrome stays put while only the video grid scrolls. test("the channel chrome folds once, then stays fixed while the grid scrolls", async ({ page, }) => { await openApp(page); await page.getByTestId("video-card-channel").first().click(); const count = page.getByTestId("item-count"); await expect(count).toBeVisible(); await expect(page.getByTestId("video-card").first()).toBeVisible(); await expect(page.getByTestId("channel-folded")).toHaveCount(0); // Keep nudging until the grid is tall enough to actually scroll (it may still be filling in). await expect .poll(async () => { await page.getByRole("main").evaluate((el) => el.scrollBy(0, 600)); return page.getByRole("main").evaluate((el) => el.scrollTop); }) .toBeGreaterThan(400); const folded = page.getByTestId("channel-folded"); await expect(folded).toBeVisible(); await expect(folded.getByTestId("channel-title")).toBeVisible(); const titleTop = await folded.evaluate((el) => el.getBoundingClientRect().top); const countTop = await count.evaluate((el) => el.getBoundingClientRect().top); await page.getByRole("main").evaluate((el) => el.scrollBy(0, 400)); expect(await folded.evaluate((el) => el.getBoundingClientRect().top)).toBe(titleTop); expect(await count.evaluate((el) => el.getBoundingClientRect().top)).toBe(countTop); }); });