import { 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 settings from "../board/__fixtures__/settings.json"; import { routes } from "../../../routes.tsx"; import { json, mockFetch } from "../../../test/fetchMock.ts"; import type { AgileResponse } from "../api/types.ts"; const S = { label: "AWPCA", name: "АРМ КА", board_id: 4216, board_type: "scrum", created_at: "", issues_count: 9, jira_synced_at: null }; const K = { label: "KAN", name: "Канбан", board_id: 5120, board_type: "kanban", created_at: "", issues_count: 4, jira_synced_at: null }; const data: AgileResponse = { board: { id: 4216, type: "scrum", columns: [{ name: "Новая", statuses: ["Новая"] }], kanban_backlog: null, fetched_at: "2026-10-24T10:00:00+00:00" }, sprints: [], backlog: [], board_issues: [], }; const open = (path: string) => { const router = createMemoryRouter(routes, { initialEntries: [path], basename: "/app" }); render(); return router; }; const base = (method: string, pathname: string, projects: unknown[]) => { const k = `${method} ${pathname}`; if (k === "GET /projects") return json(200, { projects }); if (k === "GET /settings") return json(200, settings); if (k === "GET /issues") return json(200, { issues: [] }); if (k === "GET /batches") return json(200, { batches: [] }); if (k === "GET /agents") return json(200, { agents: [] }); return null; }; describe("AgileGate (T9.4)", () => { beforeEach(() => { setToken("tok"); localStorage.clear(); }); it("пока /agile не ответил — «Загрузка…»", async () => { mockFetch((method, url) => { if (`${method} ${url.pathname}` === "GET /agile") return new Promise(() => {}); return base(method, url.pathname, [S]) ?? json(404, { error: "not_found" }); }); open("/app/jira/sprint?project=AWPCA"); expect(await screen.findByText("Загрузка…")).toBeInTheDocument(); }); it("ошибка /agile — текст ошибки и «Повторить», щелчок повторяет запрос", async () => { let calls = 0; mockFetch((method, url) => { if (`${method} ${url.pathname}` === "GET /agile") { calls += 1; return calls === 1 ? json(500, { error: "hub_down" }) : json(200, data); } return base(method, url.pathname, [S]) ?? json(404, { error: "not_found" }); }); open("/app/jira/sprint?project=AWPCA"); const retry = await screen.findByRole("button", { name: "Повторить" }); expect(screen.queryByText("Загрузка…")).not.toBeInTheDocument(); await userEvent.click(retry); await waitFor(() => expect(calls).toBe(2)); await waitFor(() => expect(screen.queryByRole("button", { name: "Повторить" })).not.toBeInTheDocument()); }); it("меню kanban-проекта: «Kanban» есть, «Бэклог» и «Спринт» нет", async () => { mockFetch((method, url) => base(method, url.pathname, [K]) ?? json(200, { ...data, board: { ...data.board, type: "kanban" }, sprints: [], backlog: null })); open("/app/jira/kanban?project=KAN"); const menu = await screen.findByRole("group", { name: "Jira" }); expect(await within(menu).findByRole("link", { name: "Kanban" })).toBeInTheDocument(); expect(within(menu).queryByRole("link", { name: "Бэклог" })).not.toBeInTheDocument(); expect(within(menu).queryByRole("link", { name: "Спринт" })).not.toBeInTheDocument(); }); });