513 lines
21 KiB
Go
513 lines
21 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestCorpseStarchModelRequisitionViaKey(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
next, _ := m.Update(key("1"))
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.Starch != 1 {
|
||
t.Fatalf("ожидался запас 1 после нажатия [1], получено %v", m.game.Starch)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelDipActionHiddenDuringIncursion(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.TotalEarned = corpseStarchUnlockSump // открываем отстойник
|
||
|
||
m.game.ChaosIncursionUntil = m.game.LastTick.Add(time.Hour)
|
||
during := m.buildActions(m.game.LastTick)
|
||
|
||
for _, a := range during {
|
||
if a.Label == T("corpsestarch.action.dip") {
|
||
t.Errorf("окунание меча не должно предлагаться во время вторжения — иначе новая Скверна могла бы перезапустить уже идущее вторжение")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelDipActionReappearsAfterIncursionEnds(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.TotalEarned = corpseStarchUnlockSump
|
||
m.game.ChaosIncursionUntil = time.Time{} // вторжения нет
|
||
|
||
actions := m.buildActions(m.game.LastTick)
|
||
found := false
|
||
for _, a := range actions {
|
||
if a.Label == T("corpsestarch.action.dip") {
|
||
found = true
|
||
}
|
||
}
|
||
if !found {
|
||
t.Errorf("окунание меча должно быть доступно вне вторжения")
|
||
}
|
||
}
|
||
|
||
// TestWarpBreachDuringIncursionCannotBeTriggeredAgain — регрессия:
|
||
// раньше окунание меча оставалось доступным даже во время уже
|
||
// идущего вторжения, и если бросок снова достигал порога Скверны,
|
||
// triggerWarpBreach вызывался повторно поверх уже активного
|
||
// вторжения, сбрасывая его таймер (ChaosIncursionUntil) и прогресс
|
||
// защиты (ChaosDefendClicks) игрока. Теперь действие недоступно, пока
|
||
// идёт вторжение (см. buildActions), так что до самого движка такой
|
||
// повторный вызов дойти не может — но зафиксируем и в движке, что
|
||
// вызов triggerWarpBreach во время уже идущего вторжения продлевает
|
||
// его, а не более тонко сохраняет прогресс, чтобы явно видеть, какое
|
||
// именно поведение предотвращает интерфейс.
|
||
func TestWarpBreachDuringIncursionResetsProgressAtEngineLevel(t *testing.T) {
|
||
g := NewCorpseStarchGame(time.Now())
|
||
now := g.LastTick
|
||
g.ChaosIncursionUntil = now.Add(time.Second) // вторжение вот-вот закончится
|
||
g.ChaosDefendClicks = 5 // накопленный прогресс защиты
|
||
|
||
g.triggerWarpBreach(now)
|
||
|
||
if g.ChaosDefendClicks != 0 {
|
||
t.Errorf("на уровне движка triggerWarpBreach действительно обнуляет прогресс защиты (%d) — поэтому интерфейс и не должен допускать повторный вызов во время вторжения", g.ChaosDefendClicks)
|
||
}
|
||
if !g.ChaosIncursionUntil.After(now.Add(time.Second)) {
|
||
t.Errorf("на уровне движка повторный вызов продлевает вторжение заново — поэтому интерфейс блокирует окунание меча, пока вторжение идёт")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelBuyServitorActionHiddenDuringIncursion(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.TotalEarned = corpseStarchUnlockServitors // открываем раздел сервиторов
|
||
|
||
m.game.ChaosIncursionUntil = m.game.LastTick.Add(time.Hour)
|
||
during := m.buildActions(m.game.LastTick)
|
||
|
||
for _, a := range during {
|
||
if strings.Contains(a.Label, T("corpsestarch.servitor.skull")) {
|
||
t.Errorf("покупка сервитора не должна предлагаться во время вторжения")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelDefendActionAppearsDuringIncursion(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
before := len(m.buildActions(m.game.LastTick))
|
||
|
||
m.game.ChaosIncursionUntil = m.game.LastTick.Add(1)
|
||
after := m.buildActions(m.game.LastTick)
|
||
if len(after) != before+1 {
|
||
t.Fatalf("ожидалось на одно действие больше во время вторжения, было %d, стало %d", before, len(after))
|
||
}
|
||
if after[len(after)-1].Label != Tf("corpsestarch.action.defend", 0) {
|
||
t.Errorf("последнее действие должно быть 'отбиваться', получено: %q", after[len(after)-1].Label)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelDefendActionIncrementsCounterViaKey(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.ChaosIncursionUntil = time.Now().Add(time.Hour)
|
||
actions := m.buildActions(time.Now())
|
||
defendKey := fmt.Sprintf("%d", len(actions))
|
||
|
||
next, _ := m.Update(key(defendKey))
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.ChaosDefendClicks != 1 {
|
||
t.Errorf("ожидался счётчик защиты 1 после нажатия, получено %d", m.game.ChaosDefendClicks)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelWarpBreachVisibleInView(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.Starch = 1000
|
||
m.game.ServitorCounts[0] = 10
|
||
m.game.DipChainsword(m.game.LastTick, 0.95)
|
||
m.game.DipChainsword(m.game.LastTick, 0.95)
|
||
m.game.DipChainsword(m.game.LastTick, 0.95)
|
||
|
||
view := m.View()
|
||
normalized := strings.Join(strings.Fields(view), " ")
|
||
wantLog := strings.Join(strings.Fields(T("corpsestarch.log.warp_breach")), " ")
|
||
if !strings.Contains(normalized, wantLog) {
|
||
t.Errorf("экран должен был показать сообщение о варп-выбросе в журнале")
|
||
}
|
||
if !strings.Contains(view, "ВТОРЖЕНИЕ ХАОСА") {
|
||
t.Errorf("экран должен был показать баннер активного вторжения")
|
||
}
|
||
if m.game.ServitorCounts[0] != 5 {
|
||
t.Errorf("сервиторы должны были уполовиниться, получено %d", m.game.ServitorCounts[0])
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelActionsExpandAsUnlocked(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
if len(m.buildActions(time.Now())) != 1 {
|
||
t.Fatalf("в начале должно быть только 1 действие (реквизиция)")
|
||
}
|
||
|
||
m.game.TotalEarned = corpseStarchUnlockServitors
|
||
if len(m.buildActions(time.Now())) != 2 {
|
||
t.Fatalf("после открытия сервиторов должно быть 2 действия")
|
||
}
|
||
|
||
m.game.TotalEarned = corpseStarchUnlockMissions
|
||
actions := m.buildActions(time.Now())
|
||
if len(actions) != 8 {
|
||
t.Fatalf("ожидалось 8 действий при полностью открытых (кроме 3 тира) разделах, получено %d", len(actions))
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelBuyServitorViaKey(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.TotalEarned = corpseStarchUnlockServitors
|
||
m.game.Starch = 100
|
||
|
||
next, _ := m.Update(key("2"))
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.ServitorCounts[0] != 1 {
|
||
t.Fatalf("ожидалась покупка сервочерпа, получено count=%d", m.game.ServitorCounts[0])
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelTickAccruesIncome(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.ServitorCounts[0] = 10
|
||
past := m.game.LastTick.Add(2 * time.Second)
|
||
|
||
next, _ := m.Update(corpseStarchTickMsg{At: past})
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.Starch != 10 {
|
||
t.Errorf("ожидался запас 10 после тика на 2 секунды при 5/сек, получено %v", m.game.Starch)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelResetRequestShowsConfirmation(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
next, _ := m.Update(key("N"))
|
||
m = next.(CorpseStarchModel)
|
||
if !m.confirmingReset {
|
||
t.Fatalf("ожидался запрос подтверждения после нажатия N")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelResetCancelKeepsProgress(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.Starch = 500
|
||
m.game.TotalEarned = 500
|
||
|
||
next, _ := m.Update(key("N"))
|
||
m = next.(CorpseStarchModel)
|
||
next, _ = m.Update(key("n"))
|
||
m = next.(CorpseStarchModel)
|
||
|
||
if m.confirmingReset {
|
||
t.Errorf("запрос подтверждения должен был закрыться после отмены")
|
||
}
|
||
if m.game.Starch != 500 {
|
||
t.Errorf("прогресс не должен был сброситься при отмене, получено Starch=%v", m.game.Starch)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelResetConfirmWipesProgress(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.Starch = 500
|
||
m.game.TotalEarned = 500
|
||
|
||
next, _ := m.Update(key("N"))
|
||
m = next.(CorpseStarchModel)
|
||
next, _ = m.Update(key("y"))
|
||
m = next.(CorpseStarchModel)
|
||
|
||
if m.confirmingReset {
|
||
t.Errorf("запрос подтверждения должен был закрыться после подтверждения")
|
||
}
|
||
if m.game.Starch != 0 || m.game.TotalEarned != 0 {
|
||
t.Errorf("прогресс должен был сброситься, получено Starch=%v TotalEarned=%v", m.game.Starch, m.game.TotalEarned)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelChaosOfferAcceptStartsRebirth(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.ChaosMarks = corpseStarchChaosMarksForOffer
|
||
|
||
view := m.View()
|
||
if !strings.Contains(view, T("corpsestarch.chaos.offer.title")) {
|
||
t.Fatalf("экран должен был показать предложение Хаоса")
|
||
}
|
||
|
||
next, _ := m.Update(key("y"))
|
||
m = next.(CorpseStarchModel)
|
||
if !m.game.InRebirth(m.game.LastTick) && m.game.RebirthUntil.IsZero() {
|
||
t.Errorf("принятие предложения должно было начать перерождение")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelChaosOfferDeclineResetsMarks(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.ChaosMarks = corpseStarchChaosMarksForOffer
|
||
starchBefore := m.game.Starch
|
||
|
||
next, _ := m.Update(key("n"))
|
||
m = next.(CorpseStarchModel)
|
||
|
||
if m.game.ChaosMarks != 0 {
|
||
t.Errorf("отказ должен был сбросить счётчик меток, получено %d", m.game.ChaosMarks)
|
||
}
|
||
if m.game.Starch <= starchBefore {
|
||
t.Errorf("отказ должен был дать награду, было %v, стало %v", starchBefore, m.game.Starch)
|
||
}
|
||
if m.game.ChaosOfferPending() {
|
||
t.Errorf("предложение не должно оставаться активным после отказа")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelActionsIgnoredDuringChaosOffer(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.ChaosMarks = corpseStarchChaosMarksForOffer
|
||
starchBefore := m.game.Starch
|
||
|
||
next, _ := m.Update(key("1")) // обычная реквизиция не должна сработать
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.Starch != starchBefore {
|
||
t.Errorf("обычные действия не должны срабатывать, пока предложение Хаоса не разрешено")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelRebirthShowsHintForQuitAndNewGame(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.RebirthUntil = m.game.LastTick.Add(time.Hour)
|
||
|
||
view := m.View()
|
||
if !strings.Contains(view, "[q]") || !strings.Contains(view, "[N]") {
|
||
t.Errorf("экран перерождения должен был показывать подсказки [q] и [N], получено:\n%s", view)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelNKeyDuringRebirthOpensConfirmation(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.RebirthUntil = m.game.LastTick.Add(time.Hour)
|
||
|
||
next, _ := m.Update(key("N"))
|
||
m = next.(CorpseStarchModel)
|
||
if !m.confirmingReset {
|
||
t.Fatalf("клавиша N во время перерождения должна была открыть подтверждение сброса")
|
||
}
|
||
|
||
view := m.View()
|
||
if !strings.Contains(view, T("corpsestarch.confirm_reset")) {
|
||
t.Errorf("экран подтверждения должен был показаться поверх экрана перерождения")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelConfirmNewGameDuringRebirthActuallyResets(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.RebirthUntil = m.game.LastTick.Add(time.Hour)
|
||
m.game.Starch = 999
|
||
|
||
next, _ := m.Update(key("N"))
|
||
m = next.(CorpseStarchModel)
|
||
next, _ = m.Update(key("y"))
|
||
m = next.(CorpseStarchModel)
|
||
|
||
if m.confirmingReset {
|
||
t.Errorf("подтверждение должно было закрыться после ответа")
|
||
}
|
||
if m.game.InRebirth(time.Now()) {
|
||
t.Errorf("новая партия не должна была унаследовать перерождение старой")
|
||
}
|
||
if m.game.Starch != 0 {
|
||
t.Errorf("новая партия должна была начаться с чистого счёта, получено %v", m.game.Starch)
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelRebirthBlocksAllActionsExceptQuit(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.RebirthUntil = m.game.LastTick.Add(time.Hour)
|
||
starchBefore := m.game.Starch
|
||
|
||
next, _ := m.Update(key("1"))
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.Starch != starchBefore {
|
||
t.Errorf("во время перерождения обычные действия не должны срабатывать")
|
||
}
|
||
|
||
view := m.View()
|
||
if !strings.Contains(view, "ПЕРЕРОЖДЕНИЕ") {
|
||
t.Errorf("экран должен был показать статус перерождения")
|
||
}
|
||
|
||
next, _ = m.Update(key("q"))
|
||
m = next.(CorpseStarchModel)
|
||
if !m.backToMenu {
|
||
t.Errorf("клавиша q должна была сработать даже во время перерождения (сохранение и выход)")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelResetActionsIgnoredWhileConfirming(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
starchBefore := m.game.Starch
|
||
|
||
next, _ := m.Update(key("N"))
|
||
m = next.(CorpseStarchModel)
|
||
// нажатие "1" (реквизиция) не должно срабатывать, пока ждём подтверждения
|
||
next, _ = m.Update(key("1"))
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.Starch != starchBefore {
|
||
t.Errorf("обычные действия не должны выполняться во время запроса подтверждения")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelQReturnsToMenu(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
next, _ := m.Update(key("q"))
|
||
m = next.(CorpseStarchModel)
|
||
if !m.backToMenu {
|
||
t.Fatalf("ожидался флаг backToMenu после 'q'")
|
||
}
|
||
if m.quitting {
|
||
t.Fatalf("'q' не должен закрывать всё приложение")
|
||
}
|
||
}
|
||
|
||
func TestCorpseStarchModelMissionFlowViaKeys(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.TotalEarned = corpseStarchUnlockMissions
|
||
m.game.Starch = 0
|
||
|
||
actions := m.buildActions(time.Now())
|
||
missionActionIdx := -1
|
||
for i, a := range actions {
|
||
if a.Label == Tf("corpsestarch.action.start_mission", T("corpsestarch.mission.vermin"), formatDuration(corpseStarchMissionDefs[0].Duration)) {
|
||
missionActionIdx = i
|
||
break
|
||
}
|
||
}
|
||
if missionActionIdx == -1 {
|
||
t.Fatalf("не нашли действие запуска первой миссии среди %d действий", len(actions))
|
||
}
|
||
|
||
next, _ := m.Update(key(string(rune('1' + missionActionIdx))))
|
||
m = next.(CorpseStarchModel)
|
||
if m.game.ActiveMission == nil {
|
||
t.Fatalf("миссия должна была начаться")
|
||
}
|
||
|
||
finished := m.game.ActiveMission.StartedAt.Add(corpseStarchMissionDefs[0].Duration + time.Second)
|
||
if m.game.MissionRemaining(finished) != 0 {
|
||
t.Fatalf("миссия должна была завершиться к этому моменту")
|
||
}
|
||
|
||
collectActions := m.buildActions(finished)
|
||
collectIdx := -1
|
||
for i, a := range collectActions {
|
||
if a.Label == T("corpsestarch.action.collect_mission") {
|
||
collectIdx = i
|
||
break
|
||
}
|
||
}
|
||
if collectIdx == -1 {
|
||
t.Fatalf("не нашли действие сбора награды")
|
||
}
|
||
collectActions[collectIdx].Handle(&m, finished)
|
||
if m.game.ActiveMission != nil {
|
||
t.Errorf("миссия должна была завершиться после сбора награды")
|
||
}
|
||
if m.game.MissionsDone != 1 {
|
||
t.Errorf("счётчик завершённых миссий должен был увеличиться")
|
||
}
|
||
}
|
||
|
||
// TestCorpseStarchDemonicModeNeverMentionsCorpseStarchOrMunitorum —
|
||
// регрессия: после перехода на сторону Хаоса часть сообщений
|
||
// (реквизиция, нехватка средств, окунание меча, приветствие после
|
||
// возвращения, заголовок журнала) была жёстко зашита на имперскую
|
||
// формулировку "трупный крахмал"/"Муниторум" и не переключалась
|
||
// вместе с остальной игрой (сервиторы и миссии уже переключались
|
||
// корректно).
|
||
func TestCorpseStarchDemonicModeNeverMentionsCorpseStarchOrMunitorum(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
m.game.Mode = CorpseStarchModeDemonic
|
||
m.game.TotalEarned = corpseStarchUnlockSump
|
||
m.game.Starch = 1000
|
||
now := m.game.LastTick
|
||
|
||
// реально прогоняем игровую логику в демоническом режиме, а не
|
||
// подставляем старые ключи лога вручную — сами ключи лога
|
||
// выбираются один раз, в момент события (см. modeKey), а не
|
||
// задним числом при отрисовке
|
||
for i := 0; i < 3; i++ {
|
||
m.game.DipChainsword(now, 0.95) // >=0.9 — гарантированная Скверна на каждом броске
|
||
}
|
||
m.game.ChaosDefendClicks = 0
|
||
m.game.ChaosIncursionUntil = now.Add(time.Second)
|
||
m.game.resolveChaosIncursion() // без бонуса (мало кликов защиты)
|
||
m.game.ChaosDefendClicks = corpseStarchDefendClicksForBonus
|
||
m.game.ChaosIncursionUntil = now.Add(time.Second)
|
||
m.game.resolveChaosIncursion() // с бонусом
|
||
|
||
forbidden := []string{"трупн", "Муниторум", "сервитор"}
|
||
checkNoForbidden := func(label string, text string) {
|
||
t.Helper()
|
||
for _, f := range forbidden {
|
||
if strings.Contains(text, f) {
|
||
t.Errorf("%s: в демоническом режиме не должно встречаться %q, получено: %q", label, f, text)
|
||
}
|
||
}
|
||
}
|
||
|
||
// заголовок игры ("ТРУПНЫЕ БАТОНЧИКИ") — имя самой игры, не
|
||
// переключается по режиму и намеренно не проверяется здесь
|
||
checkNoForbidden("заголовок журнала", T(m.game.modeKey("corpsestarch.log.title", "corpsestarch.log.title.demonic")))
|
||
for _, key := range m.game.Log {
|
||
checkNoForbidden("запись журнала "+key, T(key))
|
||
}
|
||
|
||
actions := m.buildActions(now)
|
||
for _, a := range actions {
|
||
checkNoForbidden("метка действия "+a.Label, a.Label)
|
||
}
|
||
|
||
m.game.Requisition(now)
|
||
m.setInfo(m.game.modeKey("corpsestarch.msg.requisitioned", "corpsestarch.msg.requisitioned.demonic"))
|
||
checkNoForbidden("сообщение после реквизиции", m.message)
|
||
|
||
m.setInfo(m.game.modeKey("corpsestarch.msg.cannot_afford", "corpsestarch.msg.cannot_afford.demonic"))
|
||
checkNoForbidden("сообщение о нехватке средств", m.message)
|
||
|
||
m.message = Tf(m.game.modeKey("corpsestarch.msg.welcome_back", "corpsestarch.msg.welcome_back.demonic"), formatStarch(100))
|
||
checkNoForbidden("приветственное сообщение", m.message)
|
||
}
|
||
|
||
func TestCorpseStarchImperialModeStillUsesOriginalWording(t *testing.T) {
|
||
withTempConfigDir(t)
|
||
m := NewCorpseStarchModel()
|
||
// режим по умолчанию — имперский; проверяем, что оригинальная
|
||
// формулировка никуда не делась для тех, кто ещё не перешёл на
|
||
// сторону Хаоса
|
||
view := m.View()
|
||
if !strings.Contains(view, "трупный крахмал") && !strings.Contains(view, "Трупный крахмал") {
|
||
t.Errorf("в имперском режиме заголовок должен был по-прежнему упоминать трупный крахмал, получено:\n%s", view)
|
||
}
|
||
}
|