265 lines
7.4 KiB
Go
265 lines
7.4 KiB
Go
package tui
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"superwork-tui/internal/cc"
|
|
"superwork-tui/internal/config"
|
|
"superwork-tui/internal/issue"
|
|
"superwork-tui/internal/store"
|
|
)
|
|
|
|
// ── profile picker tests ──────────────────────────────────────────────────────
|
|
|
|
func TestProfilePicker_NoProfilesDir_ShowsStatus(t *testing.T) {
|
|
m := New()
|
|
m.settings = &config.Settings{ProfilesDir: ""}
|
|
m.state = stateDetail
|
|
iss := issue.Issue{Number: 42}
|
|
m.detailIss = &iss
|
|
|
|
m2, _ := m.openProfilePicker(false)
|
|
if m2.state != stateDetail {
|
|
t.Errorf("state should stay stateDetail when no ProfilesDir, got %v", m2.state)
|
|
}
|
|
if !strings.Contains(m2.statusMsg, "profiles") {
|
|
t.Errorf("statusMsg should mention profiles, got %q", m2.statusMsg)
|
|
}
|
|
}
|
|
|
|
func TestProfilePicker_EscReturnsToDetail(t *testing.T) {
|
|
m := New()
|
|
m.settings = &config.Settings{ProfilesDir: t.TempDir()}
|
|
m.profilePickerPrevState = stateDetail
|
|
m.state = stateProfilePicker
|
|
m.profilePickerProfiles = []cc.ClaudeProfile{{Name: "dev", Path: "/p/dev.json"}}
|
|
|
|
result, _ := m.handleProfilePickerKey(keyMsg("esc"))
|
|
m2 := result.(Model)
|
|
if m2.state != stateDetail {
|
|
t.Errorf("expected stateDetail after esc, got %v", m2.state)
|
|
}
|
|
}
|
|
|
|
func TestProfilePicker_SelectSetsProfilePath(t *testing.T) {
|
|
m := New()
|
|
m.settings = &config.Settings{ProfilesDir: t.TempDir()}
|
|
m.state = stateProfilePicker
|
|
m.profilePickerPrevState = stateDetail
|
|
m.profilePickerForTest = false
|
|
m.profilePickerProfiles = []cc.ClaudeProfile{
|
|
{Name: "dev", Path: "/profiles/dev.json"},
|
|
{Name: "prod", Path: "/profiles/prod.json"},
|
|
}
|
|
m.profilePickerCursor = 1
|
|
iss := issue.Issue{Number: 7}
|
|
m.detailIss = &iss
|
|
|
|
result, _ := m.handleProfilePickerKey(keyMsg("enter"))
|
|
m2 := result.(Model)
|
|
|
|
if m2.state != stateDetail {
|
|
t.Errorf("expected stateDetail after selection, got %v", m2.state)
|
|
}
|
|
if m2.detailIss.ProfilePath != "/profiles/prod.json" {
|
|
t.Errorf("ProfilePath = %q, want /profiles/prod.json", m2.detailIss.ProfilePath)
|
|
}
|
|
}
|
|
|
|
func TestProfilePicker_SelectSetsTestProfilePath(t *testing.T) {
|
|
m := New()
|
|
m.settings = &config.Settings{ProfilesDir: t.TempDir()}
|
|
m.state = stateProfilePicker
|
|
m.profilePickerPrevState = stateDetail
|
|
m.profilePickerForTest = true
|
|
m.profilePickerProfiles = []cc.ClaudeProfile{
|
|
{Name: "test", Path: "/profiles/test.json"},
|
|
}
|
|
m.profilePickerCursor = 0
|
|
iss := issue.Issue{Number: 7}
|
|
m.detailIss = &iss
|
|
|
|
result, _ := m.handleProfilePickerKey(keyMsg("enter"))
|
|
m2 := result.(Model)
|
|
|
|
if m2.detailIss.TestProfilePath != "/profiles/test.json" {
|
|
t.Errorf("TestProfilePath = %q, want /profiles/test.json", m2.detailIss.TestProfilePath)
|
|
}
|
|
}
|
|
|
|
func TestProfilePicker_CursorNavigation(t *testing.T) {
|
|
m := New()
|
|
m.state = stateProfilePicker
|
|
m.profilePickerProfiles = []cc.ClaudeProfile{
|
|
{Name: "a", Path: "/a.json"},
|
|
{Name: "b", Path: "/b.json"},
|
|
{Name: "c", Path: "/c.json"},
|
|
}
|
|
m.profilePickerCursor = 0
|
|
|
|
result, _ := m.handleProfilePickerKey(keyMsg("j"))
|
|
m2 := result.(Model)
|
|
if m2.profilePickerCursor != 1 {
|
|
t.Errorf("cursor after j = %d, want 1", m2.profilePickerCursor)
|
|
}
|
|
|
|
result2, _ := m2.handleProfilePickerKey(keyMsg("k"))
|
|
m3 := result2.(Model)
|
|
if m3.profilePickerCursor != 0 {
|
|
t.Errorf("cursor after k = %d, want 0", m3.profilePickerCursor)
|
|
}
|
|
}
|
|
|
|
func TestProfilePickerView_ContainsProfiles(t *testing.T) {
|
|
m := New()
|
|
m.state = stateProfilePicker
|
|
m.profilePickerProfiles = []cc.ClaudeProfile{
|
|
{Name: "dev", Path: "/profiles/dev.json"},
|
|
{Name: "prod", Path: "/profiles/prod.json"},
|
|
}
|
|
m.profilePickerCursor = 0
|
|
|
|
out := m.profilePickerView()
|
|
for _, want := range []string{"dev", "prod", "/profiles/dev.json"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("profilePickerView missing %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── profile grid tests ────────────────────────────────────────────────────────
|
|
|
|
func TestProfileGrid_OpenLoadsData(t *testing.T) {
|
|
m := New()
|
|
m2, cmd := m.openProfileGrid()
|
|
if m2.state != stateProfileGrid {
|
|
t.Errorf("state = %v, want stateProfileGrid", m2.state)
|
|
}
|
|
if cmd == nil {
|
|
t.Error("openProfileGrid should return a load cmd")
|
|
}
|
|
}
|
|
|
|
func TestProfileGrid_EscReturnsToBoard(t *testing.T) {
|
|
m := New()
|
|
m.state = stateProfileGrid
|
|
m.profileGridData = store.ProfilesData{Profiles: []string{"dev"}, Rows: nil}
|
|
|
|
result, _ := m.handleProfileGridKey(keyMsg("esc"))
|
|
m2 := result.(Model)
|
|
if m2.state != stateLoaded {
|
|
t.Errorf("expected stateLoaded after esc, got %v", m2.state)
|
|
}
|
|
}
|
|
|
|
func TestProfileGrid_EditCellAndSave(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
restore := overrideWorkspaceRoot(tmpDir)
|
|
defer restore()
|
|
|
|
m := New()
|
|
m.state = stateProfileGrid
|
|
m.profileGridData = store.ProfilesData{
|
|
Profiles: []string{"dev", "prod"},
|
|
Rows: []store.ProfileRow{
|
|
{Key: "model", Values: map[string]string{"dev": "opus", "prod": "sonnet"}},
|
|
},
|
|
}
|
|
m.profileGridRow = 0
|
|
m.profileGridCol = 1 // "dev" column
|
|
|
|
// Enter edit mode
|
|
result, _ := m.handleProfileGridKey(keyMsg("enter"))
|
|
m2 := result.(Model)
|
|
if !m2.profileGridEditing {
|
|
t.Fatal("expected editing mode after enter on value cell")
|
|
}
|
|
|
|
// Type a new value
|
|
m2.profileGridEdit.SetValue("haiku")
|
|
|
|
// Commit
|
|
result2, _ := m2.handleProfileGridKey(keyMsg("enter"))
|
|
m3 := result2.(Model)
|
|
if m3.profileGridEditing {
|
|
t.Error("editing should be false after commit")
|
|
}
|
|
if m3.profileGridData.Rows[0].Values["dev"] != "haiku" {
|
|
t.Errorf("dev value = %q, want haiku", m3.profileGridData.Rows[0].Values["dev"])
|
|
}
|
|
|
|
// Save
|
|
_, saveCmd := m3.handleProfileGridKey(keyMsg("ctrl+s"))
|
|
if saveCmd == nil {
|
|
t.Fatal("ctrl+s should return a save cmd")
|
|
}
|
|
msg := saveCmd()
|
|
saved, ok := msg.(profileGridSavedMsg)
|
|
if !ok {
|
|
t.Fatalf("expected profileGridSavedMsg, got %T", msg)
|
|
}
|
|
if saved.err != nil {
|
|
t.Errorf("save error: %v", saved.err)
|
|
}
|
|
|
|
// Verify persisted
|
|
loaded, err := store.ReadProfiles(tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if loaded.Rows[0].Values["dev"] != "haiku" {
|
|
t.Errorf("persisted dev = %q, want haiku", loaded.Rows[0].Values["dev"])
|
|
}
|
|
}
|
|
|
|
func TestProfileGrid_AddRow(t *testing.T) {
|
|
m := New()
|
|
m.state = stateProfileGrid
|
|
m.profileGridData = store.ProfilesData{
|
|
Profiles: []string{"dev"},
|
|
Rows: []store.ProfileRow{},
|
|
}
|
|
|
|
// Press 'a' to add row
|
|
result, _ := m.handleProfileGridKey(keyMsg("a"))
|
|
m2 := result.(Model)
|
|
if !m2.profileGridAddingKey {
|
|
t.Error("expected profileGridAddingKey = true after pressing a")
|
|
}
|
|
|
|
// Type key name
|
|
m2.profileGridNewKey.SetValue("timeout")
|
|
|
|
// Confirm
|
|
result2, _ := m2.handleProfileGridKey(keyMsg("enter"))
|
|
m3 := result2.(Model)
|
|
if m3.profileGridAddingKey {
|
|
t.Error("profileGridAddingKey should be false after confirm")
|
|
}
|
|
if len(m3.profileGridData.Rows) != 1 {
|
|
t.Errorf("rows = %d, want 1", len(m3.profileGridData.Rows))
|
|
}
|
|
if m3.profileGridData.Rows[0].Key != "timeout" {
|
|
t.Errorf("row key = %q, want timeout", m3.profileGridData.Rows[0].Key)
|
|
}
|
|
}
|
|
|
|
func TestProfileGridView_ContainsData(t *testing.T) {
|
|
m := New()
|
|
m.state = stateProfileGrid
|
|
m.profileGridData = store.ProfilesData{
|
|
Profiles: []string{"dev", "prod"},
|
|
Rows: []store.ProfileRow{
|
|
{Key: "model", Values: map[string]string{"dev": "opus", "prod": "sonnet"}},
|
|
},
|
|
}
|
|
|
|
out := m.profileGridView()
|
|
for _, want := range []string{"Profile Grid", "dev", "prod", "model", "opus", "sonnet"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("profileGridView missing %q", want)
|
|
}
|
|
}
|
|
}
|