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; 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); }); });