| | |
| | | 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() { |
| | |
| | | XCTAssertNil(WindowEnumerationPolicy.window(from: tinyRecord)) |
| | | } |
| | | |
| | | func testWindowEnumerationPolicyKeepsShottrCGOnlyAnnotationWindow() { |
| | | func testWindowEnumerationPolicyExcludesClosedShottrCGPlaceholderButKeepsVisibleWindow() { |
| | | let shottrApp = AlignerApp( |
| | | bundleIdentifier: "cc.ffitch.shottr", |
| | | name: "Shottr", |
| | | category: .generic |
| | | ) |
| | | let annotationRecord = WindowEnumerationRecord( |
| | | let closedPlaceholderRecord = WindowEnumerationRecord( |
| | | id: 78, |
| | | app: shottrApp, |
| | | activationPolicy: .accessory, |
| | |
| | | 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, |
| | |
| | | isOnscreen: false |
| | | ) |
| | | |
| | | XCTAssertEqual(WindowEnumerationPolicy.window(from: annotationRecord)?.title, "Shottr") |
| | | 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() { |
| | |
| | | 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 |
| | |
| | | "schemaVersion", |
| | | "quickSwitch.showMinimizedWindows", |
| | | "quickSwitch.showFullscreenWindows", |
| | | "quickSwitch.waterfallViewMode", |
| | | "appearance.theme", |
| | | "appearance.language", |
| | | "spaces.displayStrategy" |
| | |
| | | } |
| | | |
| | | 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) |
| | |
| | | let preferences = AlignerPreferences( |
| | | showMinimizedWindows: false, |
| | | showFullscreenWindows: false, |
| | | quickSwitchWaterfallViewMode: .horizontalMasonry, |
| | | theme: .dark, |
| | | language: .simplifiedChinese, |
| | | spaceDisplayStrategy: .currentSpaceOnly |
| | |
| | | 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 |
| | |
| | | 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( |
| | |
| | | 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( |