Skip to content

Commit dc36788

Browse files
grokifyclaude
andcommitted
test(desktop): add comprehensive unit tests for services
Expand test coverage for SessionManager, WindowStateManager, and AppState using mock-based dependency injection. SessionManager tests (32 total): - Session parsing (valid, empty, malformed, multiple sessions) - Status determination (running, idle, stuck at thresholds) - Session name sanitization - Tmux availability checking - Error handling WindowStateManager tests (26 total): - State loading (v2, v1 migration, corrupted, missing) - State saving (directory creation, file writes) - Window registration and updates - Pending config management AppState tests (7 total): - Initialization with default/custom dependencies - Monitoring lifecycle - Integration with mocked services Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e0c37ae commit dc36788

File tree

3 files changed

+742
-37
lines changed

3 files changed

+742
-37
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import XCTest
2+
@testable import PlexusOneDesktop
3+
4+
final class AppStateTests: XCTestCase {
5+
6+
// MARK: - Initialization Tests
7+
8+
func testAppStateInitializesWithDefaultDependencies() {
9+
let appState = AppState()
10+
11+
XCTAssertNotNil(appState.sessionManager)
12+
XCTAssertNotNil(appState.windowStateManager)
13+
XCTAssertFalse(appState.isInitialized)
14+
XCTAssertNil(appState.pendingPopOutSession)
15+
}
16+
17+
func testAppStateInitializesWithCustomDependencies() {
18+
let mockExecutor = MockCommandExecutor()
19+
mockExecutor.stubWhichTmux(available: true)
20+
21+
let sessionManager = SessionManager(commandExecutor: mockExecutor)
22+
let mockFileSystem = MockFileSystem()
23+
let windowStateManager = WindowStateManager(
24+
fileSystem: mockFileSystem,
25+
stateDirectory: URL(fileURLWithPath: "/tmp/test")
26+
)
27+
28+
let appState = AppState(
29+
sessionManager: sessionManager,
30+
windowStateManager: windowStateManager
31+
)
32+
33+
XCTAssertTrue(appState.sessionManager === sessionManager)
34+
// windowStateManager is a value type, so we can just verify it exists
35+
XCTAssertNotNil(appState.windowStateManager)
36+
}
37+
38+
// MARK: - Monitoring Tests
39+
40+
func testStartMonitoringInitializesOnce() async {
41+
let mockExecutor = MockCommandExecutor()
42+
mockExecutor.stubWhichTmux(available: true)
43+
mockExecutor.stubNoServerRunning()
44+
45+
let sessionManager = SessionManager(
46+
commandExecutor: mockExecutor,
47+
tmuxPaths: ["/opt/homebrew/bin/tmux"]
48+
)
49+
50+
let appState = AppState(sessionManager: sessionManager)
51+
52+
XCTAssertFalse(appState.isInitialized)
53+
54+
await appState.startMonitoring()
55+
56+
XCTAssertTrue(appState.isInitialized)
57+
58+
// Second call should be no-op
59+
let executionCountBefore = mockExecutor.executedCommands.count
60+
await appState.startMonitoring()
61+
let executionCountAfter = mockExecutor.executedCommands.count
62+
63+
XCTAssertEqual(executionCountBefore, executionCountAfter)
64+
}
65+
66+
func testStartMonitoringWithTmuxAvailable() async {
67+
let mockExecutor = MockCommandExecutor()
68+
mockExecutor.stubWhichTmux(available: true)
69+
mockExecutor.stubNoServerRunning()
70+
71+
let sessionManager = SessionManager(
72+
commandExecutor: mockExecutor,
73+
tmuxPaths: ["/opt/homebrew/bin/tmux"]
74+
)
75+
76+
let appState = AppState(sessionManager: sessionManager)
77+
78+
await appState.startMonitoring()
79+
80+
XCTAssertTrue(appState.isInitialized)
81+
// Should have checked tmux availability
82+
XCTAssertTrue(mockExecutor.wasExecuted(path: "/usr/bin/which"))
83+
}
84+
85+
func testStartMonitoringWithTmuxUnavailable() async {
86+
let mockExecutor = MockCommandExecutor()
87+
mockExecutor.stubWhichTmux(available: false)
88+
89+
let sessionManager = SessionManager(
90+
commandExecutor: mockExecutor,
91+
tmuxPaths: ["/nonexistent/tmux"]
92+
)
93+
94+
let appState = AppState(sessionManager: sessionManager)
95+
96+
await appState.startMonitoring()
97+
98+
// Should still complete initialization even if tmux unavailable
99+
XCTAssertTrue(appState.isInitialized)
100+
}
101+
102+
// MARK: - Pending Pop-Out Session Tests
103+
104+
func testPendingPopOutSession() {
105+
let appState = AppState()
106+
107+
XCTAssertNil(appState.pendingPopOutSession)
108+
109+
let session = Session(name: "test-session")
110+
appState.pendingPopOutSession = session
111+
112+
XCTAssertEqual(appState.pendingPopOutSession?.name, "test-session")
113+
114+
appState.pendingPopOutSession = nil
115+
XCTAssertNil(appState.pendingPopOutSession)
116+
}
117+
118+
// MARK: - Integration Tests
119+
120+
func testAppStateIntegration() async {
121+
let mockExecutor = MockCommandExecutor()
122+
let now = Date()
123+
let timestamp = Int(now.timeIntervalSince1970) - 10
124+
125+
// Set up mocks for full flow
126+
mockExecutor.stubWhichTmux(available: true)
127+
mockExecutor.stubTmuxSuccess(arguments: ["list-sessions"])
128+
mockExecutor.stubListSessions("session-1|\(timestamp)|0\nsession-2|\(timestamp)|1")
129+
130+
let sessionManager = SessionManager(
131+
commandExecutor: mockExecutor,
132+
tmuxPaths: ["/opt/homebrew/bin/tmux"]
133+
)
134+
135+
let mockFileSystem = MockFileSystem()
136+
let windowStateManager = WindowStateManager(
137+
fileSystem: mockFileSystem,
138+
stateDirectory: URL(fileURLWithPath: "/tmp/test-integration")
139+
)
140+
141+
let appState = AppState(
142+
sessionManager: sessionManager,
143+
windowStateManager: windowStateManager
144+
)
145+
146+
await appState.startMonitoring()
147+
148+
XCTAssertTrue(appState.isInitialized)
149+
XCTAssertEqual(appState.sessionManager.sessions.count, 2)
150+
}
151+
}

0 commit comments

Comments
 (0)