Files
superwork/tui/internal/tui/managed_sessions_panel_test.go
T
2026-06-23 05:02:15 +08:00

419 lines
11 KiB
Go

package tui
import (
"context"
"strings"
"testing"
"superwork-tui/internal/cc"
"superwork-tui/internal/store"
)
func TestManagedSessions_OpenSetsState(t *testing.T) {
m := New()
m2, cmd := m.openManagedSessions()
if m2.state != stateManagedSessions {
t.Errorf("state = %v, want stateManagedSessions", m2.state)
}
if cmd == nil {
t.Error("openManagedSessions should return a load cmd")
}
}
func TestManagedSessions_EscReturnsToBoard(t *testing.T) {
m := New()
m.state = stateManagedSessions
result, _ := m.handleManagedSessionsKey(keyMsg("esc"))
m2 := result.(Model)
if m2.state != stateLoaded {
t.Errorf("expected stateLoaded, got %v", m2.state)
}
}
func TestManagedSessions_CreateFiresLaunchCmd(t *testing.T) {
origWatcher := watchForNewSessionFn
watchForNewSessionFn = func(_ context.Context, _ cc.SessionWatchOpts) (string, error) {
return "fake-session-id-123", nil
}
defer func() { watchForNewSessionFn = origWatcher }()
tmpDir := t.TempDir()
restore := overrideWorkspaceRoot(tmpDir)
defer restore()
rec := &recorder{
responses: map[string][]byte{
"has-session": {},
"list-windows": []byte(""),
},
}
sm := NewSessionManager(rec.run)
m := New()
m.state = stateManagedSessions
m.sessionMgr = sm
m.managedSessions = store.ManagedSessionsData{Sessions: []store.ManagedSession{}}
result, _ := m.handleManagedSessionsKey(keyMsg("a"))
m2 := result.(Model)
if !m2.managedSessionsInputActive {
t.Fatal("expected input active after 'a'")
}
if m2.managedSessionsInputMode != sessionsModeCreate {
t.Errorf("mode = %v, want sessionsModeCreate", m2.managedSessionsInputMode)
}
m2.managedSessionsInput.SetValue("cap-sess")
result2, launchCmd := m2.handleManagedSessionsKey(keyMsg("enter"))
m3 := result2.(Model)
if m3.managedSessionsInputActive {
t.Error("input should be inactive after enter")
}
if len(m3.managedSessions.Sessions) != 0 {
t.Errorf("sessions should be 0 before capture, got %d", len(m3.managedSessions.Sessions))
}
if launchCmd == nil {
t.Fatal("expected launch cmd")
}
captureMsg := launchCmd()
captured, ok := captureMsg.(managedSessionCapturedMsg)
if !ok {
t.Fatalf("expected managedSessionCapturedMsg, got %T", captureMsg)
}
if captured.sessionID != "fake-session-id-123" {
t.Errorf("captured.sessionID = %q, want fake-session-id-123", captured.sessionID)
}
if captured.name != "cap-sess" {
t.Errorf("captured.name = %q, want cap-sess", captured.name)
}
result3, saveCmd := m3.Update(captureMsg)
m4 := result3.(Model)
if len(m4.managedSessions.Sessions) != 1 {
t.Fatalf("sessions = %d, want 1 after capture", len(m4.managedSessions.Sessions))
}
sess := m4.managedSessions.Sessions[0]
if sess.ID != "fake-session-id-123" {
t.Errorf("session ID = %q, want fake-session-id-123", sess.ID)
}
if sess.Name != "cap-sess" {
t.Errorf("session Name = %q, want cap-sess", sess.Name)
}
if saveCmd == nil {
t.Fatal("expected save cmd after capture")
}
}
func TestManagedSessions_CreateTimeout_Drops(t *testing.T) {
origWatcher := watchForNewSessionFn
watchForNewSessionFn = func(_ context.Context, _ cc.SessionWatchOpts) (string, error) {
return "", nil
}
defer func() { watchForNewSessionFn = origWatcher }()
tmpDir := t.TempDir()
restore := overrideWorkspaceRoot(tmpDir)
defer restore()
rec := &recorder{
responses: map[string][]byte{
"has-session": {},
"list-windows": []byte(""),
},
}
sm := NewSessionManager(rec.run)
m := New()
m.state = stateManagedSessions
m.sessionMgr = sm
m.managedSessions = store.ManagedSessionsData{Sessions: []store.ManagedSession{}}
result, _ := m.handleManagedSessionsKey(keyMsg("a"))
m2 := result.(Model)
m2.managedSessionsInput.SetValue("timeout-sess")
_, launchCmd := m2.handleManagedSessionsKey(keyMsg("enter"))
if launchCmd == nil {
t.Fatal("expected launch cmd")
}
timeoutMsg := launchCmd()
captured, ok := timeoutMsg.(managedSessionCapturedMsg)
if !ok {
t.Fatalf("expected managedSessionCapturedMsg, got %T", timeoutMsg)
}
if captured.sessionID != "" {
t.Errorf("expected empty sessionID on timeout, got %q", captured.sessionID)
}
result2, _ := m.Update(timeoutMsg)
m3 := result2.(Model)
if len(m3.managedSessions.Sessions) != 0 {
t.Errorf("sessions should be 0 after timeout, got %d", len(m3.managedSessions.Sessions))
}
}
func TestManagedSessions_CapturedID_ResumeBuildsCommand(t *testing.T) {
tmpDir := t.TempDir()
restore := overrideWorkspaceRoot(tmpDir)
defer restore()
rec := &recorder{
responses: map[string][]byte{
"has-session": {},
"list-windows": []byte(""),
},
}
sm := NewSessionManager(rec.run)
m := New()
m.state = stateManagedSessions
m.sessionMgr = sm
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "captured-abc123", Name: "mysess", CreatedAt: 100},
},
}
m.managedSessionsCursor = 0
_, resumeCmd := m.handleManagedSessionsKey(keyMsg("enter"))
if resumeCmd == nil {
t.Fatal("expected resume cmd")
}
msg := resumeCmd()
result, ok := msg.(sessionActionResultMsg)
if !ok {
t.Fatalf("expected sessionActionResultMsg, got %T", msg)
}
if result.err != nil {
t.Errorf("unexpected error: %v", result.err)
}
var newWinCmd string
for _, call := range rec.calls {
if len(call) > 0 && call[0] == "new-window" {
newWinCmd = call[len(call)-1]
}
}
if !strings.Contains(newWinCmd, "--resume") || !strings.Contains(newWinCmd, "captured-abc123") {
t.Errorf("new-window command missing --resume captured-abc123, got %q", newWinCmd)
}
}
func TestManagedSessions_DeleteConfirm_ShowsName(t *testing.T) {
m := New()
m.state = stateManagedSessions
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "uuid-123", Name: "my-named-session", CreatedAt: 1},
},
}
m.managedSessionsCursor = 0
m.managedSessionsDeleteID = "uuid-123"
out := m.managedSessionsView()
if !strings.Contains(out, "my-named-session") {
t.Errorf("delete confirm should show session name, got %q", out)
}
if strings.Contains(out, "确认删除会话 \"uuid-123\"") {
t.Error("delete confirm should not show raw ID")
}
}
func TestManagedSessions_RenameSession(t *testing.T) {
tmpDir := t.TempDir()
restore := overrideWorkspaceRoot(tmpDir)
defer restore()
m := New()
m.state = stateManagedSessions
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "abc123", Name: "old-name", CreatedAt: 100},
},
}
m.managedSessionsCursor = 0
// Press 'r'
result, _ := m.handleManagedSessionsKey(keyMsg("r"))
m2 := result.(Model)
if !m2.managedSessionsInputActive {
t.Fatal("expected input active after 'r'")
}
if m2.managedSessionsInputMode != sessionsModeRename {
t.Errorf("mode = %v, want sessionsModeRename", m2.managedSessionsInputMode)
}
m2.managedSessionsInput.SetValue("new-name")
result2, _ := m2.handleManagedSessionsKey(keyMsg("enter"))
m3 := result2.(Model)
if m3.managedSessions.Sessions[0].Name != "new-name" {
t.Errorf("name = %q, want new-name", m3.managedSessions.Sessions[0].Name)
}
}
func TestManagedSessions_DeleteSession(t *testing.T) {
tmpDir := t.TempDir()
restore := overrideWorkspaceRoot(tmpDir)
defer restore()
m := New()
m.state = stateManagedSessions
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "abc123", Name: "to-delete", CreatedAt: 100},
{ID: "def456", Name: "keep", CreatedAt: 200},
},
}
m.managedSessionsCursor = 0
// Press 'd'
result, _ := m.handleManagedSessionsKey(keyMsg("d"))
m2 := result.(Model)
if m2.managedSessionsDeleteID != "abc123" {
t.Errorf("deleteID = %q, want abc123", m2.managedSessionsDeleteID)
}
// Confirm 'y'
result2, saveCmd := m2.handleManagedSessionsKey(keyMsg("y"))
m3 := result2.(Model)
if m3.managedSessionsDeleteID != "" {
t.Error("deleteID should be cleared after confirmation")
}
if len(m3.managedSessions.Sessions) != 1 {
t.Errorf("sessions = %d, want 1", len(m3.managedSessions.Sessions))
}
if m3.managedSessions.Sessions[0].ID != "def456" {
t.Errorf("remaining session ID = %q, want def456", m3.managedSessions.Sessions[0].ID)
}
if saveCmd != nil {
msg := saveCmd()
if sv, ok := msg.(managedSessionsSavedMsg); ok && sv.err != nil {
t.Errorf("save error: %v", sv.err)
}
}
}
func TestManagedSessions_DeleteConfirmN_Cancels(t *testing.T) {
m := New()
m.state = stateManagedSessions
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "abc", Name: "x", CreatedAt: 1},
},
}
m.managedSessionsCursor = 0
m.managedSessionsDeleteID = "abc"
result, _ := m.handleManagedSessionsKey(keyMsg("n"))
m2 := result.(Model)
if m2.managedSessionsDeleteID != "" {
t.Error("deleteID should be cleared on 'n'")
}
if len(m2.managedSessions.Sessions) != 1 {
t.Error("sessions should not be deleted on 'n'")
}
}
func TestManagedSessions_ResumeEmptyID_Error(t *testing.T) {
rec := &recorder{
responses: map[string][]byte{
"has-session": {},
},
}
sm := NewSessionManager(rec.run)
m := New()
m.state = stateManagedSessions
m.sessionMgr = sm
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "", Name: "no-id", CreatedAt: 100},
},
}
m.managedSessionsCursor = 0
_, resumeCmd := m.handleManagedSessionsKey(keyMsg("enter"))
if resumeCmd == nil {
t.Fatal("expected resume cmd")
}
msg := resumeCmd()
result, ok := msg.(sessionActionResultMsg)
if !ok {
t.Fatalf("expected sessionActionResultMsg, got %T", msg)
}
if result.err == nil {
t.Error("expected error for empty ID resume")
}
}
func TestManagedSessions_ResumeWithID_OpensWindow(t *testing.T) {
tmpDir := t.TempDir()
restore := overrideWorkspaceRoot(tmpDir)
defer restore()
rec := &recorder{
responses: map[string][]byte{
"has-session": {},
"list-windows": []byte(""),
},
}
sm := NewSessionManager(rec.run)
m := New()
m.state = stateManagedSessions
m.sessionMgr = sm
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "sess-abc123", Name: "my-sess", ProfilePath: "/p/dev.json", CreatedAt: 100},
},
}
m.managedSessionsCursor = 0
_, resumeCmd := m.handleManagedSessionsKey(keyMsg("enter"))
if resumeCmd == nil {
t.Fatal("expected resume cmd")
}
msg := resumeCmd()
result, ok := msg.(sessionActionResultMsg)
if !ok {
t.Fatalf("expected sessionActionResultMsg, got %T", msg)
}
if result.err != nil {
t.Errorf("unexpected error: %v", result.err)
}
// Check tmux received new-window with the resume command
var newWinCmd string
for _, call := range rec.calls {
if len(call) > 0 && call[0] == "new-window" {
newWinCmd = call[len(call)-1]
}
}
if !strings.Contains(newWinCmd, "--resume") || !strings.Contains(newWinCmd, "sess-abc123") {
t.Errorf("new-window command missing resume args, got %q", newWinCmd)
}
}
func TestManagedSessionsView_ContainsSessionInfo(t *testing.T) {
m := New()
m.state = stateManagedSessions
m.managedSessions = store.ManagedSessionsData{
Sessions: []store.ManagedSession{
{ID: "abc123", Name: "my-session", ProfilePath: "/dev.json", CreatedAt: 1000},
},
}
m.managedSessionsCursor = 0
out := m.managedSessionsView()
for _, want := range []string{"my-session", "abc123"} {
if !strings.Contains(out, want) {
t.Errorf("managedSessionsView missing %q", want)
}
}
}