sdlc-hub/web/src/shell/OverviewPage.test.tsx
2026-10-24 15:30:12 +04:00

114 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, unknown> = {}) => ({ 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(<App router={router} />);
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();
});
});