package main import ( "errors" "testing" tea "github.com/charmbracelet/bubbletea" "completed/internal/client" "completed/internal/model" ) // key builds the tea.KeyMsg Update expects for a named key, matching what // bubbletea itself constructs from terminal input. func key(s string) tea.KeyMsg { switch s { case "enter": return tea.KeyMsg{Type: tea.KeyEnter} case "tab": return tea.KeyMsg{Type: tea.KeyTab} case "ctrl+c": return tea.KeyMsg{Type: tea.KeyCtrlC} default: return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(s)} } } func update(t *testing.T, m Model, msg tea.Msg) Model { t.Helper() newModel, _ := m.Update(msg) m, ok := newModel.(Model) if !ok { t.Fatalf("Update returned %T, want Model", newModel) } return m } func TestTypingFillsFocusedField(t *testing.T) { m := NewModel(client.New("")) m = update(t, m, key("a")) m = update(t, m, key("b")) if got := m.emailInput.Value(); got != "ab" { t.Errorf("emailInput.Value() = %q, want %q", got, "ab") } if got := m.passwordInput.Value(); got != "" { t.Errorf("passwordInput.Value() = %q, want empty while email is focused", got) } m = update(t, m, key("tab")) m = update(t, m, key("c")) if got := m.passwordInput.Value(); got != "c" { t.Errorf("passwordInput.Value() = %q, want %q after tab switches focus", got, "c") } } func TestEnterOnLoginStartsLoading(t *testing.T) { m := NewModel(client.New("")) m.emailInput.SetValue("a@example.com") m.passwordInput.SetValue("secret") newModel, cmd := m.Update(key("enter")) m = newModel.(Model) if m.screen != screenLoading { t.Errorf("screen = %v, want screenLoading", m.screen) } if cmd == nil { t.Error("expected a command to perform the login request, got nil") } } func TestLoginSucceededTransitionsToLoadingGames(t *testing.T) { m := NewModel(client.New("")) m.screen = screenLoading newModel, cmd := m.Update(loginSucceededMsg{user: model.User{Name: "Alice"}}) m = newModel.(Model) if m.screen != screenLoading { t.Errorf("screen = %v, want to stay screenLoading while games load", m.screen) } if m.user.Name != "Alice" { t.Errorf("user.Name = %q, want Alice", m.user.Name) } if cmd == nil { t.Error("expected a command to fetch games, got nil") } } func TestLoginFailedStaysOnLoginWithError(t *testing.T) { m := NewModel(client.New("")) m.screen = screenLoading wantErr := errors.New("invalid email or password") m = update(t, m, loginFailedMsg{err: wantErr}) if m.err != wantErr { t.Errorf("err = %v, want %v", m.err, wantErr) } } func TestGamesLoadedPopulatesTableAndSwitchesScreen(t *testing.T) { m := NewModel(client.New("")) m.screen = screenLoading games := []model.Game{ {ID: "g1", Name: "Doom", PlatformName: "PC", Score: 95, ReleaseDate: "1993-12-10"}, {ID: "g2", Name: "Half-Life", PlatformName: "PC", Score: 96, ReleaseDate: "1998-11-19", Finished: "1999-01-01"}, } m = update(t, m, gamesLoadedMsg{games: games}) if m.screen != screenGames { t.Errorf("screen = %v, want screenGames", m.screen) } rows := m.table.Rows() if len(rows) != 2 { t.Fatalf("table has %d rows, want 2", len(rows)) } if rows[0][0] != "Doom" || rows[0][4] != "" { t.Errorf("row 0 = %v, want Doom unfinished", rows[0]) } if rows[1][0] != "Half-Life" || rows[1][4] != "✓" { t.Errorf("row 1 = %v, want Half-Life finished", rows[1]) } } func TestGamesLoadFailedShowsErrorOnGamesScreen(t *testing.T) { m := NewModel(client.New("")) m.screen = screenLoading wantErr := errors.New("network error") m = update(t, m, gamesLoadFailedMsg{err: wantErr}) if m.screen != screenGames { t.Errorf("screen = %v, want screenGames (so 'r' can retry)", m.screen) } if m.err != wantErr { t.Errorf("err = %v, want %v", m.err, wantErr) } } func TestRefreshOnGamesScreenReloads(t *testing.T) { m := NewModel(client.New("")) m.screen = screenGames m.table = newGamesTable([]model.Game{{ID: "g1", Name: "Doom"}}) newModel, cmd := m.Update(key("r")) m = newModel.(Model) if m.screen != screenLoading { t.Errorf("screen = %v, want screenLoading", m.screen) } if cmd == nil { t.Error("expected a command to refetch games, got nil") } } func TestQuitOnGamesScreen(t *testing.T) { m := NewModel(client.New("")) m.screen = screenGames _, cmd := m.Update(key("q")) if cmd == nil { t.Fatal("expected tea.Quit, got nil") } } func TestCtrlCQuitsFromAnyScreen(t *testing.T) { for _, s := range []screen{screenLogin, screenLoading, screenGames} { m := NewModel(client.New("")) m.screen = s _, cmd := m.Update(key("ctrl+c")) if cmd == nil { t.Errorf("screen %v: expected tea.Quit on ctrl+c, got nil", s) } } }