返回 DeepSeek-Reasonix
desktop_shell_test.go
根目录 / desktop / desktop_shell_test.go
1 package main
2
3 import (
4 "reflect"
5 "testing"
6 "time"
7 )
8
9 func TestDesktopPresentPlanShowsHiddenElectronWindowOnLinux(t *testing.T) {
10 if got, want := desktopPresentPlanFor("linux", true), []desktopPresentAction{desktopPresentMaximise, desktopPresentWindowShow}; !reflect.DeepEqual(got, want) {
11 t.Fatalf("linux present actions = %v, want %v", got, want)
12 }
13 if got, want := desktopPresentPlanFor("linux", false), []desktopPresentAction{desktopPresentWindowShow, desktopPresentUnminimise}; !reflect.DeepEqual(got, want) {
14 t.Fatalf("linux normal present actions = %v, want %v", got, want)
15 }
16 }
17
18 func TestDesktopPresentPlanPreservesWindowsAndMacOrdering(t *testing.T) {
19 tests := []struct {
20 name string
21 goos string
22 maximised bool
23 want []desktopPresentAction
24 }{
25 {name: "windows maximised", goos: "windows", maximised: true, want: []desktopPresentAction{desktopPresentMaximise, desktopPresentWindowShow}},
26 {name: "windows normal", goos: "windows", want: []desktopPresentAction{desktopPresentWindowShow, desktopPresentUnminimise}},
27 {name: "mac", goos: "darwin", maximised: true, want: []desktopPresentAction{desktopPresentApplicationShow, desktopPresentWindowShow, desktopPresentUnminimise}},
28 }
29 for _, tt := range tests {
30 t.Run(tt.name, func(t *testing.T) {
31 if got := desktopPresentPlanFor(tt.goos, tt.maximised); !reflect.DeepEqual(got, tt.want) {
32 t.Fatalf("actions = %v, want %v", got, tt.want)
33 }
34 })
35 }
36 }
37
38 func TestDesktopShellFrontendReadyIsSeparateFromDOMReady(t *testing.T) {
39 shell := newDesktopShellCoordinator(&App{})
40 shell.markDOMReady()
41 if shell.frontendReady {
42 t.Fatal("DOM readiness must not imply frontend readiness")
43 }
44 first, healthy := shell.markFrontendHeartbeat(time.Unix(10, 0))
45 if !first || healthy {
46 t.Fatal("first bridge heartbeat should transition frontend readiness")
47 }
48 if first, healthy = shell.markFrontendHeartbeat(time.Unix(11, 0)); first || healthy {
49 t.Fatal("an immediate later heartbeat must not commit stable health")
50 }
51 if first, healthy = shell.markFrontendHeartbeat(time.Unix(13, 0)); first || !healthy {
52 t.Fatal("a later heartbeat should commit stable health once")
53 }
54 if first, healthy = shell.markFrontendHeartbeat(time.Unix(16, 0)); first || healthy {
55 t.Fatal("later heartbeats must not recommit process health")
56 }
57 }
58
59 func TestDesktopShellHeartbeatPreservesVisiblePhase(t *testing.T) {
60 shell := newDesktopShellCoordinator(&App{})
61 shell.mu.Lock()
62 shell.presented = true
63 shell.phase = desktopShellVisible
64 shell.mu.Unlock()
65
66 shell.markDOMReady()
67 shell.markFrontendHeartbeat(time.Unix(10, 0))
68 shell.mu.Lock()
69 defer shell.mu.Unlock()
70 if shell.phase != desktopShellVisible {
71 t.Fatalf("visible shell phase = %q, want %q", shell.phase, desktopShellVisible)
72 }
73 }
74
75 func TestTrayLossWhileBackgroundHiddenRequestsPresentation(t *testing.T) {
76 app := NewApp()
77 called := make(chan string, 1)
78 app.desktopShell.coordinator.presentOverride = func(source string) { called <- source }
79 app.desktopShell.coordinator.backgroundHidden = true
80 tray := newDesktopTray()
81 app.mu.Lock()
82 app.tray = tray
83 app.trayReady = true
84 app.desktopShell.trayState = "ready"
85 app.mu.Unlock()
86
87 app.setTrayHealth(tray, "unavailable", "no_host")
88 select {
89 case source := <-called:
90 if source != "tray_unavailable" {
91 t.Fatalf("presentation source = %q", source)
92 }
93 case <-time.After(time.Second):
94 t.Fatal("tray loss did not request presentation")
95 }
96 status := app.GetDesktopShellStatus()
97 if status.TrayState != "unavailable" || status.Reason != "no_host" {
98 t.Fatalf("shell status = %+v", status)
99 }
100 }
101
102 func TestEvaluateStatusNotifierSnapshot(t *testing.T) {
103 const item = "org.kde.StatusNotifierItem-42-1"
104 tests := []struct {
105 name string
106 state statusNotifierSnapshot
107 ready bool
108 reason string
109 }{
110 {name: "no watcher", reason: "no_watcher"},
111 {name: "no host", state: statusNotifierSnapshot{WatcherOwner: ":1.2"}, reason: "no_host"},
112 {name: "item has no owner", state: statusNotifierSnapshot{WatcherOwner: ":1.2", Host: true}, reason: "item_no_owner"},
113 {name: "item absent", state: statusNotifierSnapshot{WatcherOwner: ":1.2", Host: true, ItemOwner: ":1.9"}, reason: "item_not_registered"},
114 {name: "well-known registered", state: statusNotifierSnapshot{WatcherOwner: ":1.2", Host: true, ItemOwner: ":1.9", Items: []string{item + "/StatusNotifierItem"}}, ready: true},
115 {name: "unique owner registered", state: statusNotifierSnapshot{WatcherOwner: ":1.2", Host: true, ItemOwner: ":1.9", Items: []string{":1.9/StatusNotifierItem"}}, ready: true},
116 }
117 for _, tt := range tests {
118 t.Run(tt.name, func(t *testing.T) {
119 ready, reason := evaluateStatusNotifierSnapshot(tt.state, item)
120 if ready != tt.ready || reason != tt.reason {
121 t.Fatalf("evaluate = (%v, %q), want (%v, %q)", ready, reason, tt.ready, tt.reason)
122 }
123 })
124 }
125 }
126
127 func TestProcessEnvWithOverridesReplacesInsteadOfDuplicating(t *testing.T) {
128 got := processEnvWithOverrides([]string{"A=old", "B=kept", "A=duplicate"}, map[string]string{"A": "new", "C": "added"})
129 counts := map[string]int{}
130 values := map[string]string{}
131 for _, entry := range got {
132 for _, key := range []string{"A", "B", "C"} {
133 prefix := key + "="
134 if len(entry) >= len(prefix) && entry[:len(prefix)] == prefix {
135 counts[key]++
136 values[key] = entry[len(prefix):]
137 }
138 }
139 }
140 if counts["A"] != 1 || values["A"] != "new" || values["B"] != "kept" || values["C"] != "added" {
141 t.Fatalf("overridden environment = %v", got)
142 }
143 }
144
144 lines GO