| 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 |