121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
|
|
package game
|
||
|
|
|
||
|
|
import (
|
||
|
|
"io"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
tea "github.com/charmbracelet/bubbletea"
|
||
|
|
"github.com/charmbracelet/lipgloss"
|
||
|
|
"github.com/muesli/termenv"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestColorAndCharSwitching(t *testing.T) {
|
||
|
|
c := NewCanvas()
|
||
|
|
m := NewModel(c, "test-id", "tester", newTestRenderer())
|
||
|
|
c.Join("test-id", "tester", nil)
|
||
|
|
|
||
|
|
// First keypress just exits the intro screen, per Update()'s own logic.
|
||
|
|
mi, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("3")})
|
||
|
|
m = mi.(Model)
|
||
|
|
if m.state != stateCanvas {
|
||
|
|
t.Fatalf("expected first keypress to exit intro, state=%v", m.state)
|
||
|
|
}
|
||
|
|
if m.inkColorIdx != 0 {
|
||
|
|
t.Fatalf("expected inkColorIdx unchanged by the intro-exit keypress, got %d", m.inkColorIdx)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now actually press "3" for color selection.
|
||
|
|
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("3")})
|
||
|
|
m = mi.(Model)
|
||
|
|
if m.inkColorIdx != 2 {
|
||
|
|
t.Fatalf("expected inkColorIdx=2 after pressing '3', got %d", m.inkColorIdx)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Paint with the selected color.
|
||
|
|
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||
|
|
m = mi.(Model)
|
||
|
|
|
||
|
|
cells, cursors := c.Snapshot()
|
||
|
|
cur := cursors["test-id"]
|
||
|
|
painted := cells[cur.Y][cur.X]
|
||
|
|
if painted.Char != charPalette[0] {
|
||
|
|
t.Fatalf("expected painted char to be default charPalette[0]=%q, got %q", charPalette[0], painted.Char)
|
||
|
|
}
|
||
|
|
if painted.Color != inkPalette[2] {
|
||
|
|
t.Fatalf("expected painted color to be inkPalette[2]=%q (selected via '3'), got %q", inkPalette[2], painted.Color)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCharCycling(t *testing.T) {
|
||
|
|
c := NewCanvas()
|
||
|
|
m := NewModel(c, "test-id", "tester", newTestRenderer())
|
||
|
|
c.Join("test-id", "tester", nil)
|
||
|
|
|
||
|
|
mi, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("x")}) // exit intro
|
||
|
|
m = mi.(Model)
|
||
|
|
|
||
|
|
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("]")})
|
||
|
|
m = mi.(Model)
|
||
|
|
if m.charIdx != 1 {
|
||
|
|
t.Fatalf("expected charIdx=1 after ']', got %d", m.charIdx)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestViewChangesAfterMoveAwayFromPaint(t *testing.T) {
|
||
|
|
c := NewCanvas()
|
||
|
|
m := NewModel(c, "test-id", "tester", newTestRenderer())
|
||
|
|
c.Join("test-id", "tester", nil)
|
||
|
|
|
||
|
|
mi, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("x")}) // exit intro
|
||
|
|
m = mi.(Model)
|
||
|
|
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace}) // paint
|
||
|
|
m = mi.(Model)
|
||
|
|
|
||
|
|
viewAtPaintPosition := m.View()
|
||
|
|
|
||
|
|
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("l")}) // move right
|
||
|
|
m = mi.(Model)
|
||
|
|
|
||
|
|
viewAfterMove := m.View()
|
||
|
|
|
||
|
|
if viewAtPaintPosition == viewAfterMove {
|
||
|
|
t.Fatal("expected View() output to differ after moving away from a painted cell, but it's identical")
|
||
|
|
}
|
||
|
|
t.Logf("view at paint position:\n%s", viewAtPaintPosition)
|
||
|
|
t.Logf("view after move:\n%s", viewAfterMove)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMoveCursorActuallyMoves(t *testing.T) {
|
||
|
|
c := NewCanvas()
|
||
|
|
m := NewModel(c, "test-id", "tester", newTestRenderer())
|
||
|
|
c.Join("test-id", "tester", nil)
|
||
|
|
|
||
|
|
before := c.CursorsSnapshot()["test-id"]
|
||
|
|
t.Logf("before: x=%d y=%d", before.X, before.Y)
|
||
|
|
|
||
|
|
mi, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("x")}) // exit intro
|
||
|
|
m = mi.(Model)
|
||
|
|
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("l")}) // move right
|
||
|
|
m = mi.(Model)
|
||
|
|
|
||
|
|
after := c.CursorsSnapshot()["test-id"]
|
||
|
|
t.Logf("after: x=%d y=%d", after.X, after.Y)
|
||
|
|
|
||
|
|
if after.X != before.X+1 {
|
||
|
|
t.Fatalf("expected cursor X to move from %d to %d, got %d", before.X, before.X+1, after.X)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// newTestRenderer forces TrueColor rather than letting lipgloss
|
||
|
|
// auto-detect from the writer (io.Discard, like any non-*os.File writer,
|
||
|
|
// would otherwise be treated as non-color-capable) -- matches main.go's
|
||
|
|
// real per-session renderer setup, so these tests actually exercise the
|
||
|
|
// same styling path a real SSH session does instead of silently testing
|
||
|
|
// an unstyled/plain-text code path that would mask exactly this class
|
||
|
|
// of bug.
|
||
|
|
func newTestRenderer() *lipgloss.Renderer {
|
||
|
|
r := lipgloss.NewRenderer(io.Discard)
|
||
|
|
r.SetColorProfile(termenv.TrueColor)
|
||
|
|
return r
|
||
|
|
}
|