package level import ( "os" "path/filepath" "strings" "testing" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/res" ) const sample = `{ "id": "test", "name": {"sv": "Test"}, "difficulty": 1, "destination": {"sv": "Mål"}, "grid": [ "#######", "#S..#P#", "#.#.#.#", "#.#...#", "#######" ], "trains": [{"platform": "P", "line": {"en": "A"}, "destination": {"en": "B"}, "correct": true}] }` func mustParse(t *testing.T, s string) *Level { t.Helper() l, err := Parse([]byte(s)) if err != nil { t.Fatal(err) } return l } func TestParseAndSolve(t *testing.T) { l := mustParse(t, sample) if l.Width() != 7 || l.Height() != 5 { t.Fatalf("storlek %dx%d", l.Width(), l.Height()) } if l.SpawnPoint() != (Point{1, 1}) { t.Fatalf("spawn %v", l.SpawnPoint()) } if !l.Solvable() { t.Fatal("banan ska vara lösbar") } if u := l.Unreachable(); len(u) != 0 { t.Fatalf("oväntat onåbara rutor: %v", u) } } func TestUnsolvableWhenWalledOff(t *testing.T) { l := mustParse(t, strings.Replace(sample, `"#.#...#"`, `"#.#.#.#"`, 1)) if l.Solvable() { t.Fatal("banan ska INTE vara lösbar när perrongen är inmurad") } if u := l.Unreachable(); len(u) != 1 || u[0] != (Point{5, 1}) { t.Fatalf("Unreachable = %v, vill ha [(5,1)]", u) } } func TestOutsideGridIsWall(t *testing.T) { l := mustParse(t, sample) if l.At(-1, 0).Passable() || l.At(0, 99).Passable() { t.Fatal("utanför kartan ska räknas som vägg") } } func TestValidationErrors(t *testing.T) { cases := map[string]string{ "ojämn rad": strings.Replace(sample, `"#.#.#.#"`, `"#.#.#.##"`, 1), "okänt tecken": strings.Replace(sample, `"#.#.#.#"`, `"#.#.#X#"`, 1), "två starter": strings.Replace(sample, `"#S..#P#"`, `"#S.S#P#"`, 1), "ingen start": strings.Replace(sample, `"#S..#P#"`, `"#...#P#"`, 1), "ingen perrong": strings.Replace(sample, `"#S..#P#"`, `"#S..#.#"`, 1), "inget rätt tåg": strings.Replace(sample, `"correct": true`, `"correct": false`, 1), "tåg vid fel perrong": strings.Replace(sample, `"platform": "P"`, `"platform": "p"`, 1), "saknar id": strings.Replace(sample, `"id": "test"`, `"id": ""`, 1), "trasig json": "{", } for name, src := range cases { if _, err := Parse([]byte(src)); err == nil { t.Errorf("%s: förväntade fel", name) } } } func TestTextFallback(t *testing.T) { tx := Text{"en": "Exit", "ja": "出口"} if tx.Get("ja") != "出口" || tx.Get("sv") != "Exit" { t.Fatal("fel fallback") } if (Text{"ja": "x"}).Get("sv") != "x" { t.Fatal("fallback till godtyckligt språk saknas") } if (Text{}).Get("sv") != "" { t.Fatal("tom text ska ge tom sträng") } } // TestAllShippedLevels är ett datadrivet test: varje bana under // resources/data/levels måste parsa, validera och vara lösbar. // Lägger man till en bana som inte går att klara faller bygget här. func TestAllShippedLevels(t *testing.T) { dir, err := res.Path("data", "levels") if err != nil { t.Fatal(err) } files, err := filepath.Glob(filepath.Join(dir, "*.json")) if err != nil { t.Fatal(err) } if len(files) == 0 { t.Fatalf("inga banor i %s", dir) } for _, f := range files { t.Run(filepath.Base(f), func(t *testing.T) { b, err := os.ReadFile(f) if err != nil { t.Fatal(err) } l, err := Parse(b) if err != nil { t.Fatal(err) } if want := strings.TrimSuffix(filepath.Base(f), ".json"); l.ID != want { t.Errorf("id %q matchar inte filnamnet %q", l.ID, want) } if !l.Solvable() { t.Error("banan går inte att klara från start") } if u := l.Unreachable(); len(u) > 0 { t.Errorf("onåbara interaktiva rutor: %v", u) } }) } }