| | |
| | | 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)) |
| | | } |
| | | |
| | |
| | | "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.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 testQuickSwitchFocusPolicySelectsFirstStableWindowInitially() { |
| | | let app = makeApp() |
| | | let snapshot = QuickSwitchSnapshotBuilder.snapshot( |