import { existsSync, readdirSync } from "node:fs" import { join } from "node:path" import { test, expect, type Page } from "@playwright/test" import { navigationItems } from "../../lib/navigation" async function setup(page: Page) { await page.route("**/health", route => route.fulfill({ json: { ok: true } })) await page.route("**/api/**", route => { const path = new URL(route.request().url()).pathname return route.fulfill({ json: path === "/api/servers" ? [] : path === "/api/recursive-routes" ? { routes: [] } : path === "/api/recursive-routes/gateways" ? { gateways: [] } : path === "/api/auth/config" ? { required: false } : path === "/api/evobgp/settings" ? { enabled: false, baseUrl: "", secretConfigured: false } : {} }) }) await page.goto("/recursive-routes") await expect(page.getByRole("navigation", { name: "Путь страницы" })).toBeVisible() } test("каталог навигации покрывает существующие разделы без повторов и мёртвых ссылок", () => { const root = join(process.cwd(), "app/(main)") const pages = readdirSync(root).filter(name => existsSync(join(root, name, "page.tsx"))).map(name => `/${name}`) expect(navigationItems.map(item => item.url).sort()).toEqual(pages.sort()) expect(new Set(navigationItems.map(item => item.id)).size).toBe(navigationItems.length) expect(new Set(navigationItems.map(item => item.title)).size).toBe(navigationItems.length) }) test("поиск страниц: одинаковые названия, синонимы, клавиатура и переход", async ({ page }) => { await setup(page) const sidebar = page.getByRole("navigation", { name: "Основная навигация" }) for (const item of navigationItems) { await expect(sidebar.locator(`a[href="${item.url}"]`)).toContainText(item.title) } const opener = page.getByRole("button", { name: "Приложения", exact: true }) await opener.focus() await page.keyboard.press("Control+k") const dialog = page.getByRole("dialog", { name: "Поиск страниц", exact: true }) const input = dialog.getByRole("combobox", { name: "Поиск страниц", exact: true }) await expect(input).toBeFocused() await expect(dialog.getByRole("option")).toHaveCount(navigationItems.length) await input.press("ArrowDown") await expect(dialog.getByRole("option", { name: "Трафик", exact: true })).toHaveAttribute("data-selected", "true") await input.press("ArrowUp") await expect(dialog.getByRole("option", { name: "Дашборд", exact: true })).toHaveAttribute("data-selected", "true") await input.fill("uptime") await expect(dialog.getByRole("option", { name: "Мониторинг", exact: true })).toBeVisible() await input.fill("Несуществующийраздел999") await expect(dialog.getByRole("status")).toContainText("Ничего не найдено") await input.press("ArrowDown") await input.press("Enter") await expect(dialog).toBeVisible() await page.keyboard.press("Tab") await expect.poll(() => dialog.evaluate(element => element.contains(document.activeElement))).toBe(true) await page.keyboard.press("Escape") await expect(dialog).toBeHidden() await expect(opener).toBeFocused() await page.keyboard.press("Control+k") await expect(input).toHaveValue("") await input.fill("mikrotik") await expect(dialog.getByRole("option", { name: "Серверы", exact: true })).toBeVisible() await input.press("Enter") await expect(page).toHaveURL(/\/servers$/) await expect(dialog).toBeHidden() await expect(sidebar.locator('a[href="/servers"]')).toHaveAttribute("aria-current", "page") await page.goBack() await expect(sidebar.locator('a[href="/recursive-routes"]')).toHaveAttribute("aria-current", "page") }) for (const theme of ["dark", "light"]) { test(`мобильное меню и поиск: тема ${theme}, низкий экран`, async ({ page }) => { await page.addInitScript(value => localStorage.setItem("rl-theme", value), theme) await page.setViewportSize({ width: 390, height: 600 }) await setup(page) const menu = page.getByRole("button", { name: "Боковое меню", exact: true }) await menu.click() const sidebar = page.getByRole("navigation", { name: "Основная навигация" }) await sidebar.locator('a[href="/servers"]').click() await expect(page).toHaveURL(/\/servers$/) await expect(page.getByRole("dialog")).toBeHidden() await menu.click() await expect(sidebar.locator('a[href="/servers"]')).toHaveAttribute("aria-current", "page") await sidebar.locator('a[href="/servers"]').click() await expect(page.getByRole("dialog")).toBeHidden() await page.keyboard.press("Control+k") const search = page.getByRole("dialog", { name: "Поиск страниц", exact: true }) await expect(search).toBeVisible() const bounds = await search.boundingBox() expect(bounds!.x).toBeGreaterThanOrEqual(0) expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(390) expect(bounds!.y).toBeGreaterThanOrEqual(0) expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(600) await page.screenshot({ path: `test-results/navigation-mobile-${theme}.png`, animations: "disabled" }) }) }