import XCTest @testable import CommandNotch @MainActor final class ScreenRegistryTests: XCTestCase { func testRefreshCreatesContextsForConnectedScreensUsingDefaultWorkspace() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, connectedScreenIDsProvider: { ["screen-a", "screen-b"] }, activeScreenIDProvider: { "screen-b" }, screenLookup: { _ in nil } ) XCTAssertEqual(registry.allScreens().map(\.id), ["screen-a", "screen-b"]) XCTAssertEqual( registry.allScreens().map(\.workspaceID), [workspaceRegistry.defaultWorkspaceID, workspaceRegistry.defaultWorkspaceID] ) XCTAssertEqual(registry.activeScreenID(), "screen-b") } func testAssignWorkspaceUpdatesContextAndSurvivesReconnect() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() var connectedScreenIDs = ["screen-a"] let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, connectedScreenIDsProvider: { connectedScreenIDs }, activeScreenIDProvider: { connectedScreenIDs.first }, screenLookup: { _ in nil } ) let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") registry.assignWorkspace(docsWorkspaceID, to: "screen-a") XCTAssertEqual(registry.screenContext(for: "screen-a")?.workspaceID, docsWorkspaceID) connectedScreenIDs = [] registry.refreshConnectedScreens() XCTAssertNil(registry.screenContext(for: "screen-a")) connectedScreenIDs = ["screen-a"] registry.refreshConnectedScreens() XCTAssertEqual(registry.screenContext(for: "screen-a")?.workspaceID, docsWorkspaceID) } func testDeletedWorkspaceAssignmentFallsBackToDefaultWorkspace() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let assignmentStore = InMemoryScreenAssignmentStore() let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, assignmentStore: assignmentStore, connectedScreenIDsProvider: { ["screen-a"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) let reviewWorkspaceID = workspaceRegistry.createWorkspace(named: "Review") registry.assignWorkspace(reviewWorkspaceID, to: "screen-a") workspaceRegistry.deleteWorkspace(id: reviewWorkspaceID) registry.refreshConnectedScreens() XCTAssertEqual( registry.screenContext(for: "screen-a")?.workspaceID, workspaceRegistry.defaultWorkspaceID ) XCTAssertEqual( assignmentStore.savedAssignments["screen-a"], workspaceRegistry.defaultWorkspaceID ) } func testRegistryLoadsPersistedAssignmentsFromStore() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") let assignmentStore = InMemoryScreenAssignmentStore() assignmentStore.savedAssignments = ["screen-a": docsWorkspaceID] let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, assignmentStore: assignmentStore, connectedScreenIDsProvider: { ["screen-a"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) XCTAssertEqual(registry.screenContext(for: "screen-a")?.workspaceID, docsWorkspaceID) } func testAssignWorkspacePersistsAssignments() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let assignmentStore = InMemoryScreenAssignmentStore() let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, assignmentStore: assignmentStore, connectedScreenIDsProvider: { ["screen-a"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) let reviewWorkspaceID = workspaceRegistry.createWorkspace(named: "Review") registry.assignWorkspace(reviewWorkspaceID, to: "screen-a") XCTAssertEqual(assignmentStore.savedAssignments["screen-a"], reviewWorkspaceID) } func testWorkspaceControllerTracksAssignedWorkspace() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, connectedScreenIDsProvider: { ["screen-a"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") XCTAssertEqual(registry.workspaceController(for: "screen-a").id, workspaceRegistry.defaultWorkspaceID) registry.assignWorkspace(docsWorkspaceID, to: "screen-a") XCTAssertEqual(registry.workspaceController(for: "screen-a").id, docsWorkspaceID) } func testDeleteWorkspaceReassignsConnectedAndPersistedScreensToFallback() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") let reviewWorkspaceID = workspaceRegistry.createWorkspace(named: "Review") let assignmentStore = InMemoryScreenAssignmentStore() assignmentStore.savedAssignments = ["screen-a": docsWorkspaceID, "screen-b": docsWorkspaceID] let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, assignmentStore: assignmentStore, connectedScreenIDsProvider: { ["screen-a"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) registry.assignWorkspace(docsWorkspaceID, to: "screen-a") let fallbackWorkspaceID = registry.deleteWorkspace( docsWorkspaceID, preferredFallback: reviewWorkspaceID ) XCTAssertEqual(fallbackWorkspaceID, reviewWorkspaceID) XCTAssertEqual(registry.screenContext(for: "screen-a")?.workspaceID, reviewWorkspaceID) XCTAssertEqual(assignmentStore.savedAssignments["screen-a"], reviewWorkspaceID) XCTAssertEqual(assignmentStore.savedAssignments["screen-b"], reviewWorkspaceID) } func testAssignedScreenCountIncludesDisconnectedAssignments() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") let assignmentStore = InMemoryScreenAssignmentStore() assignmentStore.savedAssignments = ["screen-a": docsWorkspaceID, "screen-b": docsWorkspaceID] let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, assignmentStore: assignmentStore, connectedScreenIDsProvider: { ["screen-a"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) XCTAssertEqual(registry.assignedScreenCount(to: docsWorkspaceID), 2) XCTAssertEqual(registry.assignedScreenIDs(to: docsWorkspaceID), ["screen-a", "screen-b"]) } func testClaimWorkspacePresentationTracksPresenterPerWorkspace() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, connectedScreenIDsProvider: { ["screen-a", "screen-b"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) registry.assignWorkspace(docsWorkspaceID, to: "screen-a") registry.assignWorkspace(docsWorkspaceID, to: "screen-b") XCTAssertNil(registry.claimWorkspacePresentation(for: "screen-a")) XCTAssertEqual(registry.presentingScreenID(for: docsWorkspaceID), "screen-a") XCTAssertEqual(registry.claimWorkspacePresentation(for: "screen-b"), "screen-a") XCTAssertEqual(registry.presentingScreenID(for: docsWorkspaceID), "screen-b") registry.releaseWorkspacePresentation(for: "screen-b") XCTAssertNil(registry.presentingScreenID(for: docsWorkspaceID)) } func testAssignWorkspaceReleasesPreviousPresentationOwnership() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") let reviewWorkspaceID = workspaceRegistry.createWorkspace(named: "Review") let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, connectedScreenIDsProvider: { ["screen-a"] }, activeScreenIDProvider: { "screen-a" }, screenLookup: { _ in nil } ) registry.assignWorkspace(docsWorkspaceID, to: "screen-a") _ = registry.claimWorkspacePresentation(for: "screen-a") XCTAssertEqual(registry.presentingScreenID(for: docsWorkspaceID), "screen-a") registry.assignWorkspace(reviewWorkspaceID, to: "screen-a") XCTAssertNil(registry.presentingScreenID(for: docsWorkspaceID)) XCTAssertEqual(registry.screenContext(for: "screen-a")?.workspaceID, reviewWorkspaceID) } func testConnectedScreenSummariesReflectActiveScreenAndAssignments() { let workspaceRegistry = makeWorkspaceRegistry() let settingsController = makeSettingsController() let docsWorkspaceID = workspaceRegistry.createWorkspace(named: "Docs") let registry = ScreenRegistry( workspaceRegistry: workspaceRegistry, settingsController: settingsController, connectedScreenIDsProvider: { ["screen-a", "screen-b"] }, activeScreenIDProvider: { "screen-b" }, screenLookup: { _ in nil } ) registry.assignWorkspace(docsWorkspaceID, to: "screen-a") XCTAssertEqual( registry.connectedScreenSummaries(), [ ConnectedScreenSummary( id: "screen-a", displayName: "Screen 1", isActive: false, assignedWorkspaceID: docsWorkspaceID ), ConnectedScreenSummary( id: "screen-b", displayName: "Screen 2", isActive: true, assignedWorkspaceID: workspaceRegistry.defaultWorkspaceID ) ] ) } private func makeWorkspaceRegistry() -> WorkspaceRegistry { let settingsProvider = ScreenRegistryTestSettingsProvider() let sessionFactory = ScreenRegistryUnusedTerminalSessionFactory() return WorkspaceRegistry( initialWorkspaces: [], controllerFactory: { summary in WorkspaceController( summary: summary, sessionFactory: sessionFactory, settingsProvider: settingsProvider, bootstrapDefaultTab: false ) } ) } private func makeSettingsController() -> AppSettingsController { AppSettingsController(store: TestAppSettingsStore()) } } private final class InMemoryScreenAssignmentStore: ScreenAssignmentStoreType { var savedAssignments: [ScreenID: WorkspaceID] = [:] func loadScreenAssignments() -> [ScreenID: WorkspaceID] { savedAssignments } func saveScreenAssignments(_ assignments: [ScreenID: WorkspaceID]) { savedAssignments = assignments } } private final class TestAppSettingsStore: AppSettingsStoreType { private var storedSettings = AppSettings.default func load() -> AppSettings { storedSettings } func save(_ settings: AppSettings) { storedSettings = settings } } private final class ScreenRegistryTestSettingsProvider: TerminalSessionConfigurationProviding { let terminalSessionConfiguration = TerminalSessionConfiguration(fontSize: 13, theme: .terminalApp, shellPath: "") let hotkeySettings = AppSettings.default.hotkeys let terminalSizePresets = TerminalSizePresetStore.loadDefaults() } private struct ScreenRegistryUnusedTerminalSessionFactory: TerminalSessionFactoryType { @MainActor func makeSession(fontSize: CGFloat, theme: TerminalTheme, shellPath: String) -> TerminalSession { fatalError("ScreenRegistryTests should not create live terminal sessions.") } }