package ui import ( "context" "testing" "time" "gioui.org/widget/material" ) func TestShellStartsWithSemanticInstanceStatuses(t *testing.T) { shell := NewShell(material.NewTheme()) seen := map[string]bool{} for _, row := range shell.rows { seen[row.Status] = true } for _, status := range []string{"运行中", "启动中", "外部占用", "已退出"} { if !seen[status] { t.Fatalf("status %q is missing", status) } } } func TestShellInstanceRowsContainRequiredColumnsAndIndependentStartControls(t *testing.T) { shell := NewShell(material.NewTheme()) if len(shell.rows) < 2 { t.Fatal("expected fixture rows") } for _, row := range shell.rows { if row.ID == "" || row.Name == "" || row.Browser == "" || row.UserDataDir == "" || row.Status == "" { t.Fatalf("incomplete instance row: %#v", row) } } first := shell.startClickFor(shell.rows[0].ID) second := shell.startClickFor(shell.rows[1].ID) if first == second { t.Fatal("rows share a start button state") } } func TestShellCancelsOnlyTheActivePathSearch(t *testing.T) { shell := NewShell(material.NewTheme()) started := make(chan struct{}, 1) shell.OnPathSearch(func(ctx context.Context, field PathField, current string) (string, error) { started <- struct{}{} <-ctx.Done() return "", ctx.Err() }, nil) shell.togglePathSearch(PathChromeExecutable) if got := shell.searchButtonLabel(PathChromeExecutable); got != "取消" { t.Fatalf("chrome search label = %q, want 取消", got) } if got := shell.searchButtonLabel(PathEdgeExecutable); got != "搜索" { t.Fatalf("edge search label = %q, want 搜索", got) } select { case <-started: case <-time.After(time.Second): t.Fatal("search did not start") } shell.togglePathSearch(PathChromeExecutable) if got := shell.searchButtonLabel(PathChromeExecutable); got != "搜索" { t.Fatalf("chrome search label = %q after cancel, want 搜索", got) } } func TestShellAppliesLatestPathSearchResult(t *testing.T) { shell := NewShell(material.NewTheme()) shell.OnPathSearch(func(context.Context, PathField, string) (string, error) { return `C:\Browser\chrome.exe`, nil }, nil) shell.togglePathSearch(PathChromeExecutable) var result pathSearchResult select { case result = <-shell.searchResults: case <-time.After(time.Second): t.Fatal("search did not produce a result") } shell.searchResults <- result shell.consumeSearchResults() if got := shell.chromePath.Text(); got != `C:\Browser\chrome.exe` { t.Fatalf("chrome path = %q", got) } } func TestShellSettingsCanBeRestored(t *testing.T) { shell := NewShell(material.NewTheme()) shell.SetSettings(SettingsState{ChromePath: "chrome", EdgePath: "edge", DefaultDir: "profiles", LogDir: "logs", CloseOnExit: false}) if shell.chromePath.Text() != "chrome" || shell.edgePath.Text() != "edge" || shell.dataDir.Text() != "profiles" || shell.logDir.Text() != "logs" || shell.closeOnExit.Value { t.Fatalf("settings were not restored") } } func TestShellKeepsSettingsStateAcrossPageSwitch(t *testing.T) { shell := NewShell(material.NewTheme()) shell.page = pageSettings shell.dataDir.SetText(`D:\profiles\current`) shell.pathFeedback = "正在搜索默认 User Data Dir…" shell.page = pageInstances shell.page = pageSettings if got := shell.dataDir.Text(); got != `D:\profiles\current` { t.Fatalf("data dir = %q", got) } if got := shell.pathFeedback; got != "正在搜索默认 User Data Dir…" { t.Fatalf("path feedback = %q", got) } } func TestShellIgnoresCancelledPathSearchResult(t *testing.T) { shell := NewShell(material.NewTheme()) started := make(chan struct{}, 1) shell.OnPathSearch(func(ctx context.Context, field PathField, current string) (string, error) { started <- struct{}{} <-ctx.Done() return `C:\stale\chrome.exe`, nil }, nil) shell.togglePathSearch(PathChromeExecutable) select { case <-started: case <-time.After(time.Second): t.Fatal("search did not start") } shell.togglePathSearch(PathChromeExecutable) select { case result := <-shell.searchResults: shell.searchResults <- result case <-time.After(time.Second): t.Fatal("cancelled search did not return") } shell.consumeSearchResults() if got := shell.chromePath.Text(); got == `C:\stale\chrome.exe` { t.Fatal("cancelled result overwrote the editor") } } func TestShellAppliesSelectedInstanceDirectory(t *testing.T) { shell := NewShell(material.NewTheme()) shell.OnChooseDirectory(func(context.Context) (string, error) { return `C:\profiles\new-instance`, nil }, nil) shell.chooseInstanceDirectory() var result directoryPickResult select { case result = <-shell.directoryResults: case <-time.After(time.Second): t.Fatal("directory picker did not produce a result") } shell.directoryResults <- result shell.consumeDirectoryResults() if got := shell.instanceDir.Text(); got != `C:\profiles\new-instance` { t.Fatalf("instance dir = %q", got) } }