Files
Pete/internal/web/who_map_test.go

149 lines
4.1 KiB
Go

package web
import (
"encoding/json"
"strings"
"testing"
"pete/internal/storage"
"time"
)
// fogMap is the shape gogobee sends: two visited rooms (entry, elite), and two
// frontier rooms reachable one hop out — n4 is really the boss but arrives with
// its kind withheld as "unknown". n3 sits behind a perception-locked door, n4
// behind a key-locked one.
func fogMap() *whoMap {
return &whoMap{
ZoneID: "ossuary",
CurrentNode: "n2",
Visited: []string{"n1", "n2"},
Nodes: []whoMapNode{
{ID: "n1", Kind: "entry"},
{ID: "n2", Kind: "elite"},
{ID: "n3", Kind: "unknown"},
{ID: "n4", Kind: "unknown"},
},
Edges: []whoMapEdge{
{From: "n1", To: "n2", Lock: ""},
{From: "n1", To: "n3", Lock: "perception_check"},
{From: "n2", To: "n4", Lock: "key_required"},
},
}
}
func TestBuildMapView_Layout(t *testing.T) {
v := buildMapView(fogMap())
if v == nil {
t.Fatal("nil view for a non-empty map")
}
if len(v.Nodes) != 4 {
t.Fatalf("want 4 nodes, got %d", len(v.Nodes))
}
// Depth places entry in column 0, its two successors in column 1, the boss
// frontier in column 2 — a left-to-right dungeon.
var n1, n2, n4 mapNode
for _, n := range v.Nodes {
switch {
case n.Kind == "entry":
n1 = n
case n.Kind == "elite":
n2 = n
case n.Kind == "unknown" && n.X > n1.X+mapColGap:
n4 = n // the deepest unknown is n4
}
}
if !(n1.X < n2.X && n2.X < n4.X) {
t.Errorf("columns not left-to-right: entry=%d elite=%d frontier=%d", n1.X, n2.X, n4.X)
}
// The boss frontier must NOT reveal its kind or its glyph.
if n4.Kind != "unknown" || n4.Glyph != "?" {
t.Errorf("frontier boss leaked: kind=%q glyph=%q", n4.Kind, n4.Glyph)
}
if n2.Current != true {
t.Errorf("current node n2 (elite) should be marked current")
}
if n1.Current {
t.Errorf("entry is not the current node")
}
// Edges: three total, the two locked ones dashed, the visited-visited one open.
if len(v.Edges) != 3 {
t.Fatalf("want 3 edges, got %d", len(v.Edges))
}
locked := 0
for _, e := range v.Edges {
if e.Locked {
locked++
}
}
if locked != 2 {
t.Errorf("want 2 locked doors (perception, key), got %d", locked)
}
}
func TestBuildMapView_EmptyIsNil(t *testing.T) {
if buildMapView(nil) != nil {
t.Error("nil map should give nil view")
}
if buildMapView(&whoMap{ZoneID: "x"}) != nil {
t.Error("map with no nodes should give nil view")
}
}
// publicDetailWithMap is publicDetail plus a fog-of-war map, the way the roster
// push carries both.
func publicDetailWithMap(t *testing.T) json.RawMessage {
t.Helper()
raw, err := json.Marshal(map[string]any{
"hp_current": 30,
"hp_max": 42,
"armor_class": 17,
"abilities": [6]int{16, 14, 15, 10, 12, 8},
"modifiers": [6]int{3, 2, 2, 0, 1, -1},
"supplies": 8,
"room": "2 / 4",
"map": fogMap(),
})
if err != nil {
t.Fatal(err)
}
return raw
}
// TestWhoMapRenders drives the real template with a map present and asserts the
// SVG lands, the current room is marked, and the fog holds: the frontier boss
// is drawn as an unexplored room, never as a boss.
func TestWhoMapRenders(t *testing.T) {
s, _ := newAdvServer(t, "tok")
s.auth = &Authenticator{secret: []byte("test-secret-key-at-least-16")}
now := time.Now().Unix()
e := entry("tok-josie", "Josie", "expedition", "holymachina")
e.Detail = publicDetailWithMap(t)
if w := postRoster(t, s, "tok", rosterPush{SnapshotAt: now, Adventurers: []storage.RosterEntry{e}}); w.Code != 200 {
t.Fatalf("seed roster = %d", w.Code)
}
w := getWho(t, s, "tok-josie", "")
if w.Code != 200 {
t.Fatalf("who = %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, want := range []string{"The map", "map-svg", "map-node-current", "map-edge-locked"} {
if !strings.Contains(body, want) {
t.Errorf("map render missing %q", want)
}
}
// The elite room glyph shows (visited); the boss glyph must not (the boss is
// only reachable as an unrevealed frontier door).
if !strings.Contains(body, "◆") {
t.Error("visited elite room should show its glyph")
}
if strings.Contains(body, "♛") {
t.Error("fog leak: the boss glyph rendered for an unexplored frontier room")
}
}