/** * The banner: what the view says big when a round turns, and when it says nothing. */ import { describe, expect, test } from "bun:test"; import { banner, drawBanner } from "../static/banner.js"; const snap = (over: Record = {}, me: Record | null = { team: "red", side: "atk" }) => ({ phase: "live", clock: 40, round: 5, result: null, last: { round: 4, winner: "blue", why: "Spike defused" }, me, ...over, }); describe("what the banner says", () => { test("a live round says nothing: the view is the game", () => { expect(banner(snap())).toBe(null); expect(banner(null)).toBe(null); }); test("a round over says whether you won, why, and when the next one starts", () => { const lost = banner(snap({ phase: "end", clock: 3.2 })); expect(lost).toEqual({ title: "Round lost", sub: "spike defused · next round in 4s", alpha: 1 }); const won = banner(snap({ phase: "end", clock: 3.2 }, { team: "blue", side: "def" })); expect(won?.title).toBe("Round won"); }); test("a round over with no outcome yet still says the round is over", () => { expect(banner(snap({ phase: "end", clock: 2, last: null }))).toEqual({ title: "Round over", sub: "next round in 2s", alpha: 1 }); }); test("a match over names the winner and the countdown to the next match", () => { const words = banner(snap({ phase: "over", clock: 9.9, result: "red win 13–7" })); expect(words?.title).toBe("red win 13–7"); expect(words?.sub).toBe("match over · a new match starts in 10s"); }); test("a round starting shows its number and your side for three seconds, then fades out", () => { // The buy phase is fourteen seconds; the card is for its first three. const fresh = banner(snap({ phase: "buy", clock: 14 })); expect(fresh).toEqual({ title: "Round 5", sub: "you attack — plant the spike on A or B", alpha: 1 }); expect(banner(snap({ phase: "buy", clock: 13 }, { team: "blue", side: "def" }))?.sub).toBe("you defend — hold A and B"); // Half a second before it goes it is half gone: it leaves by fading, never by blinking off. const leaving = banner(snap({ phase: "buy", clock: 11.25 })); expect(leaving?.alpha).toBeCloseTo(0.5, 5); expect(banner(snap({ phase: "buy", clock: 11 }))).toBe(null); expect(banner(snap({ phase: "buy", clock: 3 }))).toBe(null); }); test("a watcher with no side is told the team that took it", () => { expect(banner(snap({ phase: "end", clock: 3 }, null))?.title).toBe("blue take the round"); }); }); test("drawing it is one band and two lines of text, and nothing when there is nothing to say", () => { const ops: string[] = []; const ctx = new Proxy({} as Record, { get: (_, prop: string) => (...args: unknown[]) => ops.push(`${prop}(${args.map(String).join(",")})`), set: () => true, }); drawBanner(ctx as never, banner(snap({ phase: "end", clock: 3 })), 900, 570); expect(ops.filter((op) => op.startsWith("fillRect")).length).toBe(1); expect(ops.some((op) => op.startsWith("fillText(Round lost"))).toBe(true); expect(ops.some((op) => op.startsWith("fillText(spike defused"))).toBe(true); ops.length = 0; drawBanner(ctx as never, null, 900, 570); drawBanner(ctx as never, { title: "x", sub: "y", alpha: 0 }, 900, 570); expect(ops.length).toBe(0); });