quill/val Public Public: anyone can read this; nobody can change it.
forked from benjivers/val
workbenchthis machine
Files client-test-stands-still
| 1 | /** |
| 2 | * The banner: what the view says big when a round turns, and when it says nothing. |
| 3 | */ |
| 4 | |
| 5 | import { describe, expect, test } from "bun:test"; |
| 6 | import { banner, drawBanner } from "../static/banner.js"; |
| 7 | |
| 8 | const snap = (over: Record<string, unknown> = {}, me: Record<string, unknown> | null = { team: "red", side: "atk" }) => ({ |
| 9 | phase: "live", |
| 10 | clock: 40, |
| 11 | round: 5, |
| 12 | result: null, |
| 13 | last: { round: 4, winner: "blue", why: "Spike defused" }, |
| 14 | me, |
| 15 | ...over, |
| 16 | }); |
| 17 | |
| 18 | describe("what the banner says", () => { |
| 19 | test("a live round says nothing: the view is the game", () => { |
| 20 | expect(banner(snap())).toBe(null); |
| 21 | expect(banner(null)).toBe(null); |
| 22 | }); |
| 23 | |
| 24 | test("a round over says whether you won, why, and when the next one starts", () => { |
| 25 | const lost = banner(snap({ phase: "end", clock: 3.2 })); |
| 26 | expect(lost).toEqual({ title: "Round lost", sub: "spike defused · next round in 4s", alpha: 1 }); |
| 27 | const won = banner(snap({ phase: "end", clock: 3.2 }, { team: "blue", side: "def" })); |
| 28 | expect(won?.title).toBe("Round won"); |
| 29 | }); |
| 30 | |
| 31 | test("a round over with no outcome yet still says the round is over", () => { |
| 32 | expect(banner(snap({ phase: "end", clock: 2, last: null }))).toEqual({ title: "Round over", sub: "next round in 2s", alpha: 1 }); |
| 33 | }); |
| 34 | |
| 35 | test("a match over names the winner and the countdown to the next match", () => { |
| 36 | const words = banner(snap({ phase: "over", clock: 9.9, result: "red win 13–7" })); |
| 37 | expect(words?.title).toBe("red win 13–7"); |
| 38 | expect(words?.sub).toBe("match over · a new match starts in 10s"); |
| 39 | }); |
| 40 | |
| 41 | test("a round starting shows its number and your side for three seconds, then fades out", () => { |
| 42 | // The buy phase is fourteen seconds; the card is for its first three. |
| 43 | const fresh = banner(snap({ phase: "buy", clock: 14 })); |
| 44 | expect(fresh).toEqual({ title: "Round 5", sub: "you attack — plant the spike on A or B", alpha: 1 }); |
| 45 | expect(banner(snap({ phase: "buy", clock: 13 }, { team: "blue", side: "def" }))?.sub).toBe("you defend — hold A and B"); |
| 46 | // Half a second before it goes it is half gone: it leaves by fading, never by blinking off. |
| 47 | const leaving = banner(snap({ phase: "buy", clock: 11.25 })); |
| 48 | expect(leaving?.alpha).toBeCloseTo(0.5, 5); |
| 49 | expect(banner(snap({ phase: "buy", clock: 11 }))).toBe(null); |
| 50 | expect(banner(snap({ phase: "buy", clock: 3 }))).toBe(null); |
| 51 | }); |
| 52 | |
| 53 | test("a watcher with no side is told the team that took it", () => { |
| 54 | expect(banner(snap({ phase: "end", clock: 3 }, null))?.title).toBe("blue take the round"); |
| 55 | }); |
| 56 | }); |
| 57 | |
| 58 | test("drawing it is one band and two lines of text, and nothing when there is nothing to say", () => { |
| 59 | const ops: string[] = []; |
| 60 | const ctx = new Proxy({} as Record<string, unknown>, { |
| 61 | get: (_, prop: string) => (...args: unknown[]) => ops.push(`${prop}(${args.map(String).join(",")})`), |
| 62 | set: () => true, |
| 63 | }); |
| 64 | drawBanner(ctx as never, banner(snap({ phase: "end", clock: 3 })), 900, 570); |
| 65 | expect(ops.filter((op) => op.startsWith("fillRect")).length).toBe(1); |
| 66 | expect(ops.some((op) => op.startsWith("fillText(Round lost"))).toBe(true); |
| 67 | expect(ops.some((op) => op.startsWith("fillText(spike defused"))).toBe(true); |
| 68 | ops.length = 0; |
| 69 | drawBanner(ctx as never, null, 900, 570); |
| 70 | drawBanner(ctx as never, { title: "x", sub: "y", alpha: 0 }, 900, 570); |
| 71 | expect(ops.length).toBe(0); |
| 72 | }); |