149 lines
4.1 KiB
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")
|
|
}
|
|
}
|