import { expect, test, type Page } from "@playwright/test"; import { login } from "./hub.ts"; // то, что в jsdom не проверить: реальная геометрия, ширины и поведение при смене фильтров function watchErrors(page: Page): string[] { const errs: string[] = []; page.on("pageerror", (e) => errs.push("pageerror: " + e.message)); page.on("console", (m) => { if (m.type() === "error") errs.push("console: " + m.text()); }); return errs; } test("высота карточек берётся по содержимому из слоя измерения, а не 120px", async ({ page }) => { await login(page); await expect(page.locator(".board .card[data-key]").first()).toBeVisible(); const rows = await page.evaluate(() => [...document.querySelectorAll(".board .card[data-key]")].map((e) => ({ key: e.dataset.key!, h: e.offsetHeight, fits: e.scrollHeight <= e.clientHeight + 1, }))); expect(rows.length).toBeGreaterThan(0); for (const r of rows) { expect(r.fits, `${r.key}: содержимое не помещается`).toBe(true); expect(r.h % 8, `${r.key}: высота не на сетке 8`).toBe(0); } // на фикстуре hub-basic содержимое пяти карточек одной высоты, поэтому «разные высоты» не проверяем (отклонение от плана): // высота не равна прежней фиксированной 120px expect(rows.some((r) => r.h !== 120), "все карточки по 120px — высота не по содержимому").toBe(true); }); test("при открытой панели «Проблемы» полоса дат по ширине равна обёртке холста", async ({ page }) => { await login(page); await page.getByRole("button", { name: "По датам" }).click(); await expect(page.locator(".dates-in")).toBeVisible(); for (const open of [false, true]) { if (open) { await page.getByRole("button", { name: /^Проблемы/ }).click(); await expect(page.locator(".drawer")).toBeVisible(); } await expect.poll(async () => { const a = (await page.locator(".dates-in").boundingBox())!.width, b = (await page.locator(".stage .wrap").boundingBox())!.width; return Math.abs(a - b); }, `панель ${open ? "открыта" : "закрыта"}`).toBeLessThanOrEqual(1); } }); test("смена спринта и исполнителя, убирающая карточки, не даёт ошибок и белого экрана", async ({ page }) => { const errs = watchErrors(page); await login(page); await expect(page.locator(".board .card[data-key]").first()).toBeVisible(); const total = await page.locator(".board .card[data-key]").count(); const dd = (i: number) => page.locator(".board-controls .multi-btn").nth(i); // 0 — спринты, 1 — исполнители for (const [i, name] of [[0, "Спринт 3"], [1, "Сидорова"]] as const) { await dd(i).click(); await page.getByRole("button", { name: "Снять выбор" }).click(); await page.locator(".dd-item", { hasText: name }).first().locator("input").check(); await page.keyboard.press("Escape"); await expect(page.locator("h1")).toHaveText("Доска связей"); await expect(page.locator(".sticky .ui-page-header")).toBeVisible(); await expect(page.locator(".board")).toBeVisible(); await expect.poll(() => page.locator(".board .card[data-key]:not(.dim)").count(), `фильтр «${name}»`).toBeLessThan(total); await dd(i).click(); await page.getByRole("button", { name: "Снять выбор" }).click(); await page.keyboard.press("Escape"); } await page.getByRole("button", { name: "По датам" }).click(); await expect(page.locator(".sticky .ui-page-header")).toBeVisible(); expect(errs).toEqual([]); }); test("h1 на страницах доски и задач, h2 в окне карточки", async ({ page }) => { await login(page); await expect(page.locator("h1")).toHaveText("Доска связей"); await page.getByRole("link", { name: "Задачи" }).click(); await expect(page.getByRole("heading", { level: 1, name: "Задачи" })).toBeVisible(); await page.getByRole("link", { name: "AURORA-9002" }).click(); // карточка — окно поверх списка: её заголовок h2 (тема задачи), h1 остаётся у страницы под окном await expect(page.getByRole("dialog").getByRole("heading", { level: 2, name: "API свода" })).toBeVisible(); await expect(page.locator("h1")).toHaveCount(1); });