From 86f0b4bd20cdec86dfb5bdb7978d1f6ff4af111a Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 14 Aug 2026 22:41:54 +0800
Subject: [PATCH] [verified] feat: export performance diagnostics
---
C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift | 771 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 767 insertions(+), 4 deletions(-)
diff --git a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift b/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
index 9eca644..c0197df 100644
--- a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
+++ b/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
@@ -6,7 +6,91 @@
func testAppIdentity() {
XCTAssertEqual(AlignerAppInfo.name, "Aligner")
XCTAssertEqual(AlignerAppInfo.bundleIdentifier, "com.ar.Aligner")
- XCTAssertEqual(AlignerAppInfo.minimumMacOSVersion, "26.0")
+ XCTAssertEqual(AlignerAppInfo.minimumMacOSVersion, "14.0")
+ }
+
+ func testPerformanceDiagnosticArchiveWriterCreatesInspectableArchive() throws {
+ let fileManager = FileManager.default
+ let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
+ let logURL = rootURL.appendingPathComponent("events.log")
+ let archiveURL = rootURL.appendingPathComponent("diagnostics.zip")
+ let extractionURL = rootURL.appendingPathComponent("extracted", isDirectory: true)
+ defer { try? fileManager.removeItem(at: rootURL) }
+
+ try fileManager.createDirectory(at: rootURL, withIntermediateDirectories: true)
+ try "event=quickSwitch.snapshot.success durationMilliseconds=42\n".write(to: logURL, atomically: true, encoding: .utf8)
+ let summary = try JSONSerialization.data(withJSONObject: ["schemaVersion": 1], options: [])
+
+ _ = try PerformanceDiagnosticArchiveWriter.write(
+ to: archiveURL,
+ summaryJSON: summary,
+ eventLogURLs: [logURL]
+ )
+ XCTAssertTrue(fileManager.fileExists(atPath: archiveURL.path))
+
+ let unzipProcess = Process()
+ unzipProcess.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
+ unzipProcess.arguments = ["-x", "-k", archiveURL.path, extractionURL.path]
+ try unzipProcess.run()
+ unzipProcess.waitUntilExit()
+ XCTAssertEqual(unzipProcess.terminationStatus, 0)
+ let contentsURL = extractionURL.appendingPathComponent("Aligner-Diagnostics", isDirectory: true)
+ XCTAssertTrue(fileManager.fileExists(atPath: contentsURL.appendingPathComponent("summary.json").path))
+ XCTAssertTrue(fileManager.fileExists(atPath: contentsURL.appendingPathComponent("performance-events.log").path))
+ XCTAssertTrue(fileManager.fileExists(atPath: contentsURL.appendingPathComponent("README.txt").path))
+ }
+
+ func testPerformanceDiagnosticArchiveWriterDoesNotOverwriteExistingArchive() throws {
+ let fileManager = FileManager.default
+ let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
+ let archiveURL = rootURL.appendingPathComponent("diagnostics.zip")
+ defer { try? fileManager.removeItem(at: rootURL) }
+
+ try fileManager.createDirectory(at: rootURL, withIntermediateDirectories: true)
+ try Data("existing archive".utf8).write(to: archiveURL)
+ let summary = try JSONSerialization.data(withJSONObject: ["schemaVersion": 1], options: [])
+
+ XCTAssertThrowsError(
+ try PerformanceDiagnosticArchiveWriter.write(
+ to: archiveURL,
+ summaryJSON: summary,
+ eventLogURLs: []
+ )
+ )
+ XCTAssertEqual(try Data(contentsOf: archiveURL), Data("existing archive".utf8))
+ }
+
+ func testPerformanceDiagnosticArchiveWriterFiltersSensitiveLogFields() throws {
+ let fileManager = FileManager.default
+ let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
+ let sourceLogURL = rootURL.appendingPathComponent("events.log")
+ let archiveURL = rootURL.appendingPathComponent("diagnostics.zip")
+ let extractionURL = rootURL.appendingPathComponent("extracted", isDirectory: true)
+ defer { try? fileManager.removeItem(at: rootURL) }
+
+ try fileManager.createDirectory(at: rootURL, withIntermediateDirectories: true)
+ try "event=quickSwitch.snapshot.success ts=2026-08-14T00:00:00Z durationMilliseconds=42 reportPathBasename=private-notes.txt title=secret\n".write(to: sourceLogURL, atomically: true, encoding: .utf8)
+ let summary = try JSONSerialization.data(withJSONObject: ["schemaVersion": 1], options: [])
+ _ = try PerformanceDiagnosticArchiveWriter.write(
+ to: archiveURL,
+ summaryJSON: summary,
+ eventLogURLs: [sourceLogURL]
+ )
+
+ let unzipProcess = Process()
+ unzipProcess.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
+ unzipProcess.arguments = ["-x", "-k", archiveURL.path, extractionURL.path]
+ try unzipProcess.run()
+ unzipProcess.waitUntilExit()
+ XCTAssertEqual(unzipProcess.terminationStatus, 0)
+ let exportedLogURL = extractionURL
+ .appendingPathComponent("Aligner-Diagnostics", isDirectory: true)
+ .appendingPathComponent("performance-events.log")
+ let exportedLog = try String(contentsOf: exportedLogURL, encoding: .utf8)
+ XCTAssertTrue(exportedLog.contains("event=quickSwitch.snapshot.success"))
+ XCTAssertTrue(exportedLog.contains("durationMilliseconds=42"))
+ XCTAssertFalse(exportedLog.contains("private-notes.txt"))
+ XCTAssertFalse(exportedLog.contains("title=secret"))
}
func testRound0OnlyDefinesQuickSwitchEntryPoint() {
@@ -173,6 +257,33 @@
)
}
+ func testWindowEnumerationPolicyRejectsLowConfidenceAXOnlyRegularWindows() {
+ XCTAssertFalse(WindowEnumerationPolicy.shouldIncludeAXOnlyRecord(
+ identifierSource: .cgWindow,
+ isMinimized: false,
+ isFullscreen: false,
+ spaceIDs: []
+ ))
+ XCTAssertTrue(WindowEnumerationPolicy.shouldIncludeAXOnlyRecord(
+ identifierSource: .cgWindow,
+ isMinimized: true,
+ isFullscreen: false,
+ spaceIDs: []
+ ))
+ XCTAssertTrue(WindowEnumerationPolicy.shouldIncludeAXOnlyRecord(
+ identifierSource: .cgWindow,
+ isMinimized: false,
+ isFullscreen: true,
+ spaceIDs: [42]
+ ))
+ XCTAssertFalse(WindowEnumerationPolicy.shouldIncludeAXOnlyRecord(
+ identifierSource: .syntheticAX,
+ isMinimized: false,
+ isFullscreen: false,
+ spaceIDs: []
+ ))
+ }
+
func testMinimizedWindowFallbackPolicyAvoidsOccupiedSyntheticIDCollisions() {
let windowID = MinimizedWindowFallbackPolicy.syntheticWindowID(
processIdentifier: 42,
@@ -212,12 +323,25 @@
let record = makeWindowRecord(
title: "Closed Document.md",
hasAXBacking: false,
- isOnscreen: false,
- spaceIDs: [5]
+ isOnscreen: false
)
XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(record))
XCTAssertNil(WindowEnumerationPolicy.window(from: record))
+ }
+
+ func testWindowEnumerationPolicyKeepsCGOnlyOffscreenWindowsWithKnownSpaceIDs() {
+ let record = makeWindowRecord(
+ title: "Other Space Document.md",
+ hasAXBacking: false,
+ isOnscreen: false,
+ spaceIDs: [5]
+ )
+
+ let window = WindowEnumerationPolicy.window(from: record)
+
+ XCTAssertEqual(window?.title, "Other Space Document.md")
+ XCTAssertEqual(window?.spaceIDs, [5])
}
func testWindowEnumerationPolicyKeepsAXBackedOffscreenWindows() {
@@ -242,6 +366,334 @@
let window = WindowEnumerationPolicy.window(from: record)
XCTAssertEqual(window?.title, "Visible Document.md")
+ }
+
+ func testWindowEnumerationPolicyKeepsKnownCGOnlyOffscreenMainWindows() {
+ let hbuilderApp = AlignerApp(
+ bundleIdentifier: "io.dcloud.HBuilderX",
+ name: "HBuilderX",
+ category: .editorIDE
+ )
+ let record = WindowEnumerationRecord(
+ id: 55_388,
+ app: hbuilderApp,
+ title: "lm-app - HBuilder X 5.07",
+ size: CGSize(width: 1048, height: 796),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+ let emptyTitleRecord = WindowEnumerationRecord(
+ id: 55_389,
+ app: hbuilderApp,
+ title: "",
+ size: CGSize(width: 1048, height: 796),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+ let tinyRecord = WindowEnumerationRecord(
+ id: 55_390,
+ app: hbuilderApp,
+ title: "HBuilderX Utility",
+ size: CGSize(width: 80, height: 40),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+
+ XCTAssertEqual(WindowEnumerationPolicy.window(from: record)?.title, "lm-app - HBuilder X 5.07")
+ XCTAssertNil(WindowEnumerationPolicy.window(from: emptyTitleRecord))
+ XCTAssertNil(WindowEnumerationPolicy.window(from: tinyRecord))
+ }
+
+ func testWindowEnumerationPolicyExcludesClosedShottrCGPlaceholderButKeepsVisibleWindow() {
+ let shottrApp = AlignerApp(
+ bundleIdentifier: "cc.ffitch.shottr",
+ name: "Shottr",
+ category: .generic
+ )
+ let closedPlaceholderRecord = WindowEnumerationRecord(
+ id: 78,
+ app: shottrApp,
+ activationPolicy: .accessory,
+ title: "Shottr",
+ size: CGSize(width: 1710, height: 997),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+ let visibleRecord = WindowEnumerationRecord(
+ id: 79,
+ app: shottrApp,
+ activationPolicy: .accessory,
+ title: "Shottr",
+ size: CGSize(width: 1710, height: 997),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: true
+ )
+ let utilityRecord = WindowEnumerationRecord(
+ id: 102,
+ app: shottrApp,
+ activationPolicy: .accessory,
+ title: "Colors",
+ size: CGSize(width: 250, height: 397),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+
+ XCTAssertNil(WindowEnumerationPolicy.window(from: closedPlaceholderRecord))
+ XCTAssertEqual(WindowEnumerationPolicy.window(from: visibleRecord)?.title, "Shottr")
+ XCTAssertNil(WindowEnumerationPolicy.window(from: utilityRecord))
+ }
+
+ func testWindowEnumerationPolicyKeepsFinderCGOnlyOffscreenMainWindows() {
+ let finderApp = AlignerApp(
+ bundleIdentifier: "com.apple.finder",
+ name: "访达",
+ category: .finder
+ )
+ let record = WindowEnumerationRecord(
+ id: 83_664,
+ app: finderApp,
+ title: "Screencapture",
+ size: CGSize(width: 920, height: 436),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+ let emptyTitleRecord = WindowEnumerationRecord(
+ id: 83_665,
+ app: finderApp,
+ title: "",
+ size: CGSize(width: 920, height: 436),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+ let tinyRecord = WindowEnumerationRecord(
+ id: 83_666,
+ app: finderApp,
+ title: "Finder Utility",
+ size: CGSize(width: 80, height: 40),
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+
+ XCTAssertEqual(WindowEnumerationPolicy.window(from: record)?.title, "Screencapture")
+ XCTAssertNil(WindowEnumerationPolicy.window(from: emptyTitleRecord))
+ XCTAssertNil(WindowEnumerationPolicy.window(from: tinyRecord))
+ }
+
+ func testFinderTabSpaceAttributionInheritsFullscreenHostSpaceForMatchingFrame() {
+ let finderApp = AlignerApp(
+ bundleIdentifier: "com.apple.finder",
+ name: "访达",
+ category: .finder,
+ processIdentifier: 74678
+ )
+ let frame = CGRect(x: 0, y: 0, width: 960, height: 1080)
+ let host = WindowEnumerationRecord(
+ id: 33112,
+ app: finderApp,
+ title: "下载",
+ size: frame.size,
+ frame: frame,
+ subrole: .standard,
+ isFullscreen: true,
+ hasAXBacking: true,
+ spaceIDs: [843]
+ )
+ let inactiveTab = WindowEnumerationRecord(
+ id: 42286,
+ app: finderApp,
+ title: "Appcache",
+ size: frame.size,
+ frame: frame,
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+
+ let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([inactiveTab, host])
+
+ XCTAssertEqual(attributed[0].spaceIDs, [843])
+ XCTAssertTrue(attributed[0].isFullscreen)
+ }
+
+ func testFinderTabSpaceAttributionDoesNotGuessNoSpaceWindowsWithoutHostOverlap() {
+ let finderApp = AlignerApp(
+ bundleIdentifier: "com.apple.finder",
+ name: "访达",
+ category: .finder,
+ processIdentifier: 74678
+ )
+ let otherApp = AlignerApp(
+ bundleIdentifier: "com.example.Other",
+ name: "Other",
+ category: .generic,
+ processIdentifier: 74678
+ )
+ let hostFrame = CGRect(x: 0, y: 0, width: 960, height: 1080)
+ let farFrame = CGRect(x: 1200, y: 0, width: 500, height: 600)
+ let host = WindowEnumerationRecord(
+ id: 33112,
+ app: finderApp,
+ title: "下载",
+ size: hostFrame.size,
+ frame: hostFrame,
+ subrole: .standard,
+ isFullscreen: true,
+ hasAXBacking: true,
+ spaceIDs: [843]
+ )
+ let farFinderTab = WindowEnumerationRecord(
+ id: 42286,
+ app: finderApp,
+ title: "Appcache",
+ size: farFrame.size,
+ frame: farFrame,
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+ let nonFinderWindow = WindowEnumerationRecord(
+ id: 50222,
+ app: otherApp,
+ title: "Appcache",
+ size: hostFrame.size,
+ frame: hostFrame,
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+
+ let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([farFinderTab, nonFinderWindow, host])
+
+ XCTAssertEqual(attributed[0].spaceIDs, [])
+ XCTAssertFalse(attributed[0].isFullscreen)
+ XCTAssertEqual(attributed[1].spaceIDs, [])
+ XCTAssertFalse(attributed[1].isFullscreen)
+ }
+
+ func testFinderTabSpaceAttributionHandlesFinderSplitViewHostsByFrame() {
+ let finderApp = AlignerApp(
+ bundleIdentifier: "com.apple.finder",
+ name: "访达",
+ category: .finder,
+ processIdentifier: 74678
+ )
+ let leftFrame = CGRect(x: 0, y: 0, width: 960, height: 1080)
+ let rightFrame = CGRect(x: 960, y: 0, width: 960, height: 1080)
+ let leftHost = WindowEnumerationRecord(
+ id: 33112,
+ app: finderApp,
+ title: "下载",
+ size: leftFrame.size,
+ frame: leftFrame,
+ subrole: .standard,
+ isFullscreen: true,
+ hasAXBacking: true,
+ spaceIDs: [843]
+ )
+ let rightHost = WindowEnumerationRecord(
+ id: 41557,
+ app: finderApp,
+ title: "应用程序",
+ size: rightFrame.size,
+ frame: rightFrame,
+ subrole: .standard,
+ isFullscreen: true,
+ hasAXBacking: true,
+ spaceIDs: [843]
+ )
+ let leftInactiveTab = WindowEnumerationRecord(
+ id: 42286,
+ app: finderApp,
+ title: "Appcache",
+ size: leftFrame.size,
+ frame: leftFrame,
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+ let rightInactiveTab = WindowEnumerationRecord(
+ id: 61385,
+ app: finderApp,
+ title: "build",
+ size: rightFrame.size,
+ frame: rightFrame,
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+
+ let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([
+ leftInactiveTab,
+ rightInactiveTab,
+ leftHost,
+ rightHost
+ ])
+
+ XCTAssertEqual(attributed[0].spaceIDs, [843])
+ XCTAssertEqual(attributed[1].spaceIDs, [843])
+ XCTAssertTrue(attributed[0].isFullscreen)
+ XCTAssertTrue(attributed[1].isFullscreen)
+ }
+
+ func testFinderTabSpaceAttributionRejectsContainingHostsWithDifferentSize() {
+ let finderApp = AlignerApp(
+ bundleIdentifier: "com.apple.finder",
+ name: "访达",
+ category: .finder,
+ processIdentifier: 74678
+ )
+ let splitRightFrame = CGRect(x: 960, y: 0, width: 960, height: 1080)
+ let fullDisplayFrame = CGRect(x: 0, y: 0, width: 1920, height: 1080)
+ let splitHost = WindowEnumerationRecord(
+ id: 33112,
+ app: finderApp,
+ title: "下载",
+ size: splitRightFrame.size,
+ frame: splitRightFrame,
+ subrole: .standard,
+ isFullscreen: true,
+ hasAXBacking: true,
+ spaceIDs: [843]
+ )
+ let containingHost = WindowEnumerationRecord(
+ id: 41557,
+ app: finderApp,
+ title: "应用程序",
+ size: fullDisplayFrame.size,
+ frame: fullDisplayFrame,
+ subrole: .standard,
+ isFullscreen: true,
+ hasAXBacking: true,
+ spaceIDs: [1112]
+ )
+ let inactiveTab = WindowEnumerationRecord(
+ id: 42286,
+ app: finderApp,
+ title: "Appcache",
+ size: splitRightFrame.size,
+ frame: splitRightFrame,
+ subrole: .standard,
+ hasAXBacking: false,
+ isOnscreen: false
+ )
+
+ let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([
+ inactiveTab,
+ containingHost,
+ splitHost
+ ])
+
+ XCTAssertEqual(attributed[0].spaceIDs, [843])
+ XCTAssertTrue(attributed[0].isFullscreen)
}
func testWindowEnumerationPolicyKeepsMinimizedAndFullscreenCGOnlyOffscreenWindows() {
@@ -364,6 +816,13 @@
XCTAssertEqual(templates.map(\.category), categories)
XCTAssertTrue(templates.allSatisfy { $0.headerHeight > 0 })
XCTAssertTrue(templates.allSatisfy { $0.contentRows > 0 })
+ }
+
+ func testSkeletonThumbnailPaletteStaysQuietForFallbackState() {
+ XCTAssertLessThanOrEqual(SkeletonThumbnailPalette.headerAlpha, 0.10)
+ XCTAssertLessThanOrEqual(SkeletonThumbnailPalette.contentRowAlpha, 0.08)
+ XCTAssertLessThanOrEqual(SkeletonThumbnailPalette.accentStripeAlpha, 0.30)
+ XCTAssertGreaterThanOrEqual(SkeletonThumbnailPalette.titleBandAlpha, 0.80)
}
@MainActor
@@ -740,6 +1199,7 @@
"schemaVersion",
"quickSwitch.showMinimizedWindows",
"quickSwitch.showFullscreenWindows",
+ "quickSwitch.waterfallViewMode",
"appearance.theme",
"appearance.language",
"spaces.displayStrategy"
@@ -752,9 +1212,10 @@
}
func testDefaultPreferencesMatchRound0Decisions() {
- XCTAssertEqual(PreferenceSchema.version, 1)
+ XCTAssertEqual(PreferenceSchema.version, 2)
XCTAssertEqual(PreferenceSchema.defaults.showMinimizedWindows, true)
XCTAssertEqual(PreferenceSchema.defaults.showFullscreenWindows, true)
+ XCTAssertEqual(PreferenceSchema.defaults.quickSwitchWaterfallViewMode, .verticalColumns)
XCTAssertEqual(PreferenceSchema.defaults.theme, .system)
XCTAssertEqual(PreferenceSchema.defaults.language, .system)
XCTAssertEqual(PreferenceSchema.defaults.spaceDisplayStrategy, .includeVisibleAndFullscreen)
@@ -775,6 +1236,7 @@
let preferences = AlignerPreferences(
showMinimizedWindows: false,
showFullscreenWindows: false,
+ quickSwitchWaterfallViewMode: .horizontalMasonry,
theme: .dark,
language: .simplifiedChinese,
spaceDisplayStrategy: .currentSpaceOnly
@@ -812,12 +1274,14 @@
userDefaults.set("not-a-theme", forKey: PreferenceKey.theme.storageKey)
userDefaults.set("not-a-language", forKey: PreferenceKey.language.storageKey)
userDefaults.set("not-a-space-strategy", forKey: PreferenceKey.spaceDisplayStrategy.storageKey)
+ userDefaults.set("not-a-waterfall-mode", forKey: PreferenceKey.quickSwitchWaterfallViewMode.storageKey)
let preferences = UserDefaultsPreferenceStore(userDefaults: userDefaults).read()
XCTAssertEqual(preferences.theme, PreferenceSchema.defaults.theme)
XCTAssertEqual(preferences.language, PreferenceSchema.defaults.language)
XCTAssertEqual(preferences.spaceDisplayStrategy, PreferenceSchema.defaults.spaceDisplayStrategy)
+ XCTAssertEqual(preferences.quickSwitchWaterfallViewMode, PreferenceSchema.defaults.quickSwitchWaterfallViewMode)
}
@MainActor
@@ -1041,6 +1505,50 @@
XCTAssertEqual(snapshot.appGroups.single?.windows.map(\.primarySpaceID), [10, 11, 12, 12, nil])
}
+ func testWindowSpaceMappingFillsUnresolvedWindowsFromSpaceInverseMap() {
+ let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
+ requestedWindowIDs: [10, 20, 30],
+ directSpaceIDsByWindowID: [10: [100]],
+ windowIDsBySpaceID: [
+ 100: [10, 20],
+ 101: [30]
+ ],
+ orderedSpaceIDs: [100, 101]
+ )
+
+ XCTAssertEqual(mapping[10], [100])
+ XCTAssertEqual(mapping[20], [100])
+ XCTAssertEqual(mapping[30], [101])
+ }
+
+ func testWindowSpaceMappingDoesNotOverwriteDirectSpaceAssignments() {
+ let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
+ requestedWindowIDs: [10],
+ directSpaceIDsByWindowID: [10: [101]],
+ windowIDsBySpaceID: [
+ 100: [10]
+ ],
+ orderedSpaceIDs: [100, 101]
+ )
+
+ XCTAssertEqual(mapping[10], [101])
+ }
+
+ func testWindowSpaceMappingLeavesAmbiguousNoSpaceWindowsUnassigned() {
+ let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
+ requestedWindowIDs: [10, 20],
+ directSpaceIDsByWindowID: [:],
+ windowIDsBySpaceID: [
+ 100: [10, 99]
+ ],
+ orderedSpaceIDs: [100]
+ )
+
+ XCTAssertEqual(mapping[10], [100])
+ XCTAssertNil(mapping[20])
+ XCTAssertNil(mapping[99])
+ }
+
func testQuickSwitchSnapshotKeepsMinimizedAndFullscreenWindows() {
let app = makeApp()
let snapshot = QuickSwitchSnapshotBuilder.snapshot(
@@ -1085,6 +1593,261 @@
XCTAssertEqual(viewModel.initialSelection, QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 1))
}
+ func testQuickSwitchViewModelBuilderUsesNumericSpaceLabelsForSingleDisplay() {
+ let app = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [makeDisplay(spaceIDs: [10, 11, 12])],
+ windows: [
+ AlignerWindow(id: 1, app: app, title: "A1", spaceIDs: [10]),
+ AlignerWindow(id: 2, app: app, title: "A2", spaceIDs: [11])
+ ]
+ )
+
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot, currentSpaceIDs: [11])
+
+ XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.map(\.label) }, ["01", "02", "03"])
+ XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.filter(\.isCurrent).map(\.label) }, ["02"])
+ XCTAssertEqual(viewModel.waterfallColumns.flatMap { $0.windows.map(\.primarySpaceLabel) }, ["01", "02"])
+ }
+
+ func testQuickSwitchSpaceFilterProjectsAppShelfAndWaterfallWithoutChangingSpaceLane() {
+ let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+ let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [makeDisplay(spaceIDs: [10, 11])],
+ windows: [
+ AlignerWindow(id: 1, app: alpha, title: "A1", spaceIDs: [10]),
+ AlignerWindow(id: 2, app: alpha, title: "A2", spaceIDs: [11]),
+ AlignerWindow(id: 3, app: beta, title: "B1", spaceIDs: [11]),
+ AlignerWindow(id: 4, app: beta, title: "No Space")
+ ]
+ )
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+
+ let filtered = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+ from: viewModel,
+ lockedSpaceID: 11
+ )
+
+ XCTAssertEqual(filtered.lockedSpaceID, 11)
+ XCTAssertEqual(filtered.displays.flatMap { $0.spaces.map(\.id) }, [10, 11])
+ XCTAssertEqual(filtered.displays.flatMap { $0.spaces.map(\.windowCount) }, [1, 2])
+ XCTAssertEqual(filtered.appShelf.map(\.app.name), ["Alpha", "Beta"])
+ XCTAssertEqual(filtered.appShelf.map(\.windowCount), [1, 1])
+ XCTAssertEqual(filtered.appShelf.flatMap(\.primarySpaceIDs), [11, 11])
+ XCTAssertEqual(filtered.waterfallColumns.flatMap { $0.windows.map(\.window.id) }, [2, 3])
+ XCTAssertEqual(filtered.waterfallColumns.flatMap { $0.windows.map(\.windowIndex) }, [0, 0])
+ XCTAssertEqual(filtered.waterfallColumns.flatMap { $0.windows.map(\.globalIndex) }, [0, 1])
+ XCTAssertEqual(filtered.initialSelection, QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 2))
+ }
+
+ func testQuickSwitchSpaceFilterKeyboardNavigationStaysInsideFilteredCollection() {
+ let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+ let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [makeDisplay(spaceIDs: [10, 11])],
+ windows: [
+ AlignerWindow(id: 1, app: alpha, title: "Alpha A1", spaceIDs: [10]),
+ AlignerWindow(id: 2, app: alpha, title: "Alpha A2", spaceIDs: [11]),
+ AlignerWindow(id: 3, app: beta, title: "Beta A2", spaceIDs: [11]),
+ AlignerWindow(id: 4, app: beta, title: "Beta No Space")
+ ]
+ )
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+ let filtered = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+ from: viewModel,
+ lockedSpaceID: 11
+ )
+ let filteredSnapshot = QuickSwitchSnapshot(
+ displays: snapshot.displays,
+ appGroups: filtered.waterfallColumns.map { column in
+ QuickSwitchAppGroup(
+ app: column.app,
+ windows: column.windows.map {
+ QuickSwitchWindowItem(window: $0.window, primarySpaceID: $0.primarySpaceID)
+ }
+ )
+ }
+ )
+
+ XCTAssertEqual(filtered.initialSelection, QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 2))
+ XCTAssertEqual(
+ QuickSwitchFocusPolicy.nextSelection(
+ from: filtered.initialSelection!,
+ in: filteredSnapshot,
+ direction: .right
+ ),
+ QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3)
+ )
+ XCTAssertEqual(
+ QuickSwitchFocusPolicy.nextSelection(
+ from: QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3),
+ in: filteredSnapshot,
+ direction: .right
+ ),
+ QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3)
+ )
+ XCTAssertFalse(filteredSnapshot.appGroups.flatMap(\.windows).contains { $0.window.id == 1 || $0.window.id == 4 })
+ }
+
+ func testQuickSwitchSpaceFilterCanLockEmptySpaceWithoutChangingSpaceLane() {
+ let app = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [makeDisplay(spaceIDs: [10, 11])],
+ windows: [
+ AlignerWindow(id: 1, app: app, title: "A1", spaceIDs: [10])
+ ]
+ )
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+
+ let filtered = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+ from: viewModel,
+ lockedSpaceID: 11
+ )
+
+ XCTAssertEqual(filtered.lockedSpaceID, 11)
+ XCTAssertEqual(filtered.displays.flatMap { $0.spaces.map(\.id) }, [10, 11])
+ XCTAssertTrue(filtered.appShelf.isEmpty)
+ XCTAssertTrue(filtered.waterfallColumns.isEmpty)
+ XCTAssertNil(filtered.initialSelection)
+ }
+
+ func testQuickSwitchSpaceFilterRestoresPreferredSelectionWhenUnlocking() {
+ let app = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [makeDisplay(spaceIDs: [10, 11])],
+ windows: [
+ AlignerWindow(id: 1, app: app, title: "A1", spaceIDs: [10]),
+ AlignerWindow(id: 2, app: app, title: "A2", spaceIDs: [11])
+ ]
+ )
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+ let preferred = QuickSwitchSelection(appGroupIndex: 0, windowIndex: 1, windowID: 2)
+
+ let unlocked = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+ from: viewModel,
+ lockedSpaceID: nil,
+ preferredSelection: preferred
+ )
+
+ XCTAssertNil(unlocked.lockedSpaceID)
+ XCTAssertEqual(unlocked.initialSelection, preferred)
+ }
+
+ func testQuickSwitchSpaceFilterDetectsOnlySingleFullscreenWindowSpacesAsDirectActivation() {
+ let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+ let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
+ let displayUUID = "display-a"
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [
+ AlignerDisplay(
+ uuid: displayUUID,
+ physical: true,
+ spaces: [
+ AlignerSpace(id: 10, type: .user, displayUUID: displayUUID, index: 1),
+ AlignerSpace(id: 11, type: .fullscreen, displayUUID: displayUUID, index: 2),
+ AlignerSpace(id: 12, type: .fullscreen, displayUUID: displayUUID, index: 3)
+ ]
+ )
+ ],
+ windows: [
+ AlignerWindow(id: 1, app: alpha, title: "Normal", spaceIDs: [10]),
+ AlignerWindow(id: 2, app: alpha, title: "Fullscreen", isFullscreen: true, spaceIDs: [11]),
+ AlignerWindow(id: 3, app: beta, title: "Split A", isFullscreen: true, spaceIDs: [12]),
+ AlignerWindow(id: 4, app: beta, title: "Split B", isFullscreen: true, spaceIDs: [12])
+ ]
+ )
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+
+ XCTAssertEqual(
+ QuickSwitchSpaceFilterPolicy.singleFullscreenSelection(inSpaceID: 11, viewModel: viewModel),
+ QuickSwitchSelection(appGroupIndex: 0, windowIndex: 1, windowID: 2)
+ )
+ XCTAssertNil(QuickSwitchSpaceFilterPolicy.singleFullscreenSelection(inSpaceID: 10, viewModel: viewModel))
+ XCTAssertNil(QuickSwitchSpaceFilterPolicy.singleFullscreenSelection(inSpaceID: 12, viewModel: viewModel))
+ }
+
+ func testQuickSwitchViewModelOrdersSplitViewAppNamesByWindowFrame() {
+ let leftApp = AlignerApp(bundleIdentifier: "com.example.Left", name: "Left App", category: .generic)
+ let rightApp = AlignerApp(bundleIdentifier: "com.example.Right", name: "Right App", category: .generic)
+ let displayUUID = "display-a"
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [
+ AlignerDisplay(
+ uuid: displayUUID,
+ physical: true,
+ spaces: [
+ AlignerSpace(id: 20, type: .fullscreen, displayUUID: displayUUID, index: 1)
+ ]
+ )
+ ],
+ windows: [
+ AlignerWindow(
+ id: 2,
+ app: rightApp,
+ title: "Right",
+ isFullscreen: true,
+ frame: CGRect(x: 960, y: 0, width: 960, height: 1080),
+ spaceIDs: [20]
+ ),
+ AlignerWindow(
+ id: 1,
+ app: leftApp,
+ title: "Left",
+ isFullscreen: true,
+ frame: CGRect(x: 0, y: 0, width: 960, height: 1080),
+ spaceIDs: [20]
+ )
+ ]
+ )
+
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+ let space = viewModel.displays.single?.spaces.single
+
+ XCTAssertEqual(space?.splitViewAppNames, ["Left App", "Right App"])
+ XCTAssertEqual(space?.appNames, ["Left App", "Right App"])
+ }
+
+ func testQuickSwitchViewModelKeepsDuplicateAppNamesForSameAppSplitView() {
+ let finder = AlignerApp(bundleIdentifier: "com.apple.finder", name: "访达", category: .finder)
+ let displayUUID = "display-a"
+ let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+ displays: [
+ AlignerDisplay(
+ uuid: displayUUID,
+ physical: true,
+ spaces: [
+ AlignerSpace(id: 21, type: .fullscreen, displayUUID: displayUUID, index: 1)
+ ]
+ )
+ ],
+ windows: [
+ AlignerWindow(
+ id: 12,
+ app: finder,
+ title: "右侧访达",
+ isFullscreen: true,
+ frame: CGRect(x: 900, y: 0, width: 900, height: 1000),
+ spaceIDs: [21]
+ ),
+ AlignerWindow(
+ id: 11,
+ app: finder,
+ title: "左侧访达",
+ isFullscreen: true,
+ frame: CGRect(x: 0, y: 0, width: 900, height: 1000),
+ spaceIDs: [21]
+ )
+ ]
+ )
+
+ let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+ let space = viewModel.displays.single?.spaces.single
+
+ XCTAssertEqual(space?.splitViewAppNames, ["访达", "访达"])
+ XCTAssertEqual(space?.appNames, ["访达"])
+ }
+
func testQuickSwitchFocusPolicySelectsFirstStableWindowInitially() {
let app = makeApp()
let snapshot = QuickSwitchSnapshotBuilder.snapshot(
--
Gitblit v1.9.3