clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

feat/app-decomposition
puzzletea / resolve / resolve_test.go
resolve_test.go 4.5K
1package resolve23import (4 "strings"5 "testing"67 "github.com/FelineStateMachine/puzzletea/game"8 "github.com/FelineStateMachine/puzzletea/games/lightsout"9 "github.com/FelineStateMachine/puzzletea/puzzle"10 "github.com/FelineStateMachine/puzzletea/registry"11)1213var testEntries = registry.Entries()1415func TestNormalize(t *testing.T) {16 tests := []struct {17 name string18 input string19 want string20 }{21 {"lowercase", "Sudoku", "sudoku"},22 {"all caps", "SUDOKU", "sudoku"},23 {"hyphens to spaces", "lights-out", "lights out"},24 {"underscores to spaces", "word_search", "word search"},25 }2627 for _, tt := range tests {28 t.Run(tt.name, func(t *testing.T) {29 if got := Normalize(tt.input); got != tt.want {30 t.Errorf("Normalize(%q) = %q, want %q", tt.input, got, tt.want)31 }32 })33 }34}3536func TestMode(t *testing.T) {37 sudokuEntry, ok := registry.Resolve("Sudoku")38 if !ok {39 t.Fatal("missing sudoku entry")40 }4142 t.Run("empty name returns default (first) mode", func(t *testing.T) {43 spawner, title, err := Mode(sudokuEntry, "")44 if err != nil {45 t.Fatal(err)46 }47 if spawner == nil || title != "Beginner" {48 t.Fatalf("default mode = (%v, %q), want Beginner", spawner != nil, title)49 }50 })5152 t.Run("case insensitive match", func(t *testing.T) {53 _, title, err := Mode(sudokuEntry, "easy")54 if err != nil {55 t.Fatal(err)56 }57 if title != "Easy" {58 t.Fatalf("title = %q, want Easy", title)59 }60 })6162 t.Run("unknown mode", func(t *testing.T) {63 _, _, err := Mode(sudokuEntry, "impossible")64 if err == nil || !strings.Contains(err.Error(), "unknown mode") {65 t.Fatalf("error = %v, want unknown mode", err)66 }67 })6869 t.Run("empty modes list", func(t *testing.T) {70 emptyEntry := registry.Entry{71 Definition: puzzle.Definition{Name: "Empty"},72 }73 _, _, err := Mode(emptyEntry, "any")74 if err == nil || !strings.Contains(err.Error(), "no available modes") {75 t.Fatalf("error = %v, want no available modes", err)76 }77 })78}7980func TestModeNames(t *testing.T) {81 entry, ok := registry.Resolve("Lights Out")82 if !ok {83 t.Fatal("missing lights out entry")84 }85 names := ModeNames(entry)86 expected := []string{"Easy", "Medium", "Hard", "Extreme"}87 if len(names) != len(expected) {88 t.Fatalf("len(ModeNames) = %d, want %d", len(names), len(expected))89 }90 for i, want := range expected {91 if names[i] != want {92 t.Fatalf("mode[%d] = %q, want %q", i, names[i], want)93 }94 }95}9697func TestRNGFromString(t *testing.T) {98 rng1 := RNGFromString("hello-world")99 rng2 := RNGFromString("hello-world")100 for i := range 10 {101 if v1, v2 := rng1.Uint64(), rng2.Uint64(); v1 != v2 {102 t.Fatalf("draw %d: got %d and %d for same seed", i, v1, v2)103 }104 }105}106107func TestSeededMode(t *testing.T) {108 s1, gt1, m1, err := SeededMode("test-seed-42", testEntries)109 if err != nil {110 t.Fatal(err)111 }112 s2, gt2, m2, err := SeededMode("test-seed-42", testEntries)113 if err != nil {114 t.Fatal(err)115 }116 if s1 == nil || s2 == nil || gt1 != gt2 || m1 != m2 {117 t.Fatalf("seeded mode mismatch: (%v,%q,%q) vs (%v,%q,%q)", s1 != nil, gt1, m1, s2 != nil, gt2, m2)118 }119120 _, _, _, err = SeededMode("test", nil)121 if err == nil {122 t.Fatal("expected error for empty entries")123 }124125 emptyEntries := []registry.Entry{{126 Definition: puzzle.Definition{Name: "Empty"},127 }}128 _, _, _, err = SeededMode("test", emptyEntries)129 if err == nil {130 t.Fatal("expected error when no seeded modes available")131 }132}133134func TestSeededModeStableOnEntryChange(t *testing.T) {135 seeds := []string{"alpha", "bravo", "charlie", "delta"}136 type selection struct{ gameType, mode string }137 original := make([]selection, len(seeds))138 for i, seed := range seeds {139 _, gt, m, err := SeededMode(seed, testEntries)140 if err != nil {141 t.Fatal(err)142 }143 original[i] = selection{gt, m}144 }145146 extended := append([]registry.Entry(nil), testEntries...)147 extended = append(extended, registry.Entry{148 Definition: puzzle.Definition{149 ID: puzzle.CanonicalGameID("Synthetic"),150 Name: "Synthetic",151 Modes: []puzzle.ModeDef{{ID: puzzle.CanonicalModeID("Easy"), Title: "Easy", Seeded: true}},152 },153 Modes: []registry.ModeEntry{{154 Definition: puzzle.ModeDef{ID: puzzle.CanonicalModeID("Easy"), Title: "Easy", Seeded: true},155 Spawner: lightsout.Modes[0].(game.Spawner),156 Seeded: lightsout.Modes[0].(game.SeededSpawner),157 }},158 })159160 for i, seed := range seeds {161 _, gt, m, err := SeededMode(seed, extended)162 if err != nil {163 t.Fatal(err)164 }165 if gt == "Synthetic" {166 continue167 }168 if gt != original[i].gameType || m != original[i].mode {169 t.Fatalf("seed %q changed from (%q,%q) to (%q,%q)", seed, original[i].gameType, original[i].mode, gt, m)170 }171 }172}