games: the uno table opens its doors
Driven in a browser for the first time, which is where three bugs were. Every visit to /games/uno was a 500: the page was never added to the list server.go parses into the games template set, so render() answered "unknown page". The casino tests all call their handlers directly and never go through render(), so nothing saw it. TestEveryCasinoPageRenders now walks the mux and asks for every page the casino routes to. The play script hid the first card that lit up rather than the one you clicked, so playing any other playable card made an innocent card vanish. And on a phone the discard sized its box but not its card, which takes its size from --uno-h, so a full-size card hung out of a small hole and covered the colour in play. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
50
internal/web/games_render_test.go
Normal file
50
internal/web/games_render_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Every page the casino routes to must be in the games template set. This is not
|
||||
// a fussy test: uno shipped with its handler wired, its engine tested and its
|
||||
// template written, and every visit to the table answered "unknown page" with a
|
||||
// 500 — because the page was never added to the list server.go parses. Nothing
|
||||
// else caught it, since the other tests call the handlers straight and never go
|
||||
// through render(). Add a game, add it here.
|
||||
func TestEveryCasinoPageRenders(t *testing.T) {
|
||||
s := newCasino(t)
|
||||
|
||||
pages := []string{
|
||||
"/games",
|
||||
"/games/blackjack",
|
||||
"/games/hangman",
|
||||
"/games/solitaire",
|
||||
"/games/trivia",
|
||||
"/games/uno",
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
s.casinoRoutes(mux)
|
||||
|
||||
for _, path := range pages {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
mux.ServeHTTP(w, as(t, s, "reala", "GET", path, nil))
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("GET %s = %d, want 200 (body: %s)", path, w.Code, strings.TrimSpace(w.Body.String()))
|
||||
}
|
||||
// render() writes the 500 as a body and a page that fails halfway
|
||||
// through still comes back 200, so look at what was actually served.
|
||||
body := w.Body.String()
|
||||
if strings.Contains(body, "unknown page") {
|
||||
t.Fatalf("GET %s served the unknown-page error: the template is missing from the games set in server.go", path)
|
||||
}
|
||||
if !strings.Contains(body, "</html>") {
|
||||
t.Fatalf("GET %s did not render a whole page (%d bytes) — the template blew up mid-render", path, len(body))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled boo
|
||||
pages []string
|
||||
}{
|
||||
{"layout", []string{"_card"}, []string{"index", "channel", "weather", "bookmarks", "for-you", "status", "story"}},
|
||||
{"games_layout", []string{"_chipbar"}, []string{"games", "blackjack", "hangman", "solitaire", "trivia"}},
|
||||
{"games_layout", []string{"_chipbar"}, []string{"games", "blackjack", "hangman", "solitaire", "trivia", "uno"}},
|
||||
}
|
||||
tpls := make(map[string]*template.Template)
|
||||
for _, set := range sets {
|
||||
|
||||
@@ -1755,7 +1755,10 @@ html[data-phase="night"] {
|
||||
@media (max-width: 639px) {
|
||||
.pete-uno-hand { --uno-h: 5.2rem; --uno-w: 3.5rem; gap: 0.3rem; }
|
||||
.pete-uno-deck { --uno-h: 5.2rem; --uno-w: 3.5rem; }
|
||||
.pete-uno-discard { height: 5.2rem; width: 3.5rem; }
|
||||
/* The vars, not just the box: the card in the discard is a .pete-uno-card and
|
||||
takes its size from them, so sizing the box alone left a full-size card
|
||||
hanging out of a small hole, on top of the colour in play. */
|
||||
.pete-uno-discard { --uno-h: 5.2rem; --uno-w: 3.5rem; height: 5.2rem; width: 3.5rem; }
|
||||
.pete-uno-hand .pete-uno-card { padding: 0.22rem; }
|
||||
.pete-uno-oval { font-size: 1.15rem; }
|
||||
.pete-uno-seat { padding: 0.35rem 0.45rem; }
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -63,6 +63,8 @@
|
||||
var game = null; // the game as the server last described it
|
||||
var tier = "table";
|
||||
var pendingWild = -1; // the wild you clicked, waiting on a colour
|
||||
var played = -1; // the card you just played, so the script lifts that one out
|
||||
// of the hand and not merely the first one that lit up
|
||||
|
||||
var reduced = FX.reduced;
|
||||
function pace(ms) { return reduced ? 0 : ms; }
|
||||
@@ -399,9 +401,10 @@
|
||||
var from = seatAnchor(e.seat);
|
||||
// Your own card leaves the hand it was in, so the hand has to lose it
|
||||
// before the flight or the card is briefly in two places.
|
||||
if (e.seat === 0) {
|
||||
var live = handEl.querySelector('.pete-uno-card[data-on="1"][data-at]');
|
||||
if (e.seat === 0 && played >= 0) {
|
||||
var live = handEl.querySelector('.pete-uno-card[data-at="' + played + '"]');
|
||||
if (live) live.style.visibility = "hidden";
|
||||
played = -1;
|
||||
}
|
||||
bump(e.seat, e.left);
|
||||
return throwCard(node, from, discardEl, { index: n })
|
||||
@@ -555,6 +558,7 @@
|
||||
if (cancelWild) cancelWild.addEventListener("click", hideWild);
|
||||
|
||||
function move(body) {
|
||||
played = body.kind === "play" ? body.index : -1;
|
||||
send("/api/games/uno/move", body, gameMsgEl);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user