388 lines
24 KiB
TypeScript
388 lines
24 KiB
TypeScript
import { act, fireEvent, screen, waitFor, within } from "@testing-library/react";
|
||
import userEvent from "@testing-library/user-event";
|
||
import { readFileSync } from "node:fs";
|
||
import { join } from "node:path";
|
||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||
import { qk } from "../api/queries.ts";
|
||
import { setToken } from "../../../auth/token.ts";
|
||
import settings from "../board/__fixtures__/settings.json";
|
||
import { json, mockFetch } from "../../../test/fetchMock.ts";
|
||
import { renderWithProviders } from "../../../test/render.tsx";
|
||
import { IssuesPage } from "./IssuesPage.tsx";
|
||
|
||
const SNAP = JSON.parse(readFileSync(join(import.meta.dirname, "..", "board", "__fixtures__", "hub-basic.board.json"), "utf8"));
|
||
const deferred = <T,>() => { let resolve!: (v: T) => void; const promise = new Promise<T>((r) => { resolve = r; }); return { promise, resolve }; };
|
||
const row = (n: number) => ({ ...SNAP.issues[1], key: `AURORA-${9000 + n}`, summary: `Задача ${n}`, fetched_at: "" });
|
||
|
||
function hub(pages: (params: URLSearchParams) => unknown[]) {
|
||
return mockFetch((_m, url) => {
|
||
if (url.pathname === "/settings") return json(200, settings);
|
||
if (url.pathname === "/board") return json(200, SNAP);
|
||
if (url.pathname === "/issues") return json(200, { issues: pages(url.searchParams) });
|
||
return json(404, {});
|
||
});
|
||
}
|
||
const issuesCalls = (fetch: ReturnType<typeof hub>) =>
|
||
fetch.mock.calls.map(([u]) => new URL(String(u), "http://hub.test")).filter((u) => u.pathname === "/issues");
|
||
|
||
describe("список задач", () => {
|
||
beforeEach(() => { setToken("tok"); sessionStorage.clear(); });
|
||
const expanded = () => sessionStorage.setItem("sdlc-hub-issue-filters", "1");
|
||
|
||
it("фильтры из адреса уходят в GET /issues; ключ ведёт на карточку", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?sprint=AWPCA_Спринт 2&neighbor=1", path: "/issues" });
|
||
expect(await screen.findByRole("link", { name: "AURORA-9001" })).toHaveAttribute("href", "/jira/issues/AURORA-9001");
|
||
const q = issuesCalls(fetch)[0].searchParams;
|
||
expect(q.get("project")).toBe("AWPCA");
|
||
expect(q.get("sprint")).toBe("AWPCA_Спринт 2");
|
||
expect(q.get("neighbor")).toBe("1");
|
||
expect(q.get("limit")).toBe("50");
|
||
expect(q.get("offset")).toBe("0");
|
||
});
|
||
|
||
it("ключ строки: href прежний, щелчок с Ctrl не открывает карточку внутри приложения (Review Focus 1)", async () => {
|
||
hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
const link = await screen.findByRole("link", { name: "AURORA-9001" });
|
||
expect(link).toHaveAttribute("href", "/jira/issues/AURORA-9001");
|
||
fireEvent.click(link, { ctrlKey: true });
|
||
fireEvent.click(link, { metaKey: true });
|
||
expect(router.state.location.pathname).toBe("/issues");
|
||
});
|
||
|
||
it("выбор исполнителя пишет его в адрес и в запрос", async () => {
|
||
expanded();
|
||
const fetch = hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
await userEvent.selectOptions(screen.getByLabelText("Исполнитель"), "Петров Пётр Петрович");
|
||
await waitFor(() => expect(router.state.location.search).toContain("assignee="));
|
||
await waitFor(() => expect(issuesCalls(fetch).at(-1)!.searchParams.get("assignee")).toBe("Петров Пётр Петрович"));
|
||
});
|
||
|
||
it("ошибка списка — сообщение хаба и «Повторить», повтор перезапрашивает список (T8.4)", async () => {
|
||
let fail = true;
|
||
const fetch = mockFetch((_m, url) => {
|
||
if (url.pathname === "/settings") return json(200, settings);
|
||
if (url.pathname === "/board") return json(200, SNAP);
|
||
if (url.pathname === "/issues") return fail ? json(500, { error: "boom" }) : json(200, { issues: [row(1)] });
|
||
return json(404, {});
|
||
});
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
const alert = await screen.findByRole("alert");
|
||
expect(alert).toHaveTextContent("хаб ответил 500");
|
||
expect(screen.queryByText("По этим фильтрам задач нет")).toBeNull();
|
||
fail = false;
|
||
await userEvent.click(within(alert).getByRole("button", { name: "Повторить" }));
|
||
expect(await screen.findByRole("link", { name: "AURORA-9001" })).toBeInTheDocument();
|
||
expect(screen.queryByRole("alert")).toBeNull();
|
||
expect(issuesCalls(fetch)).toHaveLength(2);
|
||
});
|
||
|
||
it("«Показать ещё» до неполной порции", async () => {
|
||
const fetch = hub((p) => (p.get("offset") === "0" ? Array.from({ length: 50 }, (_, i) => row(i + 1)) : Array.from({ length: 17 }, (_, i) => row(i + 51))));
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await userEvent.click(await screen.findByRole("button", { name: "Показать ещё" }));
|
||
await waitFor(() => expect(screen.getAllByRole("row")).toHaveLength(1 + 67));
|
||
expect(screen.queryByRole("button", { name: "Показать ещё" })).toBeNull();
|
||
expect(issuesCalls(fetch).at(-1)!.searchParams.get("offset")).toBe("50");
|
||
});
|
||
|
||
it("поиск — один запрос после паузы ввода", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const before = issuesCalls(fetch).length;
|
||
await userEvent.type(screen.getByLabelText("Поиск по ключу или теме"), "свод");
|
||
await waitFor(() => expect(issuesCalls(fetch).at(-1)!.searchParams.get("q")).toBe("свод"));
|
||
expect(issuesCalls(fetch).length).toBe(before + 1);
|
||
});
|
||
|
||
it("хаб не отвечает — сообщение и «Повторить»", async () => {
|
||
let down = true;
|
||
mockFetch((_m, url) => {
|
||
if (url.pathname === "/issues" && down) throw new TypeError("Failed to fetch");
|
||
if (url.pathname === "/settings") return json(200, settings);
|
||
if (url.pathname === "/board") return json(200, SNAP);
|
||
return json(200, { issues: [row(1)] });
|
||
});
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
expect(await screen.findByRole("alert")).toHaveTextContent("хаб не отвечает");
|
||
down = false;
|
||
await userEvent.click(screen.getByRole("button", { name: "Повторить" }));
|
||
expect(await screen.findByRole("link", { name: "AURORA-9001" })).toBeInTheDocument();
|
||
});
|
||
|
||
it("эпик показывается названием из снимка", async () => {
|
||
hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
expect(await screen.findByRole("cell", { name: "Тестовый эпик" })).toBeInTheDocument();
|
||
});
|
||
|
||
it("T13.2: внешняя смена адреса обновляет поле поиска и не перезаписывается через 300 мс", async () => {
|
||
hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?q=свод", path: "/issues" });
|
||
const box = await screen.findByLabelText("Поиск по ключу или теме");
|
||
expect(box).toHaveValue("свод");
|
||
await act(async () => { await router.navigate("/issues"); });
|
||
await waitFor(() => expect(box).toHaveValue(""));
|
||
await new Promise((r) => setTimeout(r, 450));
|
||
expect(router.state.location.search).toBe("");
|
||
expect(box).toHaveValue("");
|
||
});
|
||
|
||
it("T13.2: ввод поиска пишется в адрес через replace (шаг «назад» не застревает)", async () => {
|
||
hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const before = router.state.historyAction;
|
||
await userEvent.type(screen.getByLabelText("Поиск по ключу или теме"), "свод");
|
||
await waitFor(() => expect(router.state.location.search).toBe("?q=%D1%81%D0%B2%D0%BE%D0%B4"));
|
||
expect(before).toBe("POP");
|
||
expect(router.state.historyAction).toBe("REPLACE");
|
||
});
|
||
|
||
it("T13.1: значение фильтра из адреса вне вариантов — в списке с пометкой", async () => {
|
||
expanded();
|
||
hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?sprint=AWPCA_Спринт 0", path: "/issues" });
|
||
const sel = await screen.findByLabelText("Спринт");
|
||
await waitFor(() => expect(within(sel).getByRole("option", { name: "AWPCA_Спринт 0 (нет на доске)" })).toBeInTheDocument());
|
||
expect(sel).toHaveValue("AWPCA_Спринт 0");
|
||
});
|
||
|
||
it("значение есть на доске, снимок грузится — подпись без пометки и после загрузки", async () => {
|
||
expanded();
|
||
const board = deferred<Response>();
|
||
mockFetch((_m, url) => {
|
||
if (url.pathname === "/settings") return json(200, settings);
|
||
if (url.pathname === "/board") return board.promise;
|
||
return json(200, { issues: [row(1)] });
|
||
});
|
||
const { client } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?sprint=AWPCA_Спринт 2", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const sel = await screen.findByLabelText("Спринт");
|
||
expect(within(sel).getByRole("option", { name: "AWPCA_Спринт 2" })).toBeInTheDocument();
|
||
expect(client.getQueryData(qk.board("AWPCA"))).toBeUndefined();
|
||
board.resolve(json(200, SNAP));
|
||
await waitFor(() => expect(client.getQueryData(qk.board("AWPCA"))).toBeDefined()); // снимок реально применён
|
||
await waitFor(() => expect(within(sel).getByRole("option", { name: "AWPCA_Спринт 2" })).toBeInTheDocument());
|
||
expect(within(sel).queryByRole("option", { name: /нет на доске/ })).toBeNull();
|
||
expect(sel).toHaveValue("AWPCA_Спринт 2");
|
||
});
|
||
|
||
it("T13.1: пока снимок доски грузится, пометки «(нет на доске)» нет", async () => {
|
||
expanded();
|
||
mockFetch((_m, url) => {
|
||
if (url.pathname === "/settings") return json(200, settings);
|
||
if (url.pathname === "/board") return new Promise<Response>(() => {}); // снимок не приходит до конца теста
|
||
return json(200, { issues: [row(1)] });
|
||
});
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?sprint=AWPCA_Спринт 2", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const sel = await screen.findByLabelText("Спринт");
|
||
expect(within(sel).queryByRole("option", { name: /нет на доске/ })).toBeNull();
|
||
expect(within(sel).getByRole("option", { name: "AWPCA_Спринт 2" })).toBeInTheDocument();
|
||
});
|
||
|
||
const openedRadio = () => screen.getByRole("radio", { name: "Открытые" });
|
||
const allRadio = () => screen.getByRole("radio", { name: "Все" });
|
||
|
||
it("закрытые скрыты по умолчанию; «Все» — closed=1 и запрос без hide_closed", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
expect(openedRadio()).toBeChecked();
|
||
let q = issuesCalls(fetch).at(-1)!.searchParams;
|
||
expect(q.get("hide_closed")).toBe("1");
|
||
expect(q.has("closed")).toBe(false);
|
||
await userEvent.click(allRadio());
|
||
await waitFor(() => expect(router.state.location.search).toBe("?closed=1"));
|
||
await waitFor(() => expect(issuesCalls(fetch).at(-1)!.searchParams.has("hide_closed")).toBe(false));
|
||
q = issuesCalls(fetch).at(-1)!.searchParams;
|
||
expect(q.has("closed")).toBe(false);
|
||
await userEvent.click(openedRadio());
|
||
await waitFor(() => expect(router.state.location.search).toBe(""));
|
||
expect(openedRadio()).toBeChecked(); // выборка с hide_closed=1 уже в кэше — повторного запроса может не быть
|
||
});
|
||
|
||
it("адрес closed=1 — выбрано «Все», запрос без hide_closed", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?closed=1", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
expect(allRadio()).toBeChecked();
|
||
expect(issuesCalls(fetch).every((u) => !u.searchParams.has("hide_closed"))).toBe(true);
|
||
});
|
||
|
||
it("выбран закрытый статус Jira — hide_closed не отправляется", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?status=Готово", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const q = issuesCalls(fetch).at(-1)!.searchParams;
|
||
expect(q.get("status")).toBe("Готово");
|
||
expect(q.has("hide_closed")).toBe(false);
|
||
expect(openedRadio()).toBeChecked();
|
||
});
|
||
|
||
it("выбран незакрытый статус Jira — hide_closed отправляется", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?status=В работе", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
expect(issuesCalls(fetch).at(-1)!.searchParams.get("hide_closed")).toBe("1");
|
||
});
|
||
|
||
it("настройки пришли позже — запрос по закрытому статусу без hide_closed", async () => {
|
||
const st = deferred<Response>();
|
||
const fetch = mockFetch((_m, url) => {
|
||
if (url.pathname === "/settings") return st.promise;
|
||
if (url.pathname === "/board") return json(200, SNAP);
|
||
return json(200, { issues: [row(1)] });
|
||
});
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?status=Снято", path: "/issues" });
|
||
st.resolve(json(200, settings));
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const calls = () => fetch.mock.calls.map(([u]) => new URL(String(u), "http://hub.test")).filter((u) => u.pathname === "/issues");
|
||
await waitFor(() => expect(calls().at(-1)!.searchParams.has("hide_closed")).toBe(false));
|
||
});
|
||
|
||
it("статуса по факту нет: ни фильтра, ни колонки; fact_status из адреса не уходит в запрос", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?fact_status=В работе", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
expect(screen.queryByLabelText("Статус по факту")).toBeNull();
|
||
expect(screen.queryByRole("columnheader", { name: "Статус по факту" })).toBeNull();
|
||
expect(issuesCalls(fetch).every((u) => !u.searchParams.has("fact_status"))).toBe(true);
|
||
});
|
||
describe("панель фильтров", () => {
|
||
const URL2 = "/issues?sprint=AWPCA_Спринт 2&assignee=Петров Пётр Петрович";
|
||
|
||
it("Фильтр в адресе: счётчик 2, плашки, панель свёрнута", async () => {
|
||
hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: URL2, path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const btn = screen.getByRole("button", { name: /Фильтры/ });
|
||
expect(btn).toHaveTextContent("2");
|
||
expect(btn).toHaveAttribute("aria-expanded", "false");
|
||
expect(screen.getByText("Спринт: AWPCA_Спринт 2")).toBeInTheDocument();
|
||
expect(screen.getByText("Исполнитель: Петров Пётр Петрович")).toBeInTheDocument();
|
||
expect(screen.queryByRole("combobox", { name: "Спринт" })).toBeNull();
|
||
});
|
||
|
||
it("кнопка «Фильтры» раскрывает панель и запоминает это в sessionStorage", async () => {
|
||
hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
await userEvent.click(screen.getByRole("button", { name: /Фильтры/ }));
|
||
expect(screen.getByRole("button", { name: /Фильтры/ })).toHaveAttribute("aria-expanded", "true");
|
||
for (const n of ["Спринт", "Исполнитель", "Вид работы", "Статус Jira", "Метка", "Эпик"]) expect(screen.getByLabelText(n)).toBeInTheDocument();
|
||
expect(sessionStorage.getItem("sdlc-hub-issue-filters")).toBe("1");
|
||
});
|
||
|
||
it("панель раскрыта по sessionStorage", async () => {
|
||
sessionStorage.setItem("sdlc-hub-issue-filters", "1");
|
||
hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
expect(await screen.findByLabelText("Спринт")).toBeInTheDocument();
|
||
});
|
||
|
||
it("Снять фильтр плашкой: sprint уходит из адреса, assignee остаётся", async () => {
|
||
const fetch = hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: URL2, path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
await userEvent.click(screen.getByRole("button", { name: "Снять фильтр Спринт" }));
|
||
await waitFor(() => expect(router.state.location.search).not.toContain("sprint="));
|
||
expect(router.state.location.search).toContain("assignee=");
|
||
await waitFor(() => expect(issuesCalls(fetch).at(-1)!.searchParams.has("sprint")).toBe(false));
|
||
});
|
||
|
||
it("«Сбросить» снимает шесть списков, поиск и «Только соседи» остаются", async () => {
|
||
hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />,
|
||
{ route: "/issues?sprint=AWPCA_Спринт 2&assignee=Петров Пётр Петрович&status=В работе&q=свод&neighbor=1", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
await userEvent.click(screen.getByRole("button", { name: "Сбросить" }));
|
||
await waitFor(() => expect(router.state.location.search).toBe("?q=%D1%81%D0%B2%D0%BE%D0%B4&neighbor=1"));
|
||
expect(screen.queryByRole("button", { name: "Сбросить" })).toBeNull();
|
||
});
|
||
|
||
it("«Только соседи» — кнопка с aria-pressed, пишет neighbor=1", async () => {
|
||
hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const b = screen.getByRole("button", { name: "Только соседи" });
|
||
expect(b).toHaveAttribute("aria-pressed", "false");
|
||
await userEvent.click(b);
|
||
await waitFor(() => expect(router.state.location.search).toBe("?neighbor=1"));
|
||
expect(screen.getByRole("button", { name: "Только соседи" })).toHaveAttribute("aria-pressed", "true");
|
||
});
|
||
});
|
||
|
||
it("подзаголовок: показано N", async () => {
|
||
hub(() => [row(1), row(2)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
expect(screen.getByText("показано 2")).toBeInTheDocument();
|
||
});
|
||
|
||
it("пустая выдача — сведения «По этим фильтрам задач нет»", async () => {
|
||
hub(() => []);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
expect(await screen.findByText("По этим фильтрам задач нет")).toBeInTheDocument();
|
||
});
|
||
|
||
it("Щелчок по строке: ячейка темы открывает карточку", async () => {
|
||
hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await userEvent.click(await screen.findByRole("cell", { name: "Задача 1" }));
|
||
await waitFor(() => expect(router.state.location.pathname).toBe("/jira/issues/AURORA-9001"));
|
||
});
|
||
|
||
it("выделенный текст в строке — без перехода", async () => {
|
||
hub(() => [row(1)]);
|
||
const { router } = renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
const cell = await screen.findByRole("cell", { name: "Задача 1" });
|
||
const sel = vi.spyOn(window, "getSelection").mockReturnValue({ toString: () => "Задач" } as unknown as Selection);
|
||
await userEvent.click(cell);
|
||
sel.mockRestore();
|
||
expect(router.state.location.pathname).toBe("/issues");
|
||
});
|
||
|
||
it("просрочено: класс overdue у срока незакрытой задачи с прошедшим сроком, у закрытой нет", async () => {
|
||
hub(() => [
|
||
{ ...row(1), planned_end: "2000-01-01", status: "В работе" },
|
||
{ ...row(2), planned_end: "2000-01-01", status: "Готово" },
|
||
{ ...row(3), planned_end: "2999-01-01", status: "В работе" },
|
||
]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?closed=1", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const dueCell = (n: number) => within(screen.getByRole("row", { name: new RegExp(`AURORA-${9000 + n}`) })).getByText(/^01\.01\.(2000|2999)$/);
|
||
await waitFor(() => expect(dueCell(1)).toHaveClass("overdue"));
|
||
expect(dueCell(2)).not.toHaveClass("overdue");
|
||
expect(dueCell(3)).not.toHaveClass("overdue");
|
||
});
|
||
it("шапка: вторичная кнопка «Обновить из Jira», primary нет", async () => {
|
||
hub(() => [row(1)]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const btn = screen.getByRole("button", { name: "Обновить из Jira" });
|
||
expect(btn).toHaveClass("ui-btn--secondary");
|
||
expect(document.querySelector(".ui-btn--primary")).toBeNull();
|
||
});
|
||
|
||
it("просроченный срок: «просрочено» для экранного чтения и title, срок через fmtDate", async () => {
|
||
hub(() => [{ ...row(1), planned_end: "2000-01-01", status: "В работе" }]);
|
||
renderWithProviders(<IssuesPage project="AWPCA" />, { route: "/issues?closed=1", path: "/issues" });
|
||
await screen.findByRole("link", { name: "AURORA-9001" });
|
||
const cell = await waitFor(() => {
|
||
const c = within(screen.getByRole("row", { name: /AURORA-9001/ })).getByText(", просрочено").closest("td")!;
|
||
expect(c).toHaveClass("overdue");
|
||
return c;
|
||
});
|
||
expect(within(cell).getByText(", просрочено")).toHaveClass("visually-hidden");
|
||
expect(cell.textContent).toBe("01.01.2000, просрочено");
|
||
expect(cell).toHaveAttribute("title", "просрочено");
|
||
expect(cell).toHaveTextContent("01.01.2000");
|
||
});
|
||
});
|