go_games_collection/klondike_tui_test.go
2026-07-07 09:12:09 +03:00

200 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"regexp"
"strings"
"testing"
)
var ansiEscapeRe = regexp.MustCompile("\x1b\\[[0-9;]*m")
func stripANSI(s string) string {
return ansiEscapeRe.ReplaceAllString(s, "")
}
// TestKlondikeTableauAlignmentWithMixedCardWidths проверяет
// исправление бага: карта "10" (единственный ранг длиной в 2
// символа) без паддинга оказывалась короче остальных карт и
// закрытой клетки "[?]", из-за чего колонки правее в той же строке
// сдвигались и вертикальное выравнивание таблицы ломалось.
func TestKlondikeTableauAlignmentWithMixedCardWidths(t *testing.T) {
m := NewKlondikeModel()
m.game.Tableau[0] = []KlondikeCard{{Card: Card{Rank: Ace, Suit: Spades}, FaceUp: true}}
m.game.Tableau[1] = []KlondikeCard{{Card: Card{Rank: Ten, Suit: Hearts}, FaceUp: true}}
m.game.Tableau[2] = []KlondikeCard{{Card: Card{Rank: King, Suit: Clubs}, FaceUp: false}}
m.game.Tableau[3] = []KlondikeCard{{Card: Card{Rank: Queen, Suit: Diamonds}, FaceUp: true}}
m.game.Tableau[4] = []KlondikeCard{}
m.game.Tableau[5] = []KlondikeCard{}
m.game.Tableau[6] = []KlondikeCard{}
rendered := stripANSI(m.renderTableau())
lines := strings.Split(rendered, "\n")
if len(lines) < 2 {
t.Fatalf("ожидалось минимум 2 строки (заголовок + ряд карт), получено %d", len(lines))
}
headerWidth := len([]rune(lines[0]))
for i, line := range lines[1:] {
if w := len([]rune(line)); w != headerWidth {
t.Errorf("строка %d имеет ширину %d, отличную от заголовка (%d): %q", i+1, w, headerWidth, line)
}
}
}
func TestKlondikeModelDrawViaKeys(t *testing.T) {
m := NewKlondikeModel()
stockBefore := len(m.game.Stock)
next, _ := m.Update(key("d"))
m = next.(KlondikeModel)
if m.isError {
t.Fatalf("неожиданная ошибка: %s", m.message)
}
if len(m.game.Stock) != stockBefore-1 {
t.Fatalf("колода должна была уменьшиться на 1")
}
}
func TestKlondikeModelSelectAndMoveTableauToTableau(t *testing.T) {
m := NewKlondikeModel()
m.game.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}}
m.game.Tableau[1] = []KlondikeCard{{Card: c(Six, Spades), FaceUp: true}}
next, _ := m.Update(key("2"))
m = next.(KlondikeModel)
if m.isError {
t.Fatalf("неожиданная ошибка выбора: %s", m.message)
}
if m.selected != klondikePileRef(1) {
t.Fatalf("ожидался выбор колонны 1 (индекс), получено %v", m.selected)
}
next, _ = m.Update(key("1"))
m = next.(KlondikeModel)
if m.isError {
t.Fatalf("неожиданная ошибка хода: %s", m.message)
}
if len(m.game.Tableau[0]) != 2 {
t.Fatalf("ожидалось 2 карты в целевой колонне, получено %d", len(m.game.Tableau[0]))
}
if m.selected != klondikeNoSelection {
t.Errorf("выбор должен был сброситься после хода")
}
}
func TestKlondikeModelSelectWasteAndSendToFoundation(t *testing.T) {
m := NewKlondikeModel()
m.game.Waste = []Card{c(Ace, Hearts)}
next, _ := m.Update(key("0"))
m = next.(KlondikeModel)
if m.isError {
t.Fatalf("неожиданная ошибка: %s", m.message)
}
if m.selected != klondikeWasteRef {
t.Fatalf("ожидался выбор отбоя")
}
next, _ = m.Update(key("f"))
m = next.(KlondikeModel)
if m.isError {
t.Fatalf("неожиданная ошибка отправки на фундамент: %s", m.message)
}
if m.game.Foundations[Hearts] != int(Ace) {
t.Errorf("туз червей должен был оказаться на фундаменте")
}
}
func TestKlondikeModelEscClearsSelection(t *testing.T) {
m := NewKlondikeModel()
m.game.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}}
next, _ := m.Update(key("1"))
m = next.(KlondikeModel)
if m.selected == klondikeNoSelection {
t.Fatalf("ожидался выбор колонны 1")
}
next, _ = m.Update(key("esc"))
m = next.(KlondikeModel)
if m.selected != klondikeNoSelection {
t.Errorf("esc должен был сбросить выбор")
}
}
func TestKlondikeModelUndoViaKeys(t *testing.T) {
m := NewKlondikeModel()
m.game.Waste = []Card{c(Ace, Hearts)}
next, _ := m.Update(key("0"))
m = next.(KlondikeModel)
next, _ = m.Update(key("f"))
m = next.(KlondikeModel)
if m.game.Foundations[Hearts] != int(Ace) {
t.Fatalf("ход должен был примениться")
}
next, _ = m.Update(key("u"))
m = next.(KlondikeModel)
if m.isError {
t.Fatalf("неожиданная ошибка отмены: %s", m.message)
}
if m.game.Foundations[Hearts] != 0 {
t.Errorf("после отмены фундамент должен был вернуться в исходное состояние")
}
}
func TestKlondikeModelNewGameViaKeys(t *testing.T) {
m := NewKlondikeModel()
oldStock := m.game.Stock
next, _ := m.Update(key("n"))
m = next.(KlondikeModel)
if len(m.game.Stock)+28 != 52 {
t.Fatalf("новая партия должна быть корректно раздана")
}
same := true
for i := range oldStock {
if i >= len(m.game.Stock) || oldStock[i] != m.game.Stock[i] {
same = false
break
}
}
if same {
t.Errorf("новая партия должна была перетасовать колоду заново")
}
}
func TestKlondikeModelQReturnsToMenu(t *testing.T) {
m := NewKlondikeModel()
next, _ := m.Update(key("q"))
m = next.(KlondikeModel)
if !m.backToMenu {
t.Fatalf("ожидался флаг backToMenu после 'q'")
}
if m.quitting {
t.Fatalf("'q' не должен закрывать всё приложение")
}
}
func TestKlondikeModelWinShowsResultAndAllowsNewGame(t *testing.T) {
m := NewKlondikeModel()
for s := Clubs; s <= Spades; s++ {
m.game.Foundations[s] = int(King)
}
m.game.Foundations[Hearts] = int(Queen)
m.game.Waste = []Card{c(King, Hearts)}
next, _ := m.Update(key("0"))
m = next.(KlondikeModel)
next, _ = m.Update(key("f"))
m = next.(KlondikeModel)
if !m.game.Won {
t.Fatalf("партия должна была завершиться победой")
}
next, _ = m.Update(key("n"))
m = next.(KlondikeModel)
if m.game.Won {
t.Errorf("новая партия не должна была начаться выигранной")
}
}