diff options
Diffstat (limited to 'src/lib/components')
| -rw-r--r-- | src/lib/components/AdminPanel.svelte | 33 | ||||
| -rw-r--r-- | src/lib/components/CourtList.svelte | 14 | ||||
| -rw-r--r-- | src/lib/components/GameCard.svelte | 17 | ||||
| -rw-r--r-- | src/lib/components/GameCard.test.js | 32 | ||||
| -rw-r--r-- | src/lib/components/GamesSection.svelte | 11 | ||||
| -rw-r--r-- | src/lib/components/GuestList.svelte | 14 | ||||
| -rw-r--r-- | src/lib/components/ProposeConfirmControls.svelte | 8 | ||||
| -rw-r--r-- | src/lib/components/RegularList.svelte | 14 | ||||
| -rw-r--r-- | src/lib/components/SkillFactorSelect.svelte | 13 | ||||
| -rw-r--r-- | src/lib/components/TeamDisplay.svelte | 7 | ||||
| -rw-r--r-- | src/lib/components/TimerControls.svelte | 39 | ||||
| -rw-r--r-- | src/lib/components/TimerControls.test.js | 35 | ||||
| -rw-r--r-- | src/lib/components/ToggleButton.svelte | 7 | ||||
| -rw-r--r-- | src/lib/components/ToggleButton.test.js | 31 |
14 files changed, 275 insertions, 0 deletions
diff --git a/src/lib/components/AdminPanel.svelte b/src/lib/components/AdminPanel.svelte new file mode 100644 index 0000000..2442386 --- /dev/null +++ b/src/lib/components/AdminPanel.svelte @@ -0,0 +1,33 @@ +<script> + import { resetSession } from "../stores/games.svelte.js"; + import { loadRegularsFromCsv } from "../stores/regulars.svelte.js"; + + let fileInput; + + function triggerUpload() { + fileInput.click(); + } + + function handleFileChange(event) { + const file = event.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + loadRegularsFromCsv(reader.result); + }; + reader.readAsText(file); + } +</script> + +<div class="grid"> + <button onclick={resetSession}>Reset Session</button> + <button onclick={triggerUpload}>Upload Database</button> +</div> +<input + type="file" + accept=".csv" + placeholder="Player database" + hidden + bind:this={fileInput} + onchange={handleFileChange} +/> diff --git a/src/lib/components/CourtList.svelte b/src/lib/components/CourtList.svelte new file mode 100644 index 0000000..b307980 --- /dev/null +++ b/src/lib/components/CourtList.svelte @@ -0,0 +1,14 @@ +<script> + import ToggleButton from "./ToggleButton.svelte"; + import { getCourts, toggleCourt } from "../stores/courts.svelte.js"; +</script> + +<div id="Court-list" class="grid-container"> + {#each getCourts() as court (court.id)} + <ToggleButton + label={court.name} + active={court.status} + onclick={() => toggleCourt(court.id)} + /> + {/each} +</div> diff --git a/src/lib/components/GameCard.svelte b/src/lib/components/GameCard.svelte new file mode 100644 index 0000000..7bdacb6 --- /dev/null +++ b/src/lib/components/GameCard.svelte @@ -0,0 +1,17 @@ +<script> + import TeamDisplay from "./TeamDisplay.svelte"; + + let { game } = $props(); + const reserved = $derived(game.teamOne.playerOne.firstName === "RESERVED"); +</script> + +<article> + <header>Court {game.id + 1}</header> + {#if reserved} + <h5>Reserved</h5> + {:else} + <TeamDisplay team={game.teamOne} /> + <hr /> + <TeamDisplay team={game.teamTwo} /> + {/if} +</article> diff --git a/src/lib/components/GameCard.test.js b/src/lib/components/GameCard.test.js new file mode 100644 index 0000000..076a409 --- /dev/null +++ b/src/lib/components/GameCard.test.js @@ -0,0 +1,32 @@ +// @vitest-environment jsdom +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/svelte"; +import GameCard from "./GameCard.svelte"; +import { createCourt, createRegular } from "../logic/players.js"; +import { + addBlockedGames, + addActiveGames, +} from "../logic/matchmaking.js"; + +describe("GameCard", () => { + it("renders 'Reserved' for a RESERVED placeholder game", () => { + const [game] = addBlockedGames([createCourt(2, false)]); + render(GameCard, { game }); + expect(screen.getByText("Reserved")).toBeInTheDocument(); + expect(screen.getByText("Court 3")).toBeInTheDocument(); + }); + + it("renders both teams' player names for a real match", () => { + const players = [ + createRegular(0, true, 1, 0, "Alice", "A"), + createRegular(1, true, 1, 0, "Bob", "B"), + createRegular(2, true, 1, 0, "Carl", "C"), + createRegular(3, true, 1, 0, "Dana", "D"), + ]; + const [game] = addActiveGames([createCourt(0, true)], players); + render(GameCard, { game }); + expect(screen.queryByText("Reserved")).not.toBeInTheDocument(); + expect(screen.getByText("Alice")).toBeInTheDocument(); + expect(screen.getByText("Dana")).toBeInTheDocument(); + }); +}); diff --git a/src/lib/components/GamesSection.svelte b/src/lib/components/GamesSection.svelte new file mode 100644 index 0000000..a2f4f55 --- /dev/null +++ b/src/lib/components/GamesSection.svelte @@ -0,0 +1,11 @@ +<script> + import GameCard from "./GameCard.svelte"; + import { getGames, getGameDescription } from "../stores/games.svelte.js"; +</script> + +<h1 class="title" id="Game-description">{getGameDescription()}</h1> +<div id="Game-list" class="grid-container"> + {#each getGames() as game (game.id)} + <GameCard {game} /> + {/each} +</div> diff --git a/src/lib/components/GuestList.svelte b/src/lib/components/GuestList.svelte new file mode 100644 index 0000000..2b597eb --- /dev/null +++ b/src/lib/components/GuestList.svelte @@ -0,0 +1,14 @@ +<script> + import ToggleButton from "./ToggleButton.svelte"; + import { getGuests, toggleGuest } from "../stores/guests.svelte.js"; +</script> + +<div id="Guest-list" class="grid-container"> + {#each getGuests() as guest (guest.id)} + <ToggleButton + label={guest.name} + active={guest.status} + onclick={() => toggleGuest(guest.id)} + /> + {/each} +</div> diff --git a/src/lib/components/ProposeConfirmControls.svelte b/src/lib/components/ProposeConfirmControls.svelte new file mode 100644 index 0000000..692ad56 --- /dev/null +++ b/src/lib/components/ProposeConfirmControls.svelte @@ -0,0 +1,8 @@ +<script> + import { propose, confirm } from "../stores/games.svelte.js"; +</script> + +<div class="grid"> + <button onclick={propose}>Propose</button> + <button onclick={confirm}>Confirm</button> +</div> diff --git a/src/lib/components/RegularList.svelte b/src/lib/components/RegularList.svelte new file mode 100644 index 0000000..dd8fb7a --- /dev/null +++ b/src/lib/components/RegularList.svelte @@ -0,0 +1,14 @@ +<script> + import ToggleButton from "./ToggleButton.svelte"; + import { getRegulars, toggleRegular } from "../stores/regulars.svelte.js"; +</script> + +<div id="Regular-list" class="grid-container"> + {#each getRegulars() as regular (regular.id)} + <ToggleButton + label={regular.name} + active={regular.status} + onclick={() => toggleRegular(regular.id)} + /> + {/each} +</div> diff --git a/src/lib/components/SkillFactorSelect.svelte b/src/lib/components/SkillFactorSelect.svelte new file mode 100644 index 0000000..bc882a7 --- /dev/null +++ b/src/lib/components/SkillFactorSelect.svelte @@ -0,0 +1,13 @@ +<script> + import { getSkillFactor, setSkillFactor } from "../stores/skillFactor.svelte.js"; +</script> + +<select + aria-label="State" + bind:value={() => String(getSkillFactor()), setSkillFactor} +> + <option value="10">All Levels</option> + <option value="5">2 Levels</option> + <option value="3">3 Levels</option> + <option value="1">Skill Based</option> +</select> diff --git a/src/lib/components/TeamDisplay.svelte b/src/lib/components/TeamDisplay.svelte new file mode 100644 index 0000000..e175446 --- /dev/null +++ b/src/lib/components/TeamDisplay.svelte @@ -0,0 +1,7 @@ +<script> + let { team } = $props(); +</script> + +<strong>{team.playerOne.firstName}</strong> {team.playerOne.lastName} +<br /><br /> +<strong>{team.playerTwo.firstName}</strong> {team.playerTwo.lastName} diff --git a/src/lib/components/TimerControls.svelte b/src/lib/components/TimerControls.svelte new file mode 100644 index 0000000..5983514 --- /dev/null +++ b/src/lib/components/TimerControls.svelte @@ -0,0 +1,39 @@ +<script> + import { + getDisplay, + getRunning, + start, + stopTimer, + changeMinutes, + } from "../stores/timer.svelte.js"; +</script> + +<fieldset id="timer"> + <button + title="Timer decrement" + disabled={getRunning()} + onclick={() => changeMinutes(-0.5)} + > + <i class="fa-solid fa-minus"></i> + </button> + <input + id="timer-display" + title="Time display" + type="text" + readonly + value={getDisplay()} + /> + <button + title="Timer increment" + disabled={getRunning()} + onclick={() => changeMinutes(0.5)} + > + <i class="fa-solid fa-plus"></i> + </button> + <button title="Timer start" onclick={() => start(true)}> + <i class="fa-solid fa-play"></i> + </button> + <button title="Timer stop" onclick={() => stopTimer()}> + <i class="fa-solid fa-stop"></i> + </button> +</fieldset> diff --git a/src/lib/components/TimerControls.test.js b/src/lib/components/TimerControls.test.js new file mode 100644 index 0000000..3176c17 --- /dev/null +++ b/src/lib/components/TimerControls.test.js @@ -0,0 +1,35 @@ +// @vitest-environment jsdom +import { describe, it, expect, afterEach } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/svelte"; +import TimerControls from "./TimerControls.svelte"; +import { stopTimer } from "../stores/timer.svelte.js"; + +describe("TimerControls", () => { + afterEach(() => { + stopTimer(); + }); + + it("shows the resting display for the default minutes", () => { + render(TimerControls); + expect(screen.getByTitle("Time display")).toHaveValue("12:00"); + }); + + it("disables inc/dec while running, and re-enables on stop", async () => { + render(TimerControls); + const startBtn = screen.getByTitle("Timer start"); + const stopBtn = screen.getByTitle("Timer stop"); + const incBtn = screen.getByTitle("Timer increment"); + const decBtn = screen.getByTitle("Timer decrement"); + + expect(incBtn).not.toBeDisabled(); + expect(decBtn).not.toBeDisabled(); + + await fireEvent.click(startBtn); + expect(incBtn).toBeDisabled(); + expect(decBtn).toBeDisabled(); + + await fireEvent.click(stopBtn); + expect(incBtn).not.toBeDisabled(); + expect(decBtn).not.toBeDisabled(); + }); +}); diff --git a/src/lib/components/ToggleButton.svelte b/src/lib/components/ToggleButton.svelte new file mode 100644 index 0000000..138aa15 --- /dev/null +++ b/src/lib/components/ToggleButton.svelte @@ -0,0 +1,7 @@ +<script> + let { label, active, onclick } = $props(); +</script> + +<button class:outline={!active} class:secondary={!active} {onclick}> + {label} +</button> diff --git a/src/lib/components/ToggleButton.test.js b/src/lib/components/ToggleButton.test.js new file mode 100644 index 0000000..cfbb90d --- /dev/null +++ b/src/lib/components/ToggleButton.test.js @@ -0,0 +1,31 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/svelte"; +import ToggleButton from "./ToggleButton.svelte"; + +describe("ToggleButton", () => { + it("renders the label", () => { + render(ToggleButton, { label: "Court 1", active: true, onclick: () => {} }); + expect(screen.getByText("Court 1")).toBeInTheDocument(); + }); + + it("shows the outline/secondary styling when inactive", () => { + render(ToggleButton, { label: "Court 1", active: false, onclick: () => {} }); + const button = screen.getByRole("button"); + expect(button).toHaveClass("outline", "secondary"); + }); + + it("has no outline/secondary styling when active", () => { + render(ToggleButton, { label: "Court 1", active: true, onclick: () => {} }); + const button = screen.getByRole("button"); + expect(button).not.toHaveClass("outline"); + expect(button).not.toHaveClass("secondary"); + }); + + it("calls onclick when clicked", async () => { + const onclick = vi.fn(); + render(ToggleButton, { label: "Court 1", active: true, onclick }); + await fireEvent.click(screen.getByRole("button")); + expect(onclick).toHaveBeenCalledOnce(); + }); +}); |
