import { cleanup, render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { createMemoryRouter } from "react-router"; import { beforeEach, describe, expect, it } from "vitest"; import { App } from "../App.tsx"; import { setToken } from "../auth/token.ts"; import { routes } from "../routes.tsx"; import { json, mockFetch } from "../test/fetchMock.ts"; const AWPCA = { label: "AWPCA", name: "АРМ КА", board_id: null, board_type: null, created_at: "", issues_count: 9, jira_synced_at: "2026-10-24T11:07:00", wiki_root: "DOC-1", wiki_synced_at: "2026-10-24T11:05:00", wiki_sync_error: null, wiki_pages: 28 }; const KEDR = { ...AWPCA, label: "sfm_kedr", name: "Кедр", wiki_root: null, wiki_synced_at: null, wiki_pages: null }; const AGENTS = [ { module: "jira", online: true, last_seen_at: "2026-10-24T09:30:00", agent_version: 14, warning: null, update_hint: null, current_command_id: null, release: null }, { module: "wiki", online: true, last_seen_at: "2026-10-24T09:30:00", agent_version: 1, warning: null, update_hint: null, current_command_id: null, release: null }, ]; const iss = (key: string, o: Record = {}) => ({ key, summary: key, issue_type: "Задача", status: "В работе", priority: null, assignee: null, sprint: null, duedate: null, planned_start: null, planned_end: null, epic_key: null, labels: [], neighbor: false, work_kind: null, ...o }); const BOARD = { generated_at: "", last_sync: null, links: [], issues: [ iss("A-1"), iss("A-2", { duedate: "2020-01-01" }), iss("A-3", { status: "Готово", duedate: "2020-01-01" }), iss("A-4", { duedate: "2999-01-01" }), iss("N-1", { neighbor: true, duedate: "2020-01-01" }), ] }; const BATCHES = [ { id: 7, title: "Обновление вики AWPCA", author: "viewer", created_at: "2026-10-24T10:00:00", status: "done", module: "wiki", counts: {}, remaining_seconds: 0 }, { id: 6, title: "Полное обновление", author: "claude", created_at: "2026-10-24T09:00:00", status: "queued", module: "jira", counts: {}, remaining_seconds: 0 }, ]; function hub() { const calls: string[] = []; mockFetch((method, url) => { calls.push(`${method} ${url.pathname}${url.search}`); const k = `${method} ${url.pathname}`; if (k === "GET /projects") return json(200, { projects: [AWPCA, KEDR] }); if (k === "GET /agents") return json(200, { agents: AGENTS }); if (k === "GET /batches") return json(200, { batches: url.searchParams.get("limit") === "10" ? BATCHES : [] }); if (k === "GET /board") return json(200, BOARD); if (k === "GET /settings") return json(200, { closed_statuses: ["Готово"] }); if (k === "GET /wiki/status") return json(200, { root: "DOC-1", root_title: "Корень", synced_at: "2026-10-24T11:05:00", pages: 28, error: null, batch: null }); if (k === "GET /wiki/tree") return json(200, { pages: [{ code: "DOC-1", title: "Корень", depth: 0, parent_code: null, size: 1 }] }); if (k === "GET /wiki/search") return json(200, { results: [{ code: "DOC-2", title: "Согласование", path: "DOC-2", snippet: "порядок согласования" }] }); return json(404, { error: "not_found" }); }); return calls; } const open = (project = "AWPCA") => { const router = createMemoryRouter(routes, { initialEntries: [`/app/?project=${project}`], basename: "/app" }); render(); return router; }; const page = async () => within(await screen.findByTestId("page-overview")); const card = async (name: string) => (await page()).findByRole("region", { name }); describe("страница «Обзор»", () => { beforeEach(() => { setToken("tok"); localStorage.clear(); cleanup(); }); it("две карточки: агент с версией, свежесть, вторичные кнопки обновления", async () => { hub(); open(); const jira = within(await card("Jira")); const wiki = within(await card("Вики")); expect(await jira.findByText("агент на связи")).toBeInTheDocument(); expect(jira.getByText("v14")).toBeInTheDocument(); expect(await wiki.findByText("v1")).toBeInTheDocument(); expect(jira.getByText("данные на 24.10 11:07 · все проекты")).toBeInTheDocument(); expect(wiki.getByText("данные на 24.10 11:05 · проект")).toBeInTheDocument(); for (const [c, name] of [[jira, "Обновить из Jira"], [wiki, "Обновить вики"]] as const) { expect(c.getByRole("button", { name })).toHaveClass("ui-btn--secondary"); } }); it("сводка jira: свои задачи, открытые и просроченные; ссылки на разделы", async () => { hub(); open(); const jira = within(await card("Jira")); expect(await jira.findByText((_, el) => el?.classList.contains("jira-summary__counts") === true && el.textContent === "4 задач · 3 открытых · 1 просрочено")).toBeInTheDocument(); expect(jira.getByRole("link", { name: "Доска связей" })).toHaveAttribute("href", "/app/jira/board?project=AWPCA"); expect(jira.getByRole("link", { name: "Задачи" })).toHaveAttribute("href", "/app/jira/issues?project=AWPCA"); expect(jira.getByRole("link", { name: "Лента" })).toHaveAttribute("href", "/app/jira/feed?project=AWPCA"); }); it("overview card without root: объяснение и «Привязать корень вики»", async () => { hub(); open("sfm_kedr"); const wiki = within(await card("Вики")); const link = await wiki.findByRole("link", { name: "Привязать корень вики" }); expect(link).toHaveAttribute("href", "/app/wiki?project=sfm_kedr"); expect(wiki.queryByRole("button", { name: "Обновить вики" })).toBeNull(); expect(wiki.queryByRole("searchbox")).toBeNull(); }); it("«Последние пакеты проекта»: номер, модуль, название, автор, статус, время", async () => { const calls = hub(); open(); const p = await page(); const rows = await p.findAllByRole("row"); const r7 = within(rows.find((r) => r.textContent?.includes("#7"))!); for (const t of ["#7", "Вики", "Обновление вики AWPCA", "viewer", "выполнен", "24.10 10:00"]) expect(r7.getByText(t)).toBeInTheDocument(); expect(within(rows.find((r) => r.textContent?.includes("#6"))!).getByText("в очереди")).toBeInTheDocument(); expect(p.getByRole("heading", { name: "Последние пакеты проекта" })).toBeInTheDocument(); expect(calls).toContain("GET /batches?project=AWPCA&limit=10"); }); it("поиск по вики: Enter ведёт на «Вики» с q, строка поиска заполнена, результаты показаны", async () => { hub(); const router = open(); const wiki = within(await card("Вики")); await userEvent.type(await wiki.findByRole("searchbox", { name: "Поиск по вики" }), "согласов{Enter}"); await waitFor(() => expect(router.state.location.pathname).toBe("/app/wiki")); expect(router.state.location.search).toBe("?project=AWPCA&q=%D1%81%D0%BE%D0%B3%D0%BB%D0%B0%D1%81%D0%BE%D0%B2"); expect(await screen.findByRole("searchbox", { name: "Поиск по вики" })).toHaveValue("согласов"); expect(await screen.findByText("Согласование")).toBeInTheDocument(); }); });