package main import ( "strings" "testing" "unicode/utf8" ) func TestWfWrapSplitsLongText(t *testing.T) { longText := "Это довольно длинный текст, который заведомо не поместится в одну строку узкого терминала и должен быть перенесён на несколько строк." // termWidth=50 -> реальная ширина переноса 50-4=46 (выше минимума // в 40, так что запасной вариант 60 тут не сработает) wrapped := wfWrap(longText, 50) lines := strings.Split(strings.TrimRight(wrapped, "\n"), "\n") if len(lines) < 2 { t.Fatalf("ожидался перенос минимум на 2 строки, получена 1: %q", wrapped) } for _, line := range lines { if n := utf8.RuneCountInString(line); n > 46 { t.Errorf("строка длиннее заданной ширины 46 рун: %q (рун: %d)", line, n) } } } func TestWfWrapShortTextStaysOnOneLine(t *testing.T) { short := "Короткая фраза." wrapped := wfWrap(short, 80) lines := strings.Split(strings.TrimRight(wrapped, " \n"), "\n") if len(lines) != 1 { t.Errorf("короткий текст не должен переноситься, получено %d строк: %q", len(lines), wrapped) } } func TestWfWrapUsesFallbackWidthWhenTermWidthUnset(t *testing.T) { longText := strings.Repeat("слово ", 30) wrapped := wfWrap(longText, 0) lines := strings.Split(strings.TrimRight(wrapped, " \n"), "\n") if len(lines) < 2 { t.Errorf("даже с запасной шириной длинный текст должен был перенестись, получено: %q", wrapped) } } func TestWayfarerQuestOfferAccept(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() m.pendingQuestKey = WayfarerQuestDefs[0].Key m.view = wfViewQuestOffer next, _ := m.Update(key("1")) m = next.(WayfarerModel) if m.view != wfViewQuestStep { t.Fatalf("после принятия задания должен был открыться экран его шага, получено view=%v", m.view) } if m.ship.ActiveQuest == nil || m.ship.ActiveQuest.Key != WayfarerQuestDefs[0].Key { t.Fatalf("задание должно было стать активным") } if m.pendingQuestKey != "" { t.Errorf("pendingQuestKey должен был очиститься после принятия") } } func TestWayfarerQuestOfferDecline(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() m.pendingQuestKey = WayfarerQuestDefs[0].Key m.view = wfViewQuestOffer next, _ := m.Update(key("2")) m = next.(WayfarerModel) if m.view != wfViewHub { t.Fatalf("после отказа должен был открыться хаб, получено view=%v", m.view) } if m.ship.ActiveQuest != nil { t.Errorf("задание не должно было стать активным после отказа") } if m.pendingQuestKey != "" { t.Errorf("pendingQuestKey должен был очиститься после отказа") } } func TestWayfarerMarketPricesStableUntilNextDocking(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() m.view = wfViewMarket before := append([]float64{}, m.marketPrices...) for i := 0; i < 10; i++ { next, _ := m.Update(key("down")) m = next.(WayfarerModel) next, _ = m.Update(key("up")) m = next.(WayfarerModel) } for i, p := range m.marketPrices { if p != before[i] { t.Errorf("цена товара %d изменилась просто от навигации по рынку: было %v, стало %v", i, before[i], p) } } } func TestWayfarerMarketPricesChangeAfterJump(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() before := append([]float64{}, m.marketPrices...) nearby := wayfarerNearbySystems(m.ship.CurrentSystem, m.ship.FuelRange()) if len(nearby) == 0 { t.Fatal("нет достижимых систем для проверки") } next, _ := m.performJump(nearby[0]) m2 := next.(WayfarerModel) changed := false for i, p := range m2.marketPrices { if p != before[i] { changed = true break } } if !changed { t.Errorf("после прыжка в новую систему цены должны были пересчитаться") } } func TestWayfarerBuySellUsesCachedPriceNotFreshRoll(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() m.view = wfViewMarket m.cursor = 0 cachedPrice := m.marketPrices[0] next, _ := m.Update(key("b")) m = next.(WayfarerModel) // после покупки кэш цены не должен был поменяться сам по себе if m.marketPrices[0] != cachedPrice { t.Errorf("покупка не должна была пересчитать цену: было %v, стало %v", cachedPrice, m.marketPrices[0]) } wantCredits := 1000 - cachedPrice if m.ship.Credits != wantCredits { t.Errorf("покупка должна была списать ровно закэшированную цену %v, кредитов осталось %v (ожидалось %v)", cachedPrice, m.ship.Credits, wantCredits) } } func TestWayfarerResetRequestShowsConfirmation(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() next, _ := m.Update(key("N")) m = next.(WayfarerModel) if !m.confirmingReset { t.Fatalf("ожидался запрос подтверждения после нажатия N") } } func TestWayfarerResetCancelKeepsCareer(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() m.ship.Credits = 5000 next, _ := m.Update(key("N")) m = next.(WayfarerModel) next, _ = m.Update(key("n")) m = next.(WayfarerModel) if m.confirmingReset { t.Errorf("запрос подтверждения должен был закрыться после отмены") } if m.ship.Credits != 5000 { t.Errorf("карьера не должна была сброситься при отмене, получено кредитов: %v", m.ship.Credits) } } func TestWayfarerResetConfirmStartsFreshCareer(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() m.ship.Credits = 5000 m.ship.CurrentSystem = WayfarerSystemID{Galaxy: 2, Index: 50} next, _ := m.Update(key("N")) m = next.(WayfarerModel) next, _ = m.Update(key("y")) m = next.(WayfarerModel) if m.confirmingReset { t.Errorf("запрос подтверждения должен был закрыться после подтверждения") } if m.ship.Credits != 1000 { t.Errorf("ожидались стартовые 1000 кредитов после новой карьеры, получено %v", m.ship.Credits) } if m.ship.CurrentSystem != wayfarerStartSystem { t.Errorf("новая карьера должна была начаться со стартовой системы") } if m.view != wfViewHub { t.Errorf("после сброса должен был открыться хаб") } } func TestWayfarerResetActionsIgnoredWhileConfirming(t *testing.T) { withTempConfigDir(t) m := NewWayfarerModel() viewBefore := m.view next, _ := m.Update(key("N")) m = next.(WayfarerModel) // нажатие "1" (переход на рынок) не должно срабатывать, пока ждём подтверждения next, _ = m.Update(key("1")) m = next.(WayfarerModel) if m.view != viewBefore { t.Errorf("обычная навигация не должна срабатывать во время запроса подтверждения") } } func TestWfWrapClampsExcessiveWidth(t *testing.T) { text := "текст" wrapped := wfWrap(text, 500) firstLine := strings.Split(wrapped, "\n")[0] // при очень широком терминале перенос всё равно не должен // растягиваться дальше 100 колонок (проверяем по длине padding'а, // которым lipgloss дополняет строку до ширины стиля) if n := utf8.RuneCountInString(firstLine); n > 100 { t.Errorf("ширина не должна была превышать 100 колонок даже на широком терминале, получено %d", n) } }