114 lines
7.4 KiB
TypeScript
114 lines
7.4 KiB
TypeScript
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();
|
||
});
|
||
});
|