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(() => { const m = new Map(); document.querySelectorAll(".measure .card[data-key]").forEach((e) => m.set(e.dataset.key!, e.offsetHeight)); return [...document.querySelectorAll(".board .card[data-key]")].map((e) => ({ key: e.dataset.key!, h: parseFloat(e.style.height), measured: m.get(e.dataset.key!) ?? -1, measureStyleHeight: getComputedStyle(document.querySelector(`.measure .card[data-key="${e.dataset.key}"]`)!).height, })); }); expect(rows.length).toBeGreaterThan(0); for (const r of rows) { expect(r.measured, r.key).toBeGreaterThan(0); expect(r.h, r.key).toBe(Math.max(80, Math.ceil(r.measured / 8) * 8)); // measuredHeight: сетка 8, не меньше MINH } // слой измерения не зажат в 120px: хотя бы одна карточка разной высоты с другой или не равна 120 expect(rows.some((r) => r.measured !== 120) || new Set(rows.map((r) => r.measured)).size > 1).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(".bar .dd-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(".top")).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(".top")).toBeVisible(); expect(errs).toEqual([]); }); test("h1 на страницах доски, задач и карточки", 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(); await expect(page.getByRole("heading", { level: 1, name: /AURORA-9002/ })).toBeVisible(); await expect(page.locator("h1")).toHaveCount(1); });