The 36rem floor guards the stacked layout's width/height feedback loop; the wide side-panel card has no such loop, and on a short wide window the floor made the video taller than the card, cutting off YouTube's control bar. The floor now applies to the stacked layout only; e2e covers a 1400x380 window.
79 lines
3.4 KiB
TypeScript
79 lines
3.4 KiB
TypeScript
import { test, expect, type Page } from "@playwright/test";
|
||
import { openApp, openFirstVideo } from "./helpers";
|
||
|
||
// Locks the in-app player's open/close contract. The player is a portaled overlay whose
|
||
// inconsistent mounting caused a regression — exactly the kind of thing the refactor's
|
||
// overlay-root convention must not break. We assert the modal shell, not YouTube playback.
|
||
test.describe("player modal", () => {
|
||
test.beforeEach(async ({ page }) => {
|
||
// Hermetic: don't actually load the YouTube iframe/player.
|
||
await page.route(/youtube\.com/, (route) => route.abort());
|
||
});
|
||
|
||
test("opens from a card and closes via the close button", async ({ page }) => {
|
||
await openApp(page);
|
||
await openFirstVideo(page);
|
||
|
||
await page.getByTestId("player-close").click();
|
||
await expect(page.getByTestId("player-modal")).toHaveCount(0);
|
||
});
|
||
|
||
test("closes on Escape", async ({ page }) => {
|
||
await openApp(page);
|
||
await openFirstVideo(page);
|
||
|
||
await page.keyboard.press("Escape");
|
||
await expect(page.getByTestId("player-modal")).toHaveCount(0);
|
||
});
|
||
});
|
||
|
||
// The video's size, in the windows the owner meets: layout bugs no DOM test can see. Each viewport
|
||
// is a real one — 1920×911 is a 1080p screen at 100% (the side-panel layout), 1097×520 the same
|
||
// screen at 175% or a small laptop at 100% (the stacked layout, on a short window), 1400×380 a wide
|
||
// window made short by docked devtools (the side-panel layout, whose card does not scroll).
|
||
test.describe("player modal sizing", () => {
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.route(/youtube\.com/, (route) => route.abort());
|
||
});
|
||
|
||
const box = async (page: Page, selector: string) =>
|
||
(await page.locator(selector).first().boundingBox())!;
|
||
|
||
for (const [w, h] of [
|
||
[1920, 911],
|
||
[1097, 520],
|
||
[1400, 380],
|
||
] as const) {
|
||
test(`the video keeps a real size at ${w}×${h}, and maximised fills the window`, async ({
|
||
page,
|
||
}) => {
|
||
await page.setViewportSize({ width: w, height: h });
|
||
await openApp(page);
|
||
await openFirstVideo(page);
|
||
|
||
// A short window narrows the card so the chrome under the video fits — but the chrome grows
|
||
// as the card narrows (its text wraps), and that loop once ran the card down to 2 px.
|
||
const stage = await box(page, ".player-stage");
|
||
expect(stage.width).toBeGreaterThanOrEqual(Math.min(w * 0.5, 560));
|
||
expect(stage.height).toBeGreaterThan(stage.width * 0.5);
|
||
// …and all of it is in sight: its bottom edge is the YouTube control bar, and the side-panel
|
||
// card clips what overflows it (it does not scroll), the stacked card scrolls to it.
|
||
const overflow = await page
|
||
.locator(".player-stage")
|
||
.first()
|
||
.evaluate((el) => {
|
||
const card = el.parentElement!;
|
||
return el.getBoundingClientRect().bottom - card.getBoundingClientRect().bottom;
|
||
});
|
||
expect(overflow).toBeLessThanOrEqual(0.5);
|
||
|
||
// Maximise (F): the stage is fixed to the viewport. In the side-panel layout its `self-start`
|
||
// once aligned the fixed box too, and it shrank to its content's height — 0 px, a thin line.
|
||
await page.getByTestId("player-modal").press("f");
|
||
await expect(page.locator(".player-stage--max")).toHaveCount(1);
|
||
const max = await box(page, ".player-stage--max");
|
||
expect([Math.round(max.width), Math.round(max.height)]).toEqual([w, h]);
|
||
});
|
||
}
|
||
});
|