sdlc-hub/web/src/board/analysis/analysis.test.ts
Andrey Epifancev bb3ddd1770 fix: волна по финальному ревью — I-2 fromFile при обновлении из файла, тесты, комментарии, спека (#29)
I-2, M-4..M-8, M-11, M-13, M-16, M-18, T7.3, M-1..M-3, M-17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:40:33 +04:00

66 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import type { BoardSnapshot } from "../../api/types.ts";
import { canon } from "../../test/canon.ts";
import { dictsFromSettings } from "../dicts.ts";
import settings from "../__fixtures__/settings.json";
import scenarios from "../__fixtures__/scenarios.json";
import { fromHub } from "../model/fromHub.ts";
import { filtersOf } from "../../test/boardScenarios.ts";
import { noFilters } from "../types.ts";
import { initialSprints, initialWho, sprintItems, whoItems } from "./facets.ts";
import { emptyReason, computeVisible } from "./visible.ts";
const fx = (name: string) => readFileSync(join(import.meta.dirname, "..", "__fixtures__", name), "utf8");
const golden = JSON.parse(fx("hub-basic.expected.json"));
const { dicts } = dictsFromSettings(settings);
const model = fromHub(JSON.parse(fx("hub-basic.board.json")) as BoardSnapshot, dicts);
const TODAY = "2026-10-14";
type Sc = { name: string; filters: Record<string, unknown>; laneOrder?: string[] };
describe("выборка = links.html@3931a88", () => {
for (const sc of scenarios as Sc[]) {
it(`computeVisible: ${sc.name}`, () => {
const f = filtersOf(sc);
const vis = computeVisible(model, f, sc.laneOrder ?? [], TODAY);
const g = golden.scenarios[sc.name];
expect(canon(vis)).toEqual(g.visible);
expect(vis.nodes.length ? null : emptyReason(model, vis, f)).toEqual(g.emptyReason);
});
}
it("пустое зеркало — своя причина", () => {
const empty = fromHub(JSON.parse(fx("hub-empty.board.json")) as BoardSnapshot, dicts);
const vis = computeVisible(empty, noFilters(), [], TODAY);
expect(emptyReason(empty, vis, noFilters())).toBe(JSON.parse(fx("hub-empty.expected.json")).scenario.emptyReason);
});
it("счётчики и начальный выбор спринтов и исполнителей", () => {
// порядок как в старой странице: сначала спринты (счётчики без фильтров), затем исполнители — уже с выбранными спринтами
const check = (remembered: unknown, g: { sprintItems: unknown; whoItems: unknown; sprints: unknown; who: unknown }) => {
const f = noFilters();
expect(canon(sprintItems(model, f).items)).toEqual(g.sprintItems);
f.sprints = initialSprints(model, f, remembered);
expect(canon(f.sprints)).toEqual(g.sprints);
expect(canon(whoItems(model, f).items)).toEqual(g.whoItems);
expect(canon(initialWho(model, f, []))).toEqual(g.who);
};
check(null, golden.facets.default);
check([], golden.facets.rememberedAll);
check(["AWPCA_Спринт 3", "нет такого"], golden.facets.rememberedSome);
});
it("initialWho: из запомненного остаются только исполнители, которые есть на доске", () => {
const real = "Иванов Иван Иванович";
expect(canon(initialWho(model, noFilters(), [real, "нет такого"]))).toEqual([real]);
});
it("initialWho: все задачи без исполнителя — выбор пуст, даже если запомнен пустой ключ", () => {
const snap = JSON.parse(fx("hub-basic.board.json")) as BoardSnapshot;
const bare = fromHub({ ...snap, issues: snap.issues.map((i) => ({ ...i, assignee: null })) }, dicts);
expect(initialWho(bare, noFilters(), [""]).size).toBe(0);
});
});