fix(web): ссылка в ветке «задачи нет в зеркале» в .issue, тесты класса и подписи фильтра, withCurrent выше комментария (#29/O-10, T5.1, T5.4, #29)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6940da5a20
commit
a002812f12
@ -180,6 +180,13 @@ describe("карточка задачи", () => {
|
|||||||
await waitFor(() => expect(screen.queryByRole("alert")).toBeNull());
|
await waitFor(() => expect(screen.queryByRole("alert")).toBeNull());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("O-10: ссылка в ветке «задачи нет в зеркале» стоит внутри .issue (цвет темы)", async () => {
|
||||||
|
mockFetch((_m, url) => url.pathname === "/settings" ? json(200, settings) : json(404, { error: "not_found", detail: "issue" }));
|
||||||
|
open("/issues/AURORA-1");
|
||||||
|
const link = await screen.findByRole("link", { name: "к списку задач" });
|
||||||
|
expect(link.closest(".issue")).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("O-11: задача без команд — текст вместо пустой таблицы", async () => {
|
it("O-11: задача без команд — текст вместо пустой таблицы", async () => {
|
||||||
mockFetch((_m, url) => url.pathname === "/settings" ? json(200, settings) : json(200, issue("AURORA-9001", { commands: [] })));
|
mockFetch((_m, url) => url.pathname === "/settings" ? json(200, settings) : json(200, issue("AURORA-9001", { commands: [] })));
|
||||||
open("/issues/AURORA-9001");
|
open("/issues/AURORA-9001");
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export function IssuePage() {
|
|||||||
|
|
||||||
if (issue.isPending) return <p className="hint" data-testid="page-issue">загрузка {key}…</p>;
|
if (issue.isPending) return <p className="hint" data-testid="page-issue">загрузка {key}…</p>;
|
||||||
if (issue.error instanceof ApiError && issue.error.status === 404) {
|
if (issue.error instanceof ApiError && issue.error.status === 404) {
|
||||||
return <p data-testid="page-issue">Задачи {key} нет в зеркале хаба. <Link to="/issues">к списку задач</Link></p>;
|
return <p className="issue" data-testid="page-issue">Задачи {key} нет в зеркале хаба. <Link to="/issues">к списку задач</Link></p>;
|
||||||
}
|
}
|
||||||
if (issue.isError && !issue.data) {
|
if (issue.isError && !issue.data) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { renderWithProviders } from "../test/render.tsx";
|
|||||||
import { IssuesPage } from "./IssuesPage.tsx";
|
import { IssuesPage } from "./IssuesPage.tsx";
|
||||||
|
|
||||||
const SNAP = JSON.parse(readFileSync(join(import.meta.dirname, "..", "board", "__fixtures__", "hub-basic.board.json"), "utf8"));
|
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: "" });
|
const row = (n: number) => ({ ...SNAP.issues[1], key: `AURORA-${9000 + n}`, summary: `Задача ${n}`, fetched_at: "" });
|
||||||
|
|
||||||
function hub(pages: (params: URLSearchParams) => unknown[]) {
|
function hub(pages: (params: URLSearchParams) => unknown[]) {
|
||||||
@ -117,6 +118,23 @@ describe("список задач", () => {
|
|||||||
expect(sel).toHaveValue("AWPCA_Спринт 0");
|
expect(sel).toHaveValue("AWPCA_Спринт 0");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("T5.1: значение есть на доске, снимок грузится — подпись без пометки и после загрузки", async () => {
|
||||||
|
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)] });
|
||||||
|
});
|
||||||
|
renderWithProviders(<IssuesPage />, { 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();
|
||||||
|
board.resolve(json(200, SNAP));
|
||||||
|
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 () => {
|
it("T13.1: пока снимок доски грузится, пометки «(нет на доске)» нет", async () => {
|
||||||
mockFetch((_m, url) => {
|
mockFetch((_m, url) => {
|
||||||
if (url.pathname === "/settings") return json(200, settings);
|
if (url.pathname === "/settings") return json(200, settings);
|
||||||
|
|||||||
@ -9,13 +9,13 @@ export const epicNames = (snap: BoardSnapshot | undefined): Record<string, strin
|
|||||||
|
|
||||||
export const dueOf = (i: { planned_end: string | null; duedate: string | null }) => (i.planned_end || i.duedate || "").slice(0, 10);
|
export const dueOf = (i: { planned_end: string | null; duedate: string | null }) => (i.planned_end || i.duedate || "").slice(0, 10);
|
||||||
|
|
||||||
// варианты выпадающих списков фильтров: из снимка /board и словарей настроек, без новых маршрутов API
|
|
||||||
// T13.1: значение фильтра из адреса, которого нет среди вариантов (закрытый спринт, ушедший исполнитель), видно в списке;
|
// T13.1: значение фильтра из адреса, которого нет среди вариантов (закрытый спринт, ушедший исполнитель), видно в списке;
|
||||||
// пока снимок доски не загружен, пометку не ставим (R-4)
|
// пока снимок доски не загружен, пометку не ставим (R-4)
|
||||||
export const withCurrent = (opts: Opt[], value: string | undefined, loaded: boolean): Opt[] =>
|
export const withCurrent = (opts: Opt[], value: string | undefined, loaded: boolean): Opt[] =>
|
||||||
!value || opts.some((o) => o.value === value) ? opts
|
!value || opts.some((o) => o.value === value) ? opts
|
||||||
: [{ value, label: loaded ? `${value} (нет на доске)` : value }, ...opts];
|
: [{ value, label: loaded ? `${value} (нет на доске)` : value }, ...opts];
|
||||||
|
|
||||||
|
// варианты выпадающих списков фильтров: из снимка /board и словарей настроек, без новых маршрутов API
|
||||||
export function filterOptions(snap: BoardSnapshot | undefined, s: HubSettings | undefined) {
|
export function filterOptions(snap: BoardSnapshot | undefined, s: HubSettings | undefined) {
|
||||||
const issues = snap?.issues ?? [];
|
const issues = snap?.issues ?? [];
|
||||||
const names = epicNames(snap);
|
const names = epicNames(snap);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user