748 lines
25 KiB
Go
748 lines
25 KiB
Go
package main
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
tea "github.com/charmbracelet/bubbletea"
|
||
)
|
||
|
||
func TestMenuNavigation(t *testing.T) {
|
||
m := NewAppModel()
|
||
if m.state != stateMenu {
|
||
t.Fatalf("ожидалось начальное состояние stateMenu")
|
||
}
|
||
if len(m.options) != len(gameRegistry)+2 {
|
||
t.Fatalf("ожидалось %d пунктов меню, получено %d", len(gameRegistry)+2, len(m.options))
|
||
}
|
||
|
||
next, _ := m.Update(key("down"))
|
||
nm := next.(AppModel)
|
||
if nm.cursor != 1 {
|
||
t.Fatalf("ожидался курсор на позиции 1, получено %d", nm.cursor)
|
||
}
|
||
|
||
next, _ = nm.Update(key("up"))
|
||
nm = next.(AppModel)
|
||
if nm.cursor != 0 {
|
||
t.Fatalf("ожидался курсор на позиции 0 после возврата, получено %d", nm.cursor)
|
||
}
|
||
}
|
||
|
||
func TestMenuLeadsToStakeSettings(t *testing.T) {
|
||
m := NewAppModel()
|
||
// первый пункт меню — первая (и пока единственная) игра, которая
|
||
// использует ставки -> должен открыться экран настроек, а не игра
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateSettings {
|
||
t.Fatalf("ожидалось состояние stateSettings после выбора игры со ставками")
|
||
}
|
||
if m.activeGame != nil {
|
||
t.Fatalf("игра не должна была запуститься до подтверждения настроек")
|
||
}
|
||
}
|
||
|
||
func TestStakeSettingsAdjustAndStart(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("enter")) // входим в настройки
|
||
m = next.(AppModel)
|
||
|
||
initialCapital := m.settings.capital()
|
||
next, _ = m.Update(key("right")) // увеличиваем капитал на шаг пресета
|
||
m = next.(AppModel)
|
||
if m.settings.capital() == initialCapital {
|
||
t.Fatalf("значение капитала должно было измениться после [right]")
|
||
}
|
||
|
||
next, _ = m.Update(key("down")) // переходим к полю анте
|
||
m = next.(AppModel)
|
||
if m.settings.fieldCursor != 1 {
|
||
t.Fatalf("ожидался курсор на поле анте (1), получено %d", m.settings.fieldCursor)
|
||
}
|
||
|
||
chosenCapital := m.settings.capital()
|
||
chosenAnte := m.settings.ante()
|
||
chosenMult := m.settings.multiplier()
|
||
|
||
next, _ = m.Update(key("enter")) // подтверждаем и запускаем игру
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось состояние statePlaying после подтверждения настроек")
|
||
}
|
||
tm, ok := m.activeGame.(Model)
|
||
if !ok {
|
||
t.Fatalf("активная игра должна быть моделью Тонка")
|
||
}
|
||
if tm.game.StartingCapital != chosenCapital {
|
||
t.Errorf("стартовый капитал в игре (%d) не совпадает с выбранным (%d)",
|
||
tm.game.StartingCapital, chosenCapital)
|
||
}
|
||
if tm.game.AnteAmount != chosenAnte {
|
||
t.Errorf("анте в игре (%d) не совпадает с выбранным (%d)", tm.game.AnteAmount, chosenAnte)
|
||
}
|
||
if tm.game.TonkMultiplier != chosenMult {
|
||
t.Errorf("множитель в игре (%d) не совпадает с выбранным (%d)", tm.game.TonkMultiplier, chosenMult)
|
||
}
|
||
}
|
||
|
||
func TestStakeSettingsEscReturnsToMenu(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateSettings {
|
||
t.Fatalf("ожидался экран настроек")
|
||
}
|
||
next, _ = m.Update(key("esc"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("esc на экране настроек должен вернуть в меню")
|
||
}
|
||
}
|
||
|
||
func TestStakeSettingsCycleClampsAtBounds(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
|
||
// уводим значение влево много раз — не должно уйти за границу
|
||
// списка пресетов (индекс не может стать отрицательным)
|
||
for i := 0; i < 10; i++ {
|
||
next, _ = m.Update(key("left"))
|
||
m = next.(AppModel)
|
||
}
|
||
if m.settings.capitalIdx != 0 {
|
||
t.Errorf("индекс капитала должен был остановиться на 0, получено %d", m.settings.capitalIdx)
|
||
}
|
||
}
|
||
|
||
func TestMenuShowsRules(t *testing.T) {
|
||
m := NewAppModel()
|
||
// переходим к пункту "Правила" (сразу после списка игр)
|
||
for i := 0; i < len(gameRegistry); i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRulesMenu {
|
||
t.Fatalf("ожидалось состояние stateRulesMenu (подменю выбора игры)")
|
||
}
|
||
|
||
// в подменю правил курсор по умолчанию на первой игре (Тонк)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRules {
|
||
t.Fatalf("ожидалось состояние stateRules")
|
||
}
|
||
if !strings.Contains(m.rulesText, "ТОНК") {
|
||
t.Fatalf("текст правил должен содержать описание Тонка")
|
||
}
|
||
|
||
// esc из текста правил возвращает в подменю выбора игры, а не сразу в меню
|
||
next, _ = m.Update(key("esc"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRulesMenu {
|
||
t.Fatalf("ожидалось возвращение в stateRulesMenu после esc из текста правил")
|
||
}
|
||
|
||
// esc из подменю возвращает в главное меню
|
||
next, _ = m.Update(key("esc"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("ожидалось возвращение в stateMenu после esc из подменю правил")
|
||
}
|
||
}
|
||
|
||
func TestMenuShowsDurakRules(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < len(gameRegistry); i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // подменю правил
|
||
m = next.(AppModel)
|
||
|
||
next, _ = m.Update(key("down")) // выбираем вторую игру (Дурак)
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRules {
|
||
t.Fatalf("ожидалось состояние stateRules")
|
||
}
|
||
if !strings.Contains(m.rulesText, "ДУРАК") {
|
||
t.Fatalf("текст правил должен содержать описание Дурака, получено начало: %.50s", m.rulesText)
|
||
}
|
||
}
|
||
|
||
func TestMenuLeadsToPlayerCountForDurak(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down")) // второй пункт меню — Дурак
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlayerCount {
|
||
t.Fatalf("ожидалось состояние statePlayerCount после выбора Дурака")
|
||
}
|
||
if m.activeGame != nil {
|
||
t.Fatalf("игра не должна была запуститься до подтверждения числа игроков")
|
||
}
|
||
}
|
||
|
||
func TestPlayerCountAdjustAndStart(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // экран выбора числа игроков
|
||
m = next.(AppModel)
|
||
|
||
if m.playerCount.count != 4 {
|
||
t.Fatalf("ожидалось значение по умолчанию 4, получено %d", m.playerCount.count)
|
||
}
|
||
next, _ = m.Update(key("right"))
|
||
m = next.(AppModel)
|
||
if m.playerCount.count != 5 {
|
||
t.Fatalf("ожидалось 5 после [right], получено %d", m.playerCount.count)
|
||
}
|
||
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying после подтверждения числа игроков")
|
||
}
|
||
dm, ok := m.activeGame.(DurakModel)
|
||
if !ok {
|
||
t.Fatalf("активная игра должна быть моделью Дурака")
|
||
}
|
||
if len(dm.game.Players) != 5 {
|
||
t.Errorf("ожидалось 5 игроков в запущенной партии, получено %d", len(dm.game.Players))
|
||
}
|
||
}
|
||
|
||
func TestPlayerCountClampsAtBounds(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
|
||
for i := 0; i < 10; i++ {
|
||
next, _ = m.Update(key("left"))
|
||
m = next.(AppModel)
|
||
}
|
||
if m.playerCount.count != 2 {
|
||
t.Errorf("число игроков должно было остановиться на 2, получено %d", m.playerCount.count)
|
||
}
|
||
for i := 0; i < 10; i++ {
|
||
next, _ = m.Update(key("right"))
|
||
m = next.(AppModel)
|
||
}
|
||
if m.playerCount.count != 6 {
|
||
t.Errorf("число игроков должно было остановиться на 6, получено %d", m.playerCount.count)
|
||
}
|
||
}
|
||
|
||
func TestPlayerCountEscReturnsToMenu(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("esc"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("esc на экране выбора числа игроков должен вернуть в меню")
|
||
}
|
||
}
|
||
|
||
func TestDurakQKeyReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // экран числа игроков
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // запускаем Дурака
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying")
|
||
}
|
||
|
||
next, _ = m.Update(key("q"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("нажатие 'q' в игре должно вернуть в меню")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("приложение не должно закрываться")
|
||
}
|
||
}
|
||
|
||
func TestMenuShowsBlackjackRules(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < len(gameRegistry); i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // подменю правил
|
||
m = next.(AppModel)
|
||
|
||
// Блэкджек — третья запись в реестре (индекс 2)
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRules {
|
||
t.Fatalf("ожидалось состояние stateRules")
|
||
}
|
||
if !strings.Contains(m.rulesText, "БЛЭКДЖЕК") {
|
||
t.Fatalf("текст правил должен содержать описание Блэкджека, получено начало: %.50s", m.rulesText)
|
||
}
|
||
}
|
||
|
||
func TestMenuLeadsToBlackjackSettings(t *testing.T) {
|
||
m := NewAppModel()
|
||
// Блэкджек — третий пункт меню (после Тонка и Дурака)
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateBlackjackSettings {
|
||
t.Fatalf("ожидалось состояние stateBlackjackSettings после выбора Блэкджека")
|
||
}
|
||
if m.activeGame != nil {
|
||
t.Fatalf("игра не должна была запуститься до подтверждения настроек")
|
||
}
|
||
}
|
||
|
||
func TestBlackjackSettingsAdjustAndStart(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // экран настроек
|
||
m = next.(AppModel)
|
||
|
||
if m.blackjack.playerCount != 1 {
|
||
t.Fatalf("ожидалось значение по умолчанию 1 игрок, получено %d", m.blackjack.playerCount)
|
||
}
|
||
next, _ = m.Update(key("right"))
|
||
m = next.(AppModel)
|
||
if m.blackjack.playerCount != 2 {
|
||
t.Fatalf("ожидалось 2 после [right], получено %d", m.blackjack.playerCount)
|
||
}
|
||
|
||
next, _ = m.Update(key("down")) // поле капитала
|
||
m = next.(AppModel)
|
||
if m.blackjack.fieldCursor != 1 {
|
||
t.Fatalf("ожидался курсор на поле капитала (1), получено %d", m.blackjack.fieldCursor)
|
||
}
|
||
chosenPlayers := m.blackjack.playerCount
|
||
chosenCapital := m.blackjack.capital()
|
||
chosenBet := m.blackjack.bet()
|
||
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying после подтверждения настроек")
|
||
}
|
||
bm, ok := m.activeGame.(BlackjackModel)
|
||
if !ok {
|
||
t.Fatalf("активная игра должна быть моделью Блэкджека")
|
||
}
|
||
if len(bm.game.Players) != chosenPlayers {
|
||
t.Errorf("число игроков в игре (%d) не совпадает с выбранным (%d)", len(bm.game.Players), chosenPlayers)
|
||
}
|
||
if bm.game.StartingCapital != chosenCapital {
|
||
t.Errorf("капитал в игре (%d) не совпадает с выбранным (%d)", bm.game.StartingCapital, chosenCapital)
|
||
}
|
||
if bm.game.Bet != chosenBet {
|
||
t.Errorf("ставка в игре (%d) не совпадает с выбранной (%d)", bm.game.Bet, chosenBet)
|
||
}
|
||
}
|
||
|
||
func TestBlackjackSettingsEscReturnsToMenu(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("esc"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("esc на экране настроек Блэкджека должен вернуть в меню")
|
||
}
|
||
}
|
||
|
||
func TestBlackjackQKeyReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // настройки
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // запускаем игру
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying")
|
||
}
|
||
|
||
next, _ = m.Update(key("q"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("нажатие 'q' в игре должно вернуть в меню")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("приложение не должно закрываться")
|
||
}
|
||
}
|
||
|
||
func TestMenuShowsOneOhOneRules(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < len(gameRegistry); i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // подменю правил
|
||
m = next.(AppModel)
|
||
|
||
// 101 — четвёртая запись в реестре (индекс 3)
|
||
for i := 0; i < 3; i++ {
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRules {
|
||
t.Fatalf("ожидалось состояние stateRules")
|
||
}
|
||
if !strings.Contains(m.rulesText, "101") {
|
||
t.Fatalf("текст правил должен содержать описание 101, получено начало: %.50s", m.rulesText)
|
||
}
|
||
}
|
||
|
||
func TestMenuLeadsToPlayerCountForOneOhOne(t *testing.T) {
|
||
m := NewAppModel()
|
||
// 101 — четвёртый пункт меню
|
||
for i := 0; i < 3; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlayerCount {
|
||
t.Fatalf("ожидалось состояние statePlayerCount после выбора 101")
|
||
}
|
||
}
|
||
|
||
func TestOneOhOneQKeyReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 3; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // экран числа игроков
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // запускаем игру
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying")
|
||
}
|
||
|
||
next, _ = m.Update(key("q"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("нажатие 'q' в игре должно вернуть в меню")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("приложение не должно закрываться")
|
||
}
|
||
}
|
||
|
||
func TestMenuShowsThousandRules(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < len(gameRegistry); i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // подменю правил
|
||
m = next.(AppModel)
|
||
|
||
// 1000 — пятая запись в реестре (индекс 4)
|
||
for i := 0; i < 4; i++ {
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRules {
|
||
t.Fatalf("ожидалось состояние stateRules")
|
||
}
|
||
if !strings.Contains(m.rulesText, "ТЫСЯЧА") {
|
||
t.Fatalf("текст правил должен содержать описание Тысячи, получено начало: %.50s", m.rulesText)
|
||
}
|
||
}
|
||
|
||
func TestThousandStartsDirectlyWithoutSettingsScreen(t *testing.T) {
|
||
m := NewAppModel()
|
||
// 1000 — пятый пункт меню, запускается сразу (без экрана настроек)
|
||
for i := 0; i < 4; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось немедленное statePlaying для 1000, получено %v", m.state)
|
||
}
|
||
if _, ok := m.activeGame.(ThousandModel); !ok {
|
||
t.Fatalf("активная игра должна быть моделью 1000")
|
||
}
|
||
}
|
||
|
||
func TestThousandQKeyReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 4; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying")
|
||
}
|
||
|
||
next, _ = m.Update(key("q"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("нажатие 'q' в игре должно вернуть в меню")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("приложение не должно закрываться")
|
||
}
|
||
}
|
||
|
||
func TestMenuShowsKlondikeRules(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < len(gameRegistry); i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // подменю правил
|
||
m = next.(AppModel)
|
||
|
||
// Косынка — шестая запись в реестре (индекс 5)
|
||
for i := 0; i < 5; i++ {
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRules {
|
||
t.Fatalf("ожидалось состояние stateRules")
|
||
}
|
||
if !strings.Contains(m.rulesText, "КОСЫНКА") {
|
||
t.Fatalf("текст правил должен содержать описание Косынки, получено начало: %.50s", m.rulesText)
|
||
}
|
||
}
|
||
|
||
func TestKlondikeStartsDirectlyWithoutSettingsScreen(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 5; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось немедленное statePlaying для Косынки, получено %v", m.state)
|
||
}
|
||
if _, ok := m.activeGame.(KlondikeModel); !ok {
|
||
t.Fatalf("активная игра должна быть моделью Косынки")
|
||
}
|
||
}
|
||
|
||
func TestKlondikeQKeyReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 5; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying")
|
||
}
|
||
|
||
next, _ = m.Update(key("q"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("нажатие 'q' в игре должно вернуть в меню")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("приложение не должно закрываться")
|
||
}
|
||
}
|
||
|
||
func TestMenuShowsCheckersRules(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < len(gameRegistry); i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // подменю правил
|
||
m = next.(AppModel)
|
||
|
||
// Шашки — седьмая запись в реестре (индекс 6)
|
||
for i := 0; i < 6; i++ {
|
||
next, _ = m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateRules {
|
||
t.Fatalf("ожидалось состояние stateRules")
|
||
}
|
||
if !strings.Contains(m.rulesText, "ШАШКИ") {
|
||
t.Fatalf("текст правил должен содержать описание Шашек, получено начало: %.50s", m.rulesText)
|
||
}
|
||
}
|
||
|
||
func TestCheckersLeadsToDifficultySettings(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 6; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != stateCheckersSettings {
|
||
t.Fatalf("ожидалось состояние stateCheckersSettings для Шашек, получено %v", m.state)
|
||
}
|
||
if m.activeGame != nil {
|
||
t.Fatalf("игра не должна была запуститься до подтверждения уровня сложности")
|
||
}
|
||
}
|
||
|
||
func TestCheckersDifficultySelectionAndStart(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 6; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // экран выбора сложности
|
||
m = next.(AppModel)
|
||
if m.checkers.levelIdx != 0 {
|
||
t.Fatalf("ожидался уровень по умолчанию 0 (Простой), получено %d", m.checkers.levelIdx)
|
||
}
|
||
|
||
next, _ = m.Update(key("down")) // переключаемся на "Сильный"
|
||
m = next.(AppModel)
|
||
if m.checkers.levelIdx != 1 {
|
||
t.Fatalf("ожидался уровень 1 (Сильный) после переключения, получено %d", m.checkers.levelIdx)
|
||
}
|
||
|
||
next, _ = m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying после подтверждения уровня")
|
||
}
|
||
cm, ok := m.activeGame.(CheckersModel)
|
||
if !ok {
|
||
t.Fatalf("активная игра должна быть моделью Шашек")
|
||
}
|
||
if cm.bot.Depth != CheckersDifficultyHard {
|
||
t.Errorf("ожидалась глубина бота %d (Сильный), получено %d", CheckersDifficultyHard, cm.bot.Depth)
|
||
}
|
||
}
|
||
|
||
func TestCheckersDifficultyEscReturnsToMenu(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 6; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("esc"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("esc на экране сложности должен вернуть в меню")
|
||
}
|
||
}
|
||
|
||
func TestCheckersQKeyReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewAppModel()
|
||
for i := 0; i < 6; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, _ := m.Update(key("enter")) // экран сложности
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // запускаем игру
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying")
|
||
}
|
||
|
||
next, _ = m.Update(key("q"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("нажатие 'q' в игре должно вернуть в меню")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("приложение не должно закрываться")
|
||
}
|
||
}
|
||
|
||
func TestMenuQuit(t *testing.T) {
|
||
m := NewAppModel()
|
||
// последний пункт меню — "Выход"
|
||
for i := 0; i < len(m.options)-1; i++ {
|
||
next, _ := m.Update(key("down"))
|
||
m = next.(AppModel)
|
||
}
|
||
next, cmd := m.Update(key("enter"))
|
||
m = next.(AppModel)
|
||
if !m.quitting {
|
||
t.Fatalf("ожидался флаг quitting после выбора пункта 'Выход'")
|
||
}
|
||
if cmd == nil {
|
||
t.Fatalf("ожидалась команда tea.Quit")
|
||
}
|
||
}
|
||
|
||
func TestGameQKeyReturnsToMenuNotQuitsApp(t *testing.T) {
|
||
m := NewAppModel()
|
||
next, _ := m.Update(key("enter")) // открываем настройки ставки
|
||
m = next.(AppModel)
|
||
next, _ = m.Update(key("enter")) // подтверждаем настройки и запускаем Тонк
|
||
m = next.(AppModel)
|
||
if m.state != statePlaying {
|
||
t.Fatalf("ожидалось statePlaying")
|
||
}
|
||
|
||
next, cmd := m.Update(key("q"))
|
||
m = next.(AppModel)
|
||
if m.state != stateMenu {
|
||
t.Fatalf("нажатие 'q' в игре должно вернуть в меню, а не закрыть приложение")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("приложение не должно закрываться при возврате в меню")
|
||
}
|
||
if cmd != nil {
|
||
// допустима nil-команда — важно лишь, что это не tea.Quit
|
||
_ = cmd
|
||
}
|
||
}
|
||
|
||
func TestAppModelImplementsTeaModel(t *testing.T) {
|
||
var _ tea.Model = AppModel{}
|
||
}
|