go_games_collection/menu_test.go
2026-07-04 13:51:04 +03:00

748 lines
25 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 (
"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{}
}