feat: refresh and edit browser instances

This commit is contained in:
QiuSW
2026-07-25 16:36:26 +08:00
parent 1a453cf8f0
commit be9292c376
7 changed files with 946 additions and 51 deletions
+112
View File
@@ -2,6 +2,7 @@ package ui
import (
"context"
"path/filepath"
"sync/atomic"
"testing"
"time"
@@ -48,6 +49,12 @@ func TestShellInstanceRowsContainRequiredColumnsAndIndependentActionControls(t *
if shell.deleteClickFor(shell.rows[0].ID) == shell.deleteClickFor(shell.rows[1].ID) {
t.Fatal("rows share a delete button state")
}
if shell.editClickFor(shell.rows[0].ID) == shell.editClickFor(shell.rows[1].ID) {
t.Fatal("rows share an edit button state")
}
if shell.rowClickFor(shell.rows[0].ID) == shell.rowClickFor(shell.rows[1].ID) {
t.Fatal("rows share a row interaction state")
}
}
func TestInstanceListColumnWeightsPrioritizeDirectoryAndCompactAction(t *testing.T) {
@@ -331,3 +338,108 @@ func TestShellAppliesSelectedInstanceDirectory(t *testing.T) {
t.Fatalf("instance dir = %q", got)
}
}
func TestShellRefreshesConfiguredInstancesAsynchronously(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[3]
seen := make(chan []InstanceRow, 1)
shell.OnRefreshInstances(func(_ context.Context, rows []InstanceRow) ([]InstanceRefreshResult, error) {
seen <- rows
return []InstanceRefreshResult{{ID: target.ID, Status: "运行中", PID: 5432, RemoteDebugPort: 9777, OccupancySource: "chub_registry"}}, nil
}, nil)
shell.requestRefresh()
if !shell.refreshClickState.running {
t.Fatal("refresh did not enter running state")
}
select {
case rows := <-seen:
if len(rows) != len(shell.rows) || rows[0].ID != shell.rows[0].ID {
t.Fatalf("refresh snapshot = %#v", rows)
}
case <-time.After(time.Second):
t.Fatal("refresh callback was not invoked")
}
var result instanceRefreshResult
select {
case result = <-shell.refreshResults:
case <-time.After(time.Second):
t.Fatal("refresh result was not produced")
}
shell.refreshResults <- result
shell.consumeRefreshResults()
row, ok := shell.instanceRow(target.ID)
if !ok || shell.refreshClickState.running || row.Status != "运行中" || row.PID != 5432 || row.RemoteDebugPort != 9777 {
t.Fatalf("refreshed row = %#v, running=%v", row, shell.refreshClickState.running)
}
}
func TestShellIgnoresRefreshResultAfterEditableFieldsChange(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[3]
shell.OnRefreshInstances(func(_ context.Context, _ []InstanceRow) ([]InstanceRefreshResult, error) {
return []InstanceRefreshResult{{ID: target.ID, Status: "运行中", PID: 5432}}, nil
}, nil)
shell.requestRefresh()
var result instanceRefreshResult
select {
case result = <-shell.refreshResults:
case <-time.After(time.Second):
t.Fatal("refresh result was not produced")
}
shell.rows[3].Name = "已编辑的实例"
shell.refreshResults <- result
shell.consumeRefreshResults()
row, ok := shell.instanceRow(target.ID)
if !ok || row.Status != "已退出" || row.PID != 0 {
t.Fatalf("stale refresh overwrote edited row: %#v", row)
}
}
func TestShellEditsExitedInstanceAndPreservesRuntimePort(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[3]
updatedDir := t.TempDir()
changes := 0
shell.OnInstancesChanged(func([]InstanceRow) { changes++ })
shell.beginEdit(target.ID)
shell.editName.SetText("已更新实例")
shell.editDir.SetText(updatedDir)
shell.editURL.SetText("https://example.com/updated")
shell.editBrowserKind.Value = "chrome"
shell.saveEdit()
row, ok := shell.instanceRow(target.ID)
if !ok || shell.editingID != "" || changes != 1 || row.Name != "已更新实例" || row.Browser != "Chrome" || row.UserDataDir != filepath.Clean(updatedDir) {
t.Fatalf("saved instance = %#v, changes=%d", row, changes)
}
if row.TargetURL != "https://example.com/updated" {
t.Fatalf("target URL = %q", row.TargetURL)
}
}
func TestShellLocksIdentityFieldsForRunningInstance(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[0]
shell.beginEdit(target.ID)
shell.editName.SetText("运行中更新名称")
shell.editDir.SetText(t.TempDir())
shell.editBrowserKind.Value = "edge"
shell.saveEdit()
row, ok := shell.instanceRow(target.ID)
if !ok || row.Name != "运行中更新名称" || row.UserDataDir != target.UserDataDir || row.Browser != target.Browser {
t.Fatalf("running instance identity changed: %#v", row)
}
}
func TestShellRequestsDiscardConfirmationForDirtyEdit(t *testing.T) {
shell := NewShell(material.NewTheme())
shell.beginEdit(shell.rows[3].ID)
shell.editName.SetText("未保存的更改")
shell.cancelEdit()
if !shell.pendingEditDiscard || shell.editingID == "" {
t.Fatalf("dirty edit cancellation state = discard %v editing %q", shell.pendingEditDiscard, shell.editingID)
}
}