import { expect, test } from "@playwright/test"; test("player can open an active spot", async ({ page }) => { await page.goto("/"); await expect(page.getByRole("heading", { name: "Выбирай место, пока клюёт." })).toBeVisible(); const activeSpots = page.locator(".spot-card[data-testid]"); await expect(activeSpots).not.toHaveCount(0); const activeSpot = activeSpots.first(); await expect(activeSpot.locator(".source-chip")).not.toHaveCount(0); const level = await activeSpot.locator("[data-activity-level]").getAttribute("data-activity-level"); await expect(page.locator(".detail-score [data-activity-level]")).toHaveAttribute("data-activity-level", level ?? ""); await activeSpot.click(); await expect(page.getByRole("heading", { name: /^Точка / })).toBeVisible(); await expect(page.getByRole("heading", { name: "Последние уловы" })).toBeVisible(); await expect(page.locator(".catch-list .source-chip")).not.toHaveCount(0); await expect(page.locator("[data-activity-level]")).toHaveAttribute("data-activity-level", level ?? ""); }); test("submitted catch appears publicly only after moderation", async ({ page }) => { const player = `E2E Player ${Date.now()}`; const x = 1000 + Date.now() % 8000; const y = x + 1; const spotTestId = `spot-${x}-${y}`; await page.goto("/report"); await page.getByLabel("Координата X *").fill(String(x)); await page.getByLabel("Координата Y *").fill(String(y)); await page.getByLabel("Вес, граммы *").fill("6789"); await page.getByText("Дополнительные сведения").click(); await page.getByLabel("Ник игрока").fill(player); await page.getByLabel(/Я отправляю собственное наблюдение/).check(); await page.getByRole("button", { name: "Отправить на проверку" }).click(); await expect(page.getByText("Улов отправлен на модерацию. Спасибо!")).toBeVisible(); await page.goto("/"); await expect(page.getByTestId(spotTestId)).toHaveCount(0); await page.goto("/admin/moderation"); await page.getByLabel("Административный токен").fill("change-me-in-production"); await page.getByRole("button", { name: "Открыть очередь" }).click(); const report = page.locator(".moderation-card").filter({ hasText: player }); await expect(report).toBeVisible(); await report.getByRole("button", { name: "Одобрить" }).click(); await expect(report).toHaveCount(0); await page.goto("/"); await expect(page.getByTestId(spotTestId)).toBeVisible(); }); for (const viewport of [{ name: "desktop", width: 1280, height: 900 }, { name: "mobile", width: 390, height: 844 }]) { test(`home filters work on ${viewport.name}`, async ({ page }) => { await page.setViewportSize(viewport); await page.goto("/"); await page.getByLabel("Водоём").selectOption("kuori"); await page.getByLabel("Рыба").selectOption("lake-trout"); if (viewport.name === "mobile") await page.getByText("Период и порядок").click(); await page.getByLabel("Период").selectOption("72"); await page.getByLabel("Сначала").selectOption("freshness"); await page.getByRole("button", { name: "⌕ Найти клёв" }).click(); await expect(page).toHaveURL(/waterbody=kuori.*fish=lake-trout.*hours=72.*sort=freshness/); await expect(page.getByRole("heading", { name: "Горячие точки" })).toBeVisible(); await expect(page.getByLabel("Применённые фильтры")).toContainText("Куори"); await expect(page.getByLabel("Применённые фильтры")).toContainText("Озёрная форель"); await expect(page.getByRole("link", { name: "Сбросить" })).toBeVisible(); await expect(page.locator(".spot-list, .state")).toBeVisible(); const dimensions = await page.evaluate(() => ({ width: document.documentElement.clientWidth, scroll: document.documentElement.scrollWidth })); expect(dimensions.scroll).toBeLessThanOrEqual(dimensions.width); }); test(`external staging queue is safe on ${viewport.name}`, async ({ page }) => { await page.setViewportSize(viewport); let mutations = 0; await page.route("**/api/v1/**", async route => { const request = route.request(); if (request.method() !== "GET") mutations += 1; const path = new URL(request.url()).pathname; if (path.endsWith("/fishes")) return route.fulfill({ json: [{ slug: "pike", name_ru: "Щука" }] }); if (path.endsWith("/waterbodies")) return route.fulfill({ json: [{ slug: "kuori", name_ru: "Куори" }] }); if (path.endsWith("/admin/external-observations")) return route.fulfill({ json: [{ id: "10000000-0000-0000-0000-000000000001", source_system: "rf4db", source_external_id: "fixture-1", source_url: "https://rf4db.com/fixture-1", fish_name: "Pike", waterbody_name: "Kuori", x: 72, y: 84, weight_g: null, status: "staged", fish_slug: null, waterbody_slug: null, review_note: null }] }); return route.fulfill({ status: 404 }); }); await page.goto("/admin/external-sources"); await page.getByLabel("Административный токен").fill("test-token-not-sent"); await page.getByRole("button", { name: "Открыть очередь" }).click(); const card = page.locator(".moderation-card"); await expect(card).toContainText("rf4db"); await expect(card.getByLabel("Каноническая рыба")).toContainText("Щука"); await expect(card.getByRole("button", { name: "Опубликовать" })).toBeDisabled(); expect(mutations).toBe(0); const dimensions = await page.evaluate(() => ({ width: document.documentElement.clientWidth, scroll: document.documentElement.scrollWidth })); expect(dimensions.scroll).toBeLessThanOrEqual(dimensions.width); }); }