import { describe, expect, it } from "vitest"; import type { ForeshadowWindowView, OutlineChapterView, } from "@/lib/api/types"; import { distinctVolumes, filterByViewVolume, groupByVolume, isCloseWindow, windowBadgeLabel, } from "./outline"; const ch = (over: Partial): OutlineChapterView => ({ no: 1, volume: 1, beats: [], foreshadow_windows: [], ...over, }); const win = ( over: Partial, ): ForeshadowWindowView => ({ code: "F-012", ...over }); describe("groupByVolume", () => { it("groups by volume and sorts volumes and chapters ascending", () => { const groups = groupByVolume([ ch({ no: 127, volume: 3 }), ch({ no: 12, volume: 1 }), ch({ no: 126, volume: 3 }), ]); expect(groups.map((g) => g.volume)).toEqual([1, 3]); expect(groups[1]?.chapters.map((c) => c.no)).toEqual([126, 127]); }); it("handles undefined", () => { expect(groupByVolume(undefined)).toEqual([]); }); }); describe("distinctVolumes", () => { it("returns sorted unique volume numbers", () => { expect( distinctVolumes([ ch({ no: 1, volume: 2 }), ch({ no: 2, volume: 1 }), ch({ no: 3, volume: 2 }), ]), ).toEqual([1, 2]); }); it("handles undefined", () => { expect(distinctVolumes(undefined)).toEqual([]); }); }); describe("filterByViewVolume", () => { const chapters = [ ch({ no: 1, volume: 1 }), ch({ no: 2, volume: 1 }), ch({ no: 3, volume: 2 }), ]; it("returns all chapters when view is 'all'", () => { expect(filterByViewVolume(chapters, "all").map((c) => c.no)).toEqual([ 1, 2, 3, ]); }); it("keeps only the selected volume", () => { expect(filterByViewVolume(chapters, 2).map((c) => c.no)).toEqual([3]); }); it("returns empty for a volume with no chapters", () => { expect(filterByViewVolume(chapters, 9)).toEqual([]); }); it("handles undefined", () => { expect(filterByViewVolume(undefined, "all")).toEqual([]); }); }); describe("windowBadgeLabel", () => { it("renders a range, half-open, or bare badge without any glyph", () => { expect( windowBadgeLabel(win({ expected_close_from: 40, expected_close_to: 60 })), ).toBe("F-012 (40-60)"); expect(windowBadgeLabel(win({ expected_close_to: 60 }))).toBe("F-012 (≤60)"); expect(windowBadgeLabel(win({ expected_close_from: 40 }))).toBe( "F-012 (≥40)", ); expect(windowBadgeLabel(win({}))).toBe("F-012"); }); it("never embeds the raw ⚑ glyph (icon comes from the render layer)", () => { expect( windowBadgeLabel(win({ expected_close_from: 40, expected_close_to: 60 })), ).not.toContain("⚑"); }); }); describe("isCloseWindow", () => { it("is true when chapter no is inside the recovery window", () => { const w = win({ expected_close_from: 40, expected_close_to: 60 }); expect(isCloseWindow(ch({ no: 50 }), w)).toBe(true); expect(isCloseWindow(ch({ no: 61 }), w)).toBe(false); expect(isCloseWindow(ch({ no: 50 }), win({}))).toBe(false); }); });