Files
tinycast/Tests/window-switch-test.swift
Abue Ammar 2a93f66ed8 Reach a window or a menu bar item from the launcher (#602) (#603)
Adds a Navigation feature: one settings pane, one switch, two commands
that move you somewhere rather than changing something.

Switch Windows sweeps every regular app's standard windows over AX,
minimized ones included, orders them most-recently-used and raises the
chosen one. Recency comes from a single CGWindowListCopyWindowInfo call
for per-app front rank — public API, no Screen Recording grant and no
long-lived activation observer.

Search Menu Bar Items is today's Search Menu Items, moved out of
Settings > Commands into the new pane and renamed. Its raw id is
unchanged, so recorded shortcuts, aliases and visibility keys survive.
It also gains a Disabled Applications list: MenuSearchTarget.classify
answers .excluded before the menu-bar test, so an excluded app starts no
walk at all rather than having a read menu filtered afterwards.

The exclusion list is the Clipboard section lifted into a shared
DisabledApplicationsSection, and AppPickerPopover moves to
Features/Launcher/Settings/ where its three consumers already pointed.
2026-09-12 04:50:08 +06:00

124 lines
4.6 KiB
Swift

import Foundation
@main
@MainActor
struct WindowSwitchTests {
static var failures = 0
static func expect(_ condition: @autoclosure () -> Bool, _ message: String) {
if !condition() {
failures += 1
print("FAIL: \(message)")
}
}
static func entry(
_ handle: Int, app: String = "Safari", title: String = "Window",
minimized: Bool = false, rank: Int = 0
) -> WindowSwitchEntry {
WindowSwitchEntry(
handle: handle, appName: app, bundleID: "com.example.\(app.lowercased())",
iconURL: nil, iconStamp: 0, title: title, isMinimized: minimized, appRank: rank)
}
static func main() {
identity()
displayTitle()
searchMapping()
ordering()
orderingIsTotal()
ranking()
rankingLimit()
print(failures == 0 ? "Window switch tests passed" : "\(failures) window switch tests failed")
exit(failures == 0 ? 0 : 1)
}
static func identity() {
expect(entry(7).id == "7", "the id is the handle, which is unique inside one sweep")
}
static func displayTitle() {
expect(entry(0, title: "Notes.md").displayTitle == "Notes.md", "a titled window shows it")
expect(
entry(0, app: "Preview", title: "").displayTitle == "Preview",
"an untitled window reads as its app rather than as a blank row")
expect(
entry(0, app: "Preview", title: " ").displayTitle == "Preview",
"a whitespace-only title is no title")
}
static func searchMapping() {
let aliases = entry(0, app: "Safari", title: "Inbox").searchFields().aliases
expect(
aliases.contains { $0.text == "Inbox" && $0.role == .name },
"the window title is the name a query matches")
expect(
aliases.contains { $0.text == "Safari" && $0.role == .owner },
"the app rides as owner: every window of one app shares it")
}
static func ordering() {
let sorted = WindowSwitchOrder.sorted([
entry(0, app: "Mail", rank: 2),
entry(1, app: "Safari", minimized: true, rank: 0),
entry(2, app: "Safari", rank: 0),
entry(3, app: "Safari", rank: 0),
entry(4, app: "Zed", rank: .max)
])
expect(
sorted.map(\.handle) == [2, 3, 0, 4, 1],
"front app first, then by rank, unranked next, minimized last: \(sorted.map(\.handle))")
}
static func orderingIsTotal() {
let entries = (0..<12).map {
entry($0, app: ["Mail", "Safari", "Zed"][$0 % 3], minimized: $0 % 4 == 0, rank: $0 % 3)
}
let once = WindowSwitchOrder.sorted(entries)
let twice = WindowSwitchOrder.sorted(entries.reversed())
expect(
once.map(\.handle) == twice.map(\.handle),
"the order is total, so a shuffled sweep sorts identically")
expect(
once.map(\.handle).sorted() == entries.map(\.handle).sorted(),
"sorting drops nothing")
let split = once.firstIndex(where: \.isMinimized) ?? once.count
expect(
once[split...].allSatisfy(\.isMinimized),
"minimized windows form one run at the end, never interleaved")
}
static func ranking() {
let entries = [
entry(0, app: "Mail", title: "Drafts"),
entry(1, app: "Safari", title: "Inbox"),
entry(2, app: "Safari", title: "Inbox archive")
]
expect(
WindowSwitchQuery.rank(entries, for: "").map(\.handle) == [0, 1, 2],
"an empty query keeps the order it was handed")
expect(
WindowSwitchQuery.rank(entries, for: "Inbox").first?.handle == 1,
"the exact title beats the one that only starts with it")
expect(
WindowSwitchQuery.rank(entries, for: "Safari").map(\.handle) == [1, 2],
"the app name matches every one of its windows, in the order given")
expect(
WindowSwitchQuery.rank(entries, for: "zzz").isEmpty,
"a query nothing matches ranks nothing")
}
static func rankingLimit() {
let entries = (0..<(WindowSwitchQuery.resultLimit + 50)).map {
entry($0, app: "Safari", title: "Tab \($0)")
}
expect(
WindowSwitchQuery.rank(entries, for: "").count == WindowSwitchQuery.resultLimit,
"an empty query is capped too, so a huge sweep never builds a huge list")
expect(
WindowSwitchQuery.rank(entries, for: "Tab").count == WindowSwitchQuery.resultLimit,
"the cap holds under a query every row matches")
}
}