Skip to content

Commit b253cae

Browse files
committed
Unit Test Everything!
1 parent a1ef3b2 commit b253cae

File tree

6 files changed

+102
-73
lines changed

6 files changed

+102
-73
lines changed

CodeEdit/Features/TerminalEmulator/Views/CEActiveTaskTerminalView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class CEActiveTaskTerminalView: CELocalShellTerminalView {
1212
var activeTask: CEActiveTask
1313

1414
private var cachedCaretColor: NSColor?
15-
private var isUserCommandRunning: Bool = false
15+
private(set) var isUserCommandRunning: Bool = false
1616
private var enableOutput: Bool = false
1717

1818
init(activeTask: CEActiveTask) {

CodeEditTests/Features/Documents/DocumentsUnitTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class DocumentsUnitTests: XCTestCase {
2222
super.setUp()
2323
hapticFeedbackPerformerMock = NSHapticFeedbackPerformerMock()
2424
navigatorViewModel = .init()
25-
workspace.taskManager = TaskManager(workspaceSettings: CEWorkspaceSettingsData())
25+
workspace.taskManager = TaskManager(workspaceSettings: CEWorkspaceSettingsData(), workspaceURL: nil)
2626
window = NSWindow()
2727
splitViewController = .init(
2828
workspace: workspace,

CodeEditTests/Features/Tasks/CEActiveTaskTests.swift

Lines changed: 48 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
// Created by Tommy Ludwig on 08.07.24.
66
//
77

8-
import XCTest
8+
import Testing
99
@testable import CodeEdit
1010

11-
final class CEActiveTaskTests: XCTestCase {
12-
var task: CETask!
13-
var activeTask: CEActiveTask!
14-
15-
override func setUpWithError() throws {
16-
try super.setUpWithError()
11+
@MainActor
12+
@Suite
13+
class CEActiveTaskTests {
14+
var task: CETask
15+
var activeTask: CEActiveTask
1716

17+
init() {
1818
task = CETask(
1919
name: "Test Task",
2020
command: "echo $STATE",
@@ -23,76 +23,60 @@ final class CEActiveTaskTests: XCTestCase {
2323
activeTask = CEActiveTask(task: task)
2424
}
2525

26-
override func tearDownWithError() throws {
27-
task = nil
28-
activeTask = nil
29-
try super.tearDownWithError()
30-
}
31-
26+
@Test
3227
func testInitialization() throws {
33-
XCTAssertEqual(activeTask.task, task, "Active task should be initialized with the provided CETask.")
28+
#expect(activeTask.task == task, "Active task should be initialized with the provided CETask.")
3429
}
3530

36-
func testRunMethod() {
37-
activeTask.run()
38-
activeTask.process?.waitUntilExit()
39-
40-
let testExpectation = XCTestExpectation()
31+
@Test(.timeLimit(.minutes(1)))
32+
func testRunMethod() async throws {
33+
activeTask.run(workspaceURL: nil)
34+
activeTask.waitForExit()
4135

42-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
43-
XCTAssertEqual(self.activeTask.status, .finished)
44-
XCTAssertTrue(self.activeTask.output.contains("Testing"))
45-
testExpectation.fulfill()
36+
await waitForExpectation {
37+
activeTask.status == .finished
38+
} onTimeout: {
39+
Issue.record("Status never changed to finished.")
4640
}
47-
wait(for: [testExpectation], timeout: 1)
48-
}
4941

50-
// the renew method is needed because a Process can only be run once
51-
func testRenewMethod() {
52-
activeTask.run()
53-
let testExpectation1 = XCTestExpectation()
54-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
55-
testExpectation1.fulfill()
56-
}
57-
wait(for: [testExpectation1], timeout: 1)
42+
let output = try #require(activeTask.output)
43+
#expect(output.getBufferAsString().contains("Testing"))
44+
}
5845

59-
activeTask.renew()
60-
activeTask.run()
46+
@Test(.timeLimit(.minutes(1)))
47+
func testHandleProcessFinished() async throws {
48+
task.command = "aNon-existentCommand"
49+
activeTask.run(workspaceURL: nil)
50+
activeTask.waitForExit()
6151

62-
let testExpectation2 = XCTestExpectation()
63-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
64-
XCTAssertEqual(self.activeTask.status, .finished)
65-
testExpectation2.fulfill()
52+
await waitForExpectation {
53+
activeTask.status == .failed
54+
} onTimeout: {
55+
Issue.record("Status never changed to failed.")
6656
}
67-
wait(for: [testExpectation2], timeout: 1)
6857
}
6958

70-
func testHandleProcessFinished() {
71-
task.command = "aNon-existentCommand"
72-
activeTask.run()
73-
let testExpectation1 = XCTestExpectation()
74-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
75-
XCTAssertEqual(self.activeTask.status, .failed)
76-
testExpectation1.fulfill()
59+
@Test
60+
func testClearOutput() async throws {
61+
activeTask.run(workspaceURL: nil)
62+
activeTask.waitForExit()
63+
64+
await waitForExpectation {
65+
activeTask.status == .finished
66+
} onTimeout: {
67+
Issue.record("Status never changed to finished.")
7768
}
78-
wait(for: [testExpectation1], timeout: 1)
79-
}
8069

81-
func testClearOutput() {
82-
activeTask.run()
83-
let testExpectation1 = XCTestExpectation()
84-
Task {
85-
await activeTask.clearOutput()
86-
testExpectation1.fulfill()
70+
#expect(
71+
activeTask.output?.getBufferAsString().isEmpty == false,
72+
"Task output should not be empty after task completion."
73+
)
74+
activeTask.clearOutput()
75+
76+
await waitForExpectation {
77+
activeTask.output?.getBufferAsString().isEmpty == true
78+
} onTimeout: {
79+
Issue.record("Task output should be empty after clearOutput is called.")
8780
}
88-
wait(for: [testExpectation1], timeout: 1)
89-
XCTAssertTrue(activeTask.output.isEmpty)
9081
}
91-
// func testClearOutputMethod() async {
92-
// // Assuming the task generates some output
93-
// await activeTask.run()
94-
// XCTAssertFalse(activeTask.output.isEmpty, "Task output should not be empty after task completion.")
95-
// await activeTask.clearOutput()
96-
// XCTAssertTrue(activeTask.output.isEmpty, "Task output should be empty after clearOutput is called.")
97-
// }
9882
}

CodeEditTests/Features/Tasks/TaskManagerTests.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class TaskManagerTests: XCTestCase {
2222
XCTFail("Error decoding JSON: \(error.localizedDescription)")
2323
}
2424

25-
taskManager = TaskManager(workspaceSettings: mockWorkspaceSettings)
25+
taskManager = TaskManager(workspaceSettings: mockWorkspaceSettings, workspaceURL: nil)
2626
}
2727

2828
override func tearDown() {
@@ -44,7 +44,9 @@ final class TaskManagerTests: XCTestCase {
4444

4545
let testExpectation = XCTestExpectation()
4646
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
47-
XCTAssertTrue(((self.taskManager.activeTasks[task.id]?.output.contains("Hello World")) != nil))
47+
XCTAssertTrue(
48+
self.taskManager.activeTasks[task.id]?.output?.getBufferAsString().contains("Hello World") != nil
49+
)
4850
testExpectation.fulfill()
4951
}
5052
wait(for: [testExpectation], timeout: 1)
@@ -93,7 +95,7 @@ final class TaskManagerTests: XCTestCase {
9395

9496
let verifySuspensionExpectation = XCTestExpectation(description: "Verify task is suspended and resume it")
9597
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
96-
XCTAssertEqual(self.taskManager.activeTasks[task.id]?.process?.isRunning, true)
98+
XCTAssertEqual(self.taskManager.activeTasks[task.id]?.output?.isUserCommandRunning, true)
9799
XCTAssertEqual(self.taskManager.taskStatus(taskID: task.id), .stopped)
98100
self.taskManager.resumeTask(taskID: task.id)
99101
verifySuspensionExpectation.fulfill()

CodeEditTests/Features/TerminalEmulator/ShellIntegrationTests.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import XCTest
1313
final class ShellIntegrationTests: XCTestCase {
1414
func testBash() throws {
1515
var environment: [String] = []
16-
let args = try ShellIntegration.setUpIntegration(for: .bash, environment: &environment, useLogin: false)
16+
let args = try ShellIntegration.setUpIntegration(
17+
for: .bash,
18+
environment: &environment,
19+
useLogin: false,
20+
interactive: true
21+
)
1722
XCTAssertTrue(
1823
environment.contains("\(ShellIntegration.Variables.ceInjection)=1"), "Does not contain injection flag"
1924
)
@@ -29,7 +34,12 @@ final class ShellIntegrationTests: XCTestCase {
2934

3035
func testBashLogin() throws {
3136
var environment: [String] = []
32-
let args = try ShellIntegration.setUpIntegration(for: .bash, environment: &environment, useLogin: true)
37+
let args = try ShellIntegration.setUpIntegration(
38+
for: .bash,
39+
environment: &environment,
40+
useLogin: true,
41+
interactive: true
42+
)
3343
XCTAssertTrue(
3444
environment.contains("\(ShellIntegration.Variables.ceInjection)=1"), "Does not contain injection flag"
3545
)
@@ -43,7 +53,12 @@ final class ShellIntegrationTests: XCTestCase {
4353

4454
func testZsh() throws {
4555
var environment: [String] = []
46-
let args = try ShellIntegration.setUpIntegration(for: .zsh, environment: &environment, useLogin: false)
56+
let args = try ShellIntegration.setUpIntegration(
57+
for: .zsh,
58+
environment: &environment,
59+
useLogin: false,
60+
interactive: true
61+
)
4762
XCTAssertTrue(args.contains("-i"), "Interactive flag")
4863
XCTAssertTrue(!args.contains("-il"), "No Interactive/Login flag")
4964

@@ -72,7 +87,12 @@ final class ShellIntegrationTests: XCTestCase {
7287

7388
func testZshLogin() throws {
7489
var environment: [String] = []
75-
let args = try ShellIntegration.setUpIntegration(for: .zsh, environment: &environment, useLogin: true)
90+
let args = try ShellIntegration.setUpIntegration(
91+
for: .zsh,
92+
environment: &environment,
93+
useLogin: true,
94+
interactive: true
95+
)
7696
XCTAssertTrue(!args.contains("-i"), "No Interactive flag")
7797
XCTAssertTrue(args.contains("-il"), "Interactive/Login flag")
7898

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// waitForExpectation.swift
3+
// CodeEditTests
4+
//
5+
// Created by Khan Winter on 7/15/25.
6+
//
7+
8+
func waitForExpectation(
9+
timeout: ContinuousClock.Duration = .seconds(2.0),
10+
_ expectation: () throws -> Bool,
11+
onTimeout: () throws -> Void
12+
) async rethrows {
13+
let start = ContinuousClock.now
14+
while .now - start < timeout {
15+
if try expectation() {
16+
return
17+
} else {
18+
await Task.yield()
19+
}
20+
}
21+
22+
try onTimeout()
23+
}

0 commit comments

Comments
 (0)