package main import "testing" func TestMinesweeperModelRevealViaKey(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) m.cursor = MinesweeperPos{Row: 4, Col: 4} next, _ := m.Update(key("enter")) m = next.(MinesweeperModel) if m.isError { t.Fatalf("неожиданная ошибка: %s", m.message) } if !m.game.Board[4][4].Revealed { t.Fatalf("клетка должна была открыться") } } func TestMinesweeperModelFlagViaKey(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) m.cursor = MinesweeperPos{Row: 4, Col: 4} next, _ := m.Update(key("f")) m = next.(MinesweeperModel) if m.isError { t.Fatalf("неожиданная ошибка: %s", m.message) } if !m.game.Board[4][4].Flagged { t.Fatalf("клетка должна была получить флаг") } next, _ = m.Update(key("f")) m = next.(MinesweeperModel) if m.game.Board[4][4].Flagged { t.Fatalf("флаг должен был сняться") } } func TestMinesweeperModelCursorMovement(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) m.cursor = MinesweeperPos{Row: 4, Col: 4} next, _ := m.Update(key("up")) m = next.(MinesweeperModel) if m.cursor.Row != 3 { t.Errorf("курсор должен был сдвинуться вверх") } next, _ = m.Update(key("right")) m = next.(MinesweeperModel) if m.cursor.Col != 5 { t.Errorf("курсор должен был сдвинуться вправо") } } func TestMinesweeperModelCursorClampsAtEdges(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) m.cursor = MinesweeperPos{Row: 0, Col: 0} next, _ := m.Update(key("up")) m = next.(MinesweeperModel) if m.cursor.Row != 0 { t.Errorf("курсор не должен был уйти за верхний край") } next, _ = m.Update(key("left")) m = next.(MinesweeperModel) if m.cursor.Col != 0 { t.Errorf("курсор не должен был уйти за левый край") } } func TestMinesweeperModelCannotRevealFlaggedCell(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) m.cursor = MinesweeperPos{Row: 4, Col: 4} next, _ := m.Update(key("f")) m = next.(MinesweeperModel) next, _ = m.Update(key("enter")) m = next.(MinesweeperModel) if !m.isError { t.Fatalf("ожидалась ошибка открытия помеченной клетки") } } func TestMinesweeperModelQReturnsToMenu(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) next, _ := m.Update(key("q")) m = next.(MinesweeperModel) if !m.backToMenu { t.Fatalf("ожидался флаг backToMenu после 'q'") } if m.quitting { t.Fatalf("'q' не должен закрывать всё приложение") } } func TestMinesweeperModelNewGameAfterResult(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) m.game.Result = &MinesweeperResult{Won: false} next, _ := m.Update(key("n")) m = next.(MinesweeperModel) if m.game.Result != nil { t.Errorf("новая партия не должна была начаться с результатом") } if m.game.Width != 9 || m.game.Height != 9 || m.game.MineCount != 10 { t.Errorf("новая партия должна была сохранить параметры поля") } } func TestMinesweeperModelSupportsAllPresets(t *testing.T) { presets := []struct{ w, h, mines int }{ {9, 9, 10}, {16, 16, 40}, {30, 16, 99}, } for _, p := range presets { m := NewMinesweeperModel(p.w, p.h, p.mines) if m.game.Width != p.w || m.game.Height != p.h || m.game.MineCount != p.mines { t.Errorf("некорректные параметры для пресета %+v: %+v", p, m.game) } } } func TestMinesweeperModelChordViaKeyOnRevealedCell(t *testing.T) { m := NewMinesweeperModel(5, 5, 2) m.game.minesPlaced = true m.game.Board[0][1].IsMine = true m.game.Board[1][0].IsMine = true for r := 0; r < 5; r++ { for c := 0; c < 5; c++ { if m.game.Board[r][c].IsMine { continue } n := 0 for _, nb := range msNeighbors(msp(r, c)) { if m.game.inBounds(nb) && m.game.Board[nb.Row][nb.Col].IsMine { n++ } } m.game.Board[r][c].AdjacentMines = n } } m.game.Board[0][0].Revealed = true m.game.RevealedCount++ m.game.ToggleFlag(msp(0, 1)) m.game.ToggleFlag(msp(1, 0)) m.cursor = msp(0, 0) next, _ := m.Update(key("enter")) m = next.(MinesweeperModel) if m.isError { t.Fatalf("неожиданная ошибка хорды через клавишу: %s", m.message) } if !m.game.Board[1][1].Revealed { t.Errorf("хорда через enter на открытой клетке должна была открыть соседа (1,1)") } } // TestMinesweeperModelFullAutoPlaythrough прогоняет TUI-модель через // последовательность нажатий на всех клетках поля, проверяя, что // путь через Update не паникует и партия завершается (тем или иным // исходом). func TestMinesweeperModelFullAutoPlaythrough(t *testing.T) { m := NewMinesweeperModel(9, 9, 10) for r := 0; r < 9 && m.game.Result == nil; r++ { for c := 0; c < 9 && m.game.Result == nil; c++ { m.cursor = MinesweeperPos{Row: r, Col: c} if m.game.Board[r][c].Revealed { continue } next, _ := m.Update(key("enter")) m = next.(MinesweeperModel) } } if m.game.Result == nil { t.Fatalf("партия должна была завершиться после перебора всех клеток") } }