From 3f30fa3eec9c2e2acf9a984ccefb9529a25e688a Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 7 Jul 2026 05:50:37 -0400 Subject: feat: add insert/remove room to context menu in admin web GUI to 'push' or 'pull' map rooms --- internal/world/grid_test.go | 181 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 internal/world/grid_test.go (limited to 'internal/world/grid_test.go') diff --git a/internal/world/grid_test.go b/internal/world/grid_test.go new file mode 100644 index 0000000..04d749c --- /dev/null +++ b/internal/world/grid_test.go @@ -0,0 +1,181 @@ +package world + +import ( + "os" + "path/filepath" + "strconv" + "testing" +) + +func writeGridTestRoom(t *testing.T, dir string, id int, body string) { + t.Helper() + rooms := filepath.Join(dir, "rooms") + if err := os.MkdirAll(rooms, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(rooms, strconv.Itoa(id)+".yaml"), []byte(body), 0o644); err != nil { + t.Fatal(err) + } +} + +func loadGridRoom(w *World) func(int) (*Room, bool) { + return func(id int) (*Room, bool) { + r, err := w.LoadRoom(id) + if err != nil { + return nil, false + } + return r, true + } +} + +func hasConflictKind(conflicts []GridConflict, kind string) bool { + for _, c := range conflicts { + if c.Kind == kind { + return true + } + } + return false +} + +func TestBuildGridConflictsClean(t *testing.T) { + dir := t.TempDir() + writeGridTestRoom(t, dir, 1, "exits:\n south: 3\n east: 2\n") + writeGridTestRoom(t, dir, 2, "exits:\n south: 4\n") + writeGridTestRoom(t, dir, 3, "exits:\n east: 4\n") + writeGridTestRoom(t, dir, 4, "name: corner\n") + + w := New(dir) + if conflicts := BuildGridConflicts(1, loadGridRoom(w)); len(conflicts) != 0 { + t.Errorf("expected no conflicts, got: %+v", conflicts) + } +} + +func TestBuildGridConflictsOverlap(t *testing.T) { + dir := t.TempDir() + writeGridTestRoom(t, dir, 1, "exits:\n south: 3\n east: 2\n") + writeGridTestRoom(t, dir, 2, "exits:\n south: 5\n") + writeGridTestRoom(t, dir, 3, "exits:\n east: 4\n") + writeGridTestRoom(t, dir, 4, "name: four\n") + writeGridTestRoom(t, dir, 5, "name: five\n") + + w := New(dir) + conflicts := BuildGridConflicts(1, loadGridRoom(w)) + if !hasConflictKind(conflicts, "overlap") { + t.Errorf("expected an overlap conflict, got: %+v", conflicts) + } +} + +func TestBuildGridConflictsTwist(t *testing.T) { + dir := t.TempDir() + writeGridTestRoom(t, dir, 1, "exits:\n south: 4\n east: 2\n") + writeGridTestRoom(t, dir, 2, "exits:\n east: 3\n") + writeGridTestRoom(t, dir, 4, "exits:\n east: 3\n") + writeGridTestRoom(t, dir, 3, "name: three\n") + + w := New(dir) + conflicts := BuildGridConflicts(1, loadGridRoom(w)) + if !hasConflictKind(conflicts, "twist") { + t.Errorf("expected a twist conflict, got: %+v", conflicts) + } +} + +func TestBuildGridConflictsDiagonalClean(t *testing.T) { + dir := t.TempDir() + writeGridTestRoom(t, dir, 1, "exits:\n northeast: 2\n") + writeGridTestRoom(t, dir, 2, "name: two\n exits:\n southwest: 1\n") + + w := New(dir) + if conflicts := BuildGridConflicts(1, loadGridRoom(w)); len(conflicts) != 0 { + t.Errorf("expected no conflicts for clean diagonal, got: %+v", conflicts) + } +} + +// TestBuildGridConflictsHypotheticalEdit models the insert-remove use case: +// the load closure returns edited copies of rooms reflecting a proposed edit, +// and BuildGridConflicts reports whether the resulting world is still clean. +// Here we model "insert a new room 5 between 1 and 2 on the east axis" and +// confirm the (clean) post-edit world has no conflicts. +func TestBuildGridConflictsHypotheticalInsert(t *testing.T) { + dir := t.TempDir() + writeGridTestRoom(t, dir, 1, "name: one\nexits:\n east: 2\n") + writeGridTestRoom(t, dir, 2, "name: two\nexits:\n west: 1\n") + w := New(dir) + + newRoom := &Room{Name: "new", Exits: map[ExitDir]ExitDef{ + East: {Room: 2}, West: {Room: 1}, + }} + load := func(id int) (*Room, bool) { + if id == 5 { + return newRoom, true + } + r, err := w.LoadRoom(id) + if err != nil { + return nil, false + } + copy := *r + if r.Exits != nil { + copy.Exits = make(map[ExitDir]ExitDef, len(r.Exits)) + for k, v := range r.Exits { + if id == 1 && k == East { + copy.Exits[k] = ExitDef{Room: 5, Condition: v.Condition, BlockedMessage: v.BlockedMessage, SetFlags: v.SetFlags, SetPlayerFlags: v.SetPlayerFlags, Hidden: v.Hidden, AlwaysBlocked: v.AlwaysBlocked} + continue + } + if id == 2 && k == West { + copy.Exits[k] = ExitDef{Room: 5, Condition: v.Condition, BlockedMessage: v.BlockedMessage, SetFlags: v.SetFlags, SetPlayerFlags: v.SetPlayerFlags, Hidden: v.Hidden, AlwaysBlocked: v.AlwaysBlocked} + continue + } + copy.Exits[k] = v + } + } + return ©, true + } + if conflicts := BuildGridConflicts(1, load); len(conflicts) != 0 { + t.Errorf("clean insert should produce no conflicts, got: %+v", conflicts) + } +} + +// TestBuildGridConflictsHypotheticalRemoveOverlap models "remove room 2 and +// pull the beyond-rooms back toward room 1". Layout: +// +// 1 east→2 east→3 south→6 (6 at (2,1,0)) +// 1 south→4 east→5 (5 at (1,1,0)) +// +// After removing 2 and rewiring 1.Exits[East]=3, 3.Exits[West]=1, the BFS +// re-places 3 at (1,0,0) and 6 at (1,1,0) — colliding with 5 which stays at +// (1,1,0). The helper must report an overlap without anything being written. +func TestBuildGridConflictsHypotheticalRemoveOverlap(t *testing.T) { + dir := t.TempDir() + writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n south: 4\n") + writeGridTestRoom(t, dir, 2, "exits:\n east: 3\n west: 1\n") + writeGridTestRoom(t, dir, 3, "exits:\n south: 6\n west: 2\n") + writeGridTestRoom(t, dir, 4, "exits:\n east: 5\n north: 1\n") + writeGridTestRoom(t, dir, 5, "exits:\n west: 4\n") + writeGridTestRoom(t, dir, 6, "exits:\n north: 3\n") + w := New(dir) + + load := func(id int) (*Room, bool) { + if id == 2 { + return nil, false // room removed + } + r, err := w.LoadRoom(id) + if err != nil { + return nil, false + } + rc := *r + rc.Exits = make(map[ExitDir]ExitDef, len(r.Exits)) + for k, v := range r.Exits { + rc.Exits[k] = v + } + if id == 1 { + rc.Exits[East] = ExitDef{Room: 3} + } + if id == 3 { + rc.Exits[West] = ExitDef{Room: 1} + } + return &rc, true + } + conflicts := BuildGridConflicts(1, load) + if !hasConflictKind(conflicts, "overlap") { + t.Errorf("expected an overlap from pulling 6 onto 5's cell, got: %+v", conflicts) + } +} -- cgit v1.2.3