diff --git a/web/e2e/board.spec.ts b/web/e2e/board.spec.ts index fea09f1..644d640 100644 --- a/web/e2e/board.spec.ts +++ b/web/e2e/board.spec.ts @@ -58,7 +58,7 @@ test("ⓘ открывает карточку окном поверх доски await expect(page).toHaveURL(/\/app\/issues\/AURORA-9001$/); await expect(page.getByRole("dialog").getByRole("heading", { name: "Схема свода" })).toBeVisible(); await expect(page.getByTestId("page-board")).toBeAttached(); - await expect(page.getByRole("link", { name: "Доска", exact: true })).toHaveClass(/active/); + await expect(page.getByRole("link", { name: "Доска связей", exact: true })).toHaveAttribute("aria-current", "page"); }); test("M-3: прокрутка и данные доски после окна карточки и после другого раздела", async ({ page }) => { @@ -85,7 +85,7 @@ test("M-3: прокрутка и данные доски после окна к // ссылки меню вверху страницы: обычный click прокрутил бы доску к ним до перехода и затёр запомненное, поэтому dispatchEvent await page.getByRole("link", { name: "Задачи", exact: true }).dispatchEvent("click"); await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/); - await page.getByRole("link", { name: "Доска", exact: true }).dispatchEvent("click"); + await page.getByRole("link", { name: "Доска связей", exact: true }).dispatchEvent("click"); await expect.poll(() => page.evaluate(() => window.scrollY)).toBeGreaterThan(y - 2); }); diff --git a/web/e2e/hub.ts b/web/e2e/hub.ts index 621bc7d..86eff72 100644 --- a/web/e2e/hub.ts +++ b/web/e2e/hub.ts @@ -9,7 +9,7 @@ export async function login(page: Page, path = "/app/board") { await page.goto(path); await page.getByLabel("Клиентский токен хаба").fill(tokens().client); await page.getByRole("button", { name: "Войти" }).click(); - await expect(page.getByRole("link", { name: "Доска" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Доска связей" })).toBeVisible(); } async function call(path: string, body: unknown) { diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx index 070faf4..611e12c 100644 --- a/web/src/App.test.tsx +++ b/web/src/App.test.tsx @@ -27,7 +27,7 @@ describe("оболочка", () => { expect(router.state.location.pathname).toBe("/app/issues"); expect(router.state.location.search).toBe("?project=AWPCA"); expect(await screen.findByTestId("page-issues")).toBeInTheDocument(); - await userEvent.click(screen.getByRole("link", { name: "Доска" })); + await userEvent.click(screen.getByRole("link", { name: "Доска связей" })); expect(router.state.location.pathname).toBe("/app/board"); expect(router.state.location.search).toBe("?project=AWPCA"); expect(await screen.findByTestId("page-board")).toBeInTheDocument(); @@ -42,6 +42,7 @@ describe("оболочка", () => { it("«Сменить токен» показывает экран входа", async () => { render(); + await userEvent.click(await screen.findByRole("button", { name: "Ещё" })); await userEvent.click(screen.getByRole("button", { name: "Сменить токен" })); expect(getAuth().token).toBe(""); expect(screen.getByRole("heading", { name: "Вход в хаб" })).toBeInTheDocument(); @@ -56,13 +57,13 @@ describe("оболочка", () => { render(); expect(await screen.findByText(/сломалось/)).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Перезагрузить" })).toBeInTheDocument(); - expect(screen.getByRole("link", { name: "Доска" })).toBeInTheDocument(); + expect(screen.getByRole("link", { name: "Доска связей" })).toBeInTheDocument(); } finally { PAGE_ROUTES.splice(PAGE_ROUTES.indexOf(boom), 1); } }); it("без токена — только экран входа", () => { clearToken(); render(); - expect(screen.queryByRole("link", { name: "Доска" })).not.toBeInTheDocument(); + expect(screen.queryByRole("link", { name: "Доска связей" })).not.toBeInTheDocument(); }); }); diff --git a/web/src/api/queries.ts b/web/src/api/queries.ts index d44bb2b..0ec0b20 100644 --- a/web/src/api/queries.ts +++ b/web/src/api/queries.ts @@ -19,8 +19,9 @@ export const qk = { export const useSettings = () => useQuery({ queryKey: qk.settings, queryFn: () => api.get("/settings"), staleTime: Infinity }); -export const useBoard = (project: string) => - useQuery({ queryKey: qk.board(project), queryFn: () => api.get("/board", { project }) }); +// project === null — проект ещё не выбран: запроса нет +export const useBoard = (project: string | null) => + useQuery({ queryKey: qk.board(project ?? ""), enabled: project !== null, queryFn: () => api.get("/board", { project: project! }) }); export const useProjects = () => useQuery({ queryKey: qk.projects, queryFn: () => api.get<{ projects?: Project[] }>("/projects").then((r) => r.projects ?? []) }); diff --git a/web/src/board/BoardPage.test.tsx b/web/src/board/BoardPage.test.tsx index afa9e82..5e70356 100644 --- a/web/src/board/BoardPage.test.tsx +++ b/web/src/board/BoardPage.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, screen, waitFor, within } from "@testing-library/react"; +import { fireEvent, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { readFileSync } from "node:fs"; import { join } from "node:path"; @@ -111,34 +111,21 @@ describe("страница доски", () => { expect(await screen.findByText("jira-export.csv (из файла)")).toBeInTheDocument(); }); - it("тема переключается по кругу и применяется к документу", async () => { + it("доска не рисует «Обновить из Jira» и ход пакета: они в верхней панели", async () => { hub(); page(); await screen.findByText(/задач из 9/); + expect(screen.queryByRole("button", { name: "Обновить из Jira" })).not.toBeInTheDocument(); await userEvent.click(screen.getByTitle("Ещё")); - await userEvent.click(screen.getByRole("button", { name: "Тема: системная" })); - expect(document.documentElement.getAttribute("data-theme")).toBe("light"); - expect(localStorage.getItem("graph-theme")).toBe("light"); + expect(screen.queryByRole("button", { name: /^Тема/ })).not.toBeInTheDocument(); }); - - it("«Обновить из Jira» ставит пакет и недоступна, пока он идёт", async () => { - const fetch = hub(); - page(); - await screen.findByText(/задач из 9/); - const btn = screen.getByRole("button", { name: "Обновить из Jira" }); - await userEvent.click(btn); - await waitFor(() => expect(btn).toBeDisabled()); - expect(fetch.mock.calls.filter(([u, i]) => u === "/refresh" && i?.method === "POST")).toHaveLength(1); - expect(await screen.findByText(/пакет #7 выполняется/)).toBeInTheDocument(); - }); - it("заголовок страницы — h1, пока идёт пакет в шапке label и строка состояния", async () => { + it("заголовок страницы — h1, в шапке label; строка состояния — только события доски", async () => { hub(); page(); await screen.findByText(/задач из 9/); expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument(); - await userEvent.click(screen.getByRole("button", { name: "Обновить из Jira" })); - expect(await screen.findByRole("status")).toHaveTextContent(/пакет #7/); expect(screen.getByText(/^Хаб · данные на/)).toBeInTheDocument(); + expect(screen.queryByRole("status")).not.toBeInTheDocument(); }); it("пустой link_types при заполненном kind_labels — называет только недостающий словарь", async () => { diff --git a/web/src/board/components/Toolbar.tsx b/web/src/board/components/Toolbar.tsx index f367126..8701d00 100644 --- a/web/src/board/components/Toolbar.tsx +++ b/web/src/board/components/Toolbar.tsx @@ -1,7 +1,4 @@ import { useEffect, useState } from "react"; -import { useJiraRefresh } from "../../shell/refresh.ts"; -import { refreshText } from "../../shell/refreshStatus.ts"; -import { applyTheme, readTheme, saveTheme, THEME_NAMES, THEMES, type Theme } from "../../shell/theme.ts"; import { statusText, type BoardStatus } from "../status.ts"; import type { BoardModel, Visible } from "../types.ts"; import { NOT_IN_HUB } from "../constants.ts"; @@ -15,23 +12,17 @@ interface ToolbarProps { } export function Toolbar(p: ToolbarProps) { - const [theme, setTheme] = useState(readTheme); const [confirm, setConfirm] = useState(false); useEffect(() => { if (!confirm) return; const t = setTimeout(() => setConfirm(false), 8000); return () => clearTimeout(t); }, [confirm]); - const refresh = useJiraRefresh(); // кнопка и ход пакета временно здесь; в верхней панели — с Task 5 - const rt = refresh.event ? refreshText(refresh.event) : null; - const st = p.status ? statusText(p.status) : rt ? { text: rt.text, warn: rt.tone === "error" || rt.tone === "warning" } : null; + const st = p.status ? statusText(p.status) : null; // только события доски; ход пакета «Обновить из Jira» — в верхней панели const m = p.model, v = p.vis, r = v.removed; const coreShown = v.nodes.filter((n) => !n.dim).length, ctxShown = v.nodes.length - coreShown; - const nextTheme = () => { const t = THEMES[(THEMES.indexOf(theme) + 1) % THEMES.length]; setTheme(t); saveTheme(t); applyTheme(t); }; return (

Доска связей

{p.label} -