Files
correx/apps/tui-go/internal/app/ideacard_test.go
T
kami d247b19608 tui: migrate Bubble Tea v1 → v2 (charm.land), enable Shift+Enter
Move the Go TUI from github.com/charmbracelet/{bubbletea,lipgloss} to the v2
ecosystem at charm.land/{bubbletea,lipgloss}/v2 (Go 1.25). v2's kitty keyboard
disambiguation is on by default, so Shift+Enter now reliably inserts a newline
in the composer (Ctrl+J / Alt+Enter kept as fallbacks for non-kitty terminals).

Approach: rather than rewrite ~190 key-match sites to v2's (Code, Mod) idiom, a
small shim (internal/app/key.go) converts a v2 KeyPressMsg into the v1-shaped
keyMsg the handlers already expect, at the single Update boundary. The rest is
mechanical:
- key constants tea.Key* → shim consts; tea.KeyMsg → keyMsg.
- lipgloss.Color is now a func returning color.Color, not a type → fields/params
  retyped to image/color.Color (theme/diff/overlays/view).
- v2 View() returns tea.View: render() builds the string, View() wraps it and
  carries AltScreen (alt-screen is a per-frame View field now, not a program opt).
- WithWhitespaceBackground/Foreground → WithWhitespaceStyle(Style).
- preview: drop lipgloss.SetColorProfile (v2 renders truecolor by default).

Build, vet, gofmt, and the full test suite are green; preview renders truecolor
across kinds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:59:55 +00:00

71 lines
2.0 KiB
Go

package app
import (
"encoding/json"
"strings"
"testing"
"github.com/correx/tui-go/internal/protocol"
"github.com/correx/tui-go/internal/ws"
)
func ideaModel() (Model, *ws.Client) {
client := ws.New("", 0) // unconnected; Send buffers, Drain reads it back
m := NewModel(client)
m.width, m.height = 120, 40
m.theme = NewTheme(SoftBlue)
m.openIdeas() // sets OverlayIdeas + sends a ListIdeas frame
m.applyServer(protocol.ServerMessage{
Type: protocol.TypeIdeaList,
Ideas: []protocol.IdeaDto{
{IdeaID: "i1", Text: "cache the repo map", CapturedAtMs: 1000},
{IdeaID: "i2", Text: "add --dry-run", CapturedAtMs: 2000},
},
})
return m, client
}
func TestIdeaBoardOpensAndRenders(t *testing.T) {
m, _ := ideaModel()
if m.overlay != OverlayIdeas {
t.Fatalf("want OverlayIdeas, got %v", m.overlay)
}
out := m.ideasModal()
for _, want := range []string{"ideas", "cache the repo map", "add --dry-run"} {
if !strings.Contains(out, want) {
t.Fatalf("board missing %q:\n%s", want, out)
}
}
}
func TestIdeaRemoveSendsDiscardAndDropsRow(t *testing.T) {
m, client := ideaModel()
_ = client.Drain() // drop the ListIdeas frame from openIdeas
updated, _ := m.handleOverlayKey(keyMsg{Type: keyRunes, Runes: []rune("x")})
m = updated.(Model)
if len(m.ideas) != 1 || m.ideas[0].IdeaID != "i2" {
t.Fatalf("expected i1 removed, got %+v", m.ideas)
}
frames := client.Drain()
if len(frames) != 1 {
t.Fatalf("expected one DiscardIdea frame, got %d", len(frames))
}
var raw map[string]any
if err := json.Unmarshal(frames[0], &raw); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if raw["type"] != "com.correx.apps.server.protocol.ClientMessage.DiscardIdea" || raw["ideaId"] != "i1" {
t.Fatalf("expected DiscardIdea i1, got %+v", raw)
}
}
func TestIdeaBoardEmptyState(t *testing.T) {
m, _ := ideaModel()
m.applyServer(protocol.ServerMessage{Type: protocol.TypeIdeaList, Ideas: nil})
if !strings.Contains(m.ideasModal(), "no ideas yet") {
t.Fatalf("expected empty state, got:\n%s", m.ideasModal())
}
}