Пакет веб-багов: карточка, лента, вики-разметка, состояние модулей (#124) #125

Merged
aep merged 17 commits from feat/124-web-bugs into main 2026-10-26 00:55:02 +03:00
5 changed files with 10 additions and 8 deletions
Showing only changes of commit 41f290c0e3 - Show all commits

View File

@ -1,5 +1,6 @@
import { act, renderHook } from "@testing-library/react";
import { beforeEach, describe, expect, it } from "vitest";
import { readPrefs } from "./storage.ts";
import type { BoardModel } from "./types.ts";
import { useBoardPrefs } from "./useBoardPrefs.ts";
@ -18,10 +19,10 @@ describe("useBoardPrefs: два переключения в одном такт
it("два toggle одного флага в одном такте возвращают исходное значение, в хранилище то же", () => {
const { result } = renderHook(() => useBoardPrefs(model, "P"));
const before = result.current.snap, stored = JSON.stringify(localStorage);
const before = result.current.snap;
act(() => { result.current.toggle("snap"); result.current.toggle("snap"); });
expect(result.current.snap).toBe(before);
expect(JSON.stringify(localStorage)).toBe(stored === "{}" ? JSON.stringify(localStorage) : stored);
expect(readPrefs().snap).toBe(before);
});
it("toggleHidden двух значений скрывает оба", () => {

View File

@ -73,7 +73,7 @@ describe("карточка задачи", () => {
expect(screen.getByRole("link", { name: "AURORA-9002" }).closest(".issue")).not.toBeNull(); // O-10: цвет темы
});
it("связь на ключ вне зеркала ведёт в Jira, а не в карточку (#103)", async () => {
it("связь на ключ вне зеркала ведёт в Jira, а не в карточку (#42 M-5)", async () => {
mockFetch((_m, url) => url.pathname === "/settings" ? json(200, settings) : json(200, issue("AURORA-9001", {
links_out: [{ target_key: "AURORA-1999", link_type: "ASUPR_Связанные", summary: null, status: null }], links_in: [],
})));
@ -84,7 +84,7 @@ describe("карточка задачи", () => {
expect(link.closest("li")).toHaveTextContent("нет в зеркале");
});
it("«Прочие поля»: вид работы отдельно от меток (#42 M-5)", async () => {
it("«Прочие поля»: вид работы отдельно от меток (#103)", async () => {
mockFetch((_m, url) => url.pathname === "/settings" ? json(200, settings) : json(200, issue("AURORA-9001", { labels: ["AWPCA", "kind-dev"] })));
open("/jira/issues/AURORA-9001");
await screen.findByRole("heading", { level: 2, name: "Тема AURORA-9001" });

View File

@ -117,7 +117,7 @@ describe("useAttachmentDownload", () => {
it("failed при опросе — ошибка команды; повторное start сбрасывает ошибку", async () => {
let n = 0;
mockFetch((m) => m === "POST" ? json(200, st("fetching")) : json(200, ++n === 1 ? st("failed", { error: "Jira: 404", ok: false }) : st("fetching")));
mockFetch((m) => m === "POST" ? json(200, st("fetching")) : json(200, ++n === 1 ? st("failed", { error: "Jira: 404" }) : st("fetching")));
const { result } = renderHook(() => useAttachmentDownload("AURORA-1650"), { wrapper });
act(() => result.current.start(att));
await flush();

View File

@ -124,7 +124,7 @@ function useTracker(m: (typeof TRACKED)[number], { qc, project, pollMs, agents }
}, [qc, status, setActiveNow]);
useEffect(() => {
// #116: ответ обрабатывается, только если отслеживаемый пакет — тот же объект, что в activeRef (смена проекта обнуляет его синхронно)
// #116: ответ обрабатывается, только если отслеживаемый пакет — тот же объект, что в activeRef (эффект смены проекта объявлен выше и в том же коммите обнуляет activeRef)
if (!active || activeRef.current !== active) return;
const b = batch.data, e = batch.error;
if (b && b.id === active.id && !batch.isError) {

View File

@ -339,11 +339,11 @@ describe("ActivityProvider", () => {
it("#116: ответ опроса прежнего проекта, пришедший в коммите смены проекта, не возвращает событие", async () => {
const late = deferred<void>();
let polls = 0;
let polls = 0, delivered = false;
hub({
...listing((mod, project) => (mod === "wiki" && project === "AWPCA" ? [item(9, "wiki")] : [])),
"GET /batches/:id": async () => {
if (++polls > 1) await late.promise; // первый ответ показан сразу, следующий держится до смены проекта
if (++polls > 1) { await late.promise; delivered = true; } // первый ответ показан сразу, следующий держится до смены проекта
return batch("running", { type: "wiki_sync" }, { id: 9, module: "wiki" });
},
});
@ -352,6 +352,7 @@ describe("ActivityProvider", () => {
await waitFor(() => expect(polls).toBeGreaterThanOrEqual(2)); // повторный опрос ушёл и висит
// смена проекта и ответ опроса — одним коммитом: оба обновления копятся в act и рендерятся вместе на выходе из него
await act(async () => { m.setProject("STOCKS_DRRK"); late.resolve(); await sleep(20); });
expect(delivered).toBe(true); // отложенный ответ действительно ушёл в обработчик
await waitFor(() => expect(m.now().wiki.event).toBeNull());
await sleep(50);
expect(m.now().wiki.event).toBeNull();