From 0045898e8cd7b2d8972bb24c8ac27e514bdd8c70 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 11 Jun 2026 16:46:31 +0800
Subject: [PATCH] Implement Quick Switch Space filtering

---
 C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift |  201 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 201 insertions(+), 0 deletions(-)

diff --git a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift b/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
index 298f686..e583820 100644
--- a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
+++ b/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
@@ -1174,6 +1174,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(
@@ -1235,6 +1279,163 @@
         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(

--
Gitblit v1.9.3