Each team gets a buzzer link (/b/[token]); a game opts in with a
"Teams buzz in" checkbox. The database's clock orders the buzzes, so
the result doesn't depend on phone clocks. The control room lists the
order and clears it per question (B); the scoreboard shows who buzzed
first with a ding.
Buzzes follow the existing rules: token-gated route handlers with the
service-role client, RLS on the new table, and data-free realtime pings
on a new buzzer:{eventId} channel.
Needs supabase/migrations/0003_buzzer.sql applied before deploying.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33 lines
1.1 KiB
TypeScript
33 lines
1.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("@/lib/env.public", () => ({ publicEnv: { NEXT_PUBLIC_SITE_URL: "https://gamenight.example" } }));
|
|
|
|
const { buzzerLinksText, judgeLinksText } = await import("./share-panel");
|
|
|
|
const judge = (name: string, token: string, revoked = false) =>
|
|
({ id: token, game_id: "g", name, token, revoked, source: "nominated", created_at: "" }) as const;
|
|
|
|
describe("judgeLinksText", () => {
|
|
it("lists one line per active judge, skipping revoked ones", () => {
|
|
const text = judgeLinksText({
|
|
title: "Karaoke",
|
|
judges: [judge("Eben", "aaa"), judge("Gone", "bbb", true), judge("Melissa", "ccc")],
|
|
});
|
|
expect(text).toBe(
|
|
"Karaoke — judge links\nEben: https://gamenight.example/j/aaa\nMelissa: https://gamenight.example/j/ccc",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("buzzerLinksText", () => {
|
|
it("lists one line per team", () => {
|
|
const text = buzzerLinksText([
|
|
{ name: "Lions", buzzer_token: "aaa" },
|
|
{ name: "Eagles", buzzer_token: "bbb" },
|
|
]);
|
|
expect(text).toBe(
|
|
"Buzzer links\nLions: https://gamenight.example/b/aaa\nEagles: https://gamenight.example/b/bbb",
|
|
);
|
|
});
|
|
});
|