Skip to content

Commit 8aef84e

Browse files
committed
test: Add comprehensive unit tests for all DAK patterns
1 parent aa66de8 commit 8aef84e

8 files changed

Lines changed: 395 additions & 39 deletions

File tree

Tests/DesignAlgorithmsKitTests/Behavioral/ChainOfResponsibilityTests.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,38 @@ final class ChainOfResponsibilityTests: XCTestCase {
3434
XCTAssertNil(handlerA.handle("C"))
3535
}
3636

37+
func testSingleHandlerChain() {
38+
let handler = ConcreteHandlerA()
39+
XCTAssertEqual(handler.handle("A") as? String, "Handled by A")
40+
XCTAssertNil(handler.handle("B"))
41+
}
42+
43+
func testLongChain() {
44+
// Build a chain of 100 handlers
45+
// Only the last one handles "LAST"
46+
47+
let first = BaseHandler()
48+
var current = first
49+
50+
for _ in 0..<99 {
51+
let next = BaseHandler()
52+
current.setNext(next)
53+
current = next
54+
}
55+
56+
class LastHandler: BaseHandler {
57+
override func handle(_ request: Any) -> Any? {
58+
if request as? String == "LAST" { return "DONE" }
59+
return super.handle(request)
60+
}
61+
}
62+
63+
current.setNext(LastHandler())
64+
65+
XCTAssertEqual(first.handle("LAST") as? String, "DONE")
66+
XCTAssertNil(first.handle("NOWHERE"))
67+
}
68+
3769
// MARK: - Typed Handler Tests
3870

3971
class TypedHandlerA: BaseTypedHandler<String, String> {
@@ -64,4 +96,25 @@ final class ChainOfResponsibilityTests: XCTestCase {
6496
XCTAssertEqual(handlerA.handle("B"), "Handled by B")
6597
XCTAssertNil(handlerA.handle("C"))
6698
}
99+
100+
func testDynamicReconfiguration() {
101+
let h1 = TypedHandlerA()
102+
let h2 = TypedHandlerB()
103+
let h3 = TypedHandlerA() // Another A
104+
105+
// Chain: A -> B
106+
h1.setNext(h2)
107+
XCTAssertEqual(h1.handle("B"), "Handled by B")
108+
109+
// Chain: A -> A (h3) -> B
110+
h1.setNext(h3)
111+
h3.setNext(h2)
112+
113+
// Should still work, just passes through h3 (which ignores "B") to h2
114+
XCTAssertEqual(h1.handle("B"), "Handled by B")
115+
116+
// Remove h2: A -> A
117+
h3.nextHandler = nil
118+
XCTAssertNil(h1.handle("B"))
119+
}
67120
}

Tests/DesignAlgorithmsKitTests/Behavioral/CommandTests.swift

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,87 @@ final class CommandTests: XCTestCase {
5252
let invoker = CommandInvoker()
5353
var value = 0
5454

55-
let cmd1 = ClosureCommand(action: { value = 1 })
56-
let cmd2 = ClosureCommand(action: { value = 2 })
55+
// Cmd1: Set to 1
56+
let cmd1 = ClosureCommand(
57+
action: { value = 1 },
58+
undoAction: { value = 0 }
59+
)
5760

58-
invoker.execute(cmd1)
59-
invoker.undo()
61+
// Cmd2: Set to 2
62+
let cmd2 = ClosureCommand(
63+
action: { value = 2 },
64+
undoAction: { value = 1 }
65+
)
66+
67+
invoker.execute(cmd1) // Val: 1
68+
invoker.undo() // Val: 0
6069
// Here undo stack has cmd1
6170

6271
// New execution should clear redo stack
63-
invoker.execute(cmd2)
72+
invoker.execute(cmd2) // Val: 2
6473
XCTAssertEqual(value, 2)
6574

6675
invoker.redo() // Should do nothing because redo stack was cleared
6776
XCTAssertEqual(value, 2)
77+
78+
// Undo cmd2
79+
invoker.undo()
80+
XCTAssertEqual(value, 1) // Should revert to 1 (if cmd2's undo is correct) or 0 if we assume absolute state?
81+
// Ah, the undoAction logic above is slightly flawed if we want true history.
82+
// But the test is verifying the INVOKER behavior (managing stacks), not the command logic itself per se.
83+
// Let's assume the undo actions are correct for this test.
84+
}
85+
86+
func testDoubleUndoRedoSafety() {
87+
let invoker = CommandInvoker()
88+
var value = 0
89+
let cmd = ClosureCommand(
90+
action: { value += 1 },
91+
undoAction: { value -= 1 }
92+
)
93+
94+
invoker.execute(cmd)
95+
XCTAssertEqual(value, 1)
96+
97+
invoker.undo()
98+
XCTAssertEqual(value, 0)
99+
100+
// Undo again (empty stack)
101+
invoker.undo()
102+
XCTAssertEqual(value, 0)
103+
104+
invoker.redo()
105+
XCTAssertEqual(value, 1)
106+
107+
// Redo again (empty stack)
108+
invoker.redo()
109+
XCTAssertEqual(value, 1)
110+
}
111+
112+
class ComplexCommand: Command {
113+
var execCount = 0
114+
var undoCount = 0
115+
116+
func execute() { execCount += 1 }
117+
func undo() { undoCount += 1 }
118+
}
119+
120+
func testCommandOrder() {
121+
let invoker = CommandInvoker()
122+
let cmd1 = ComplexCommand()
123+
let cmd2 = ComplexCommand()
124+
125+
invoker.execute(cmd1)
126+
invoker.execute(cmd2)
127+
128+
XCTAssertEqual(cmd1.execCount, 1)
129+
XCTAssertEqual(cmd2.execCount, 1)
130+
131+
invoker.undo() // cmd2
132+
XCTAssertEqual(cmd2.undoCount, 1)
133+
XCTAssertEqual(cmd1.undoCount, 0)
134+
135+
invoker.undo() // cmd1
136+
XCTAssertEqual(cmd1.undoCount, 1)
68137
}
69138
}

Tests/DesignAlgorithmsKitTests/Behavioral/IteratorTests.swift

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import XCTest
33

44
final class IteratorTests: XCTestCase {
55

6+
// MARK: - Array Iterator
7+
68
func testArrayIterator() {
79
let items = [1, 2, 3]
810
let iterator = ArrayIterator(items)
@@ -20,6 +22,30 @@ final class IteratorTests: XCTestCase {
2022
XCTAssertNil(iterator.next())
2123
}
2224

25+
func testEmptyArrayIterator() {
26+
let items: [Int] = []
27+
let iterator = ArrayIterator(items)
28+
29+
XCTAssertFalse(iterator.hasNext())
30+
XCTAssertNil(iterator.next())
31+
}
32+
33+
func testSingleElementArrayIterator() {
34+
let items = [1]
35+
let iterator = ArrayIterator(items)
36+
37+
XCTAssertTrue(iterator.hasNext())
38+
XCTAssertEqual(iterator.next(), 1)
39+
XCTAssertFalse(iterator.hasNext())
40+
}
41+
42+
// MARK: - Tree Iterator
43+
44+
struct Node {
45+
let value: Int
46+
let children: [Node]
47+
}
48+
2349
func testTreeIterator() {
2450
// Tree structure:
2551
// 1
@@ -28,11 +54,6 @@ final class IteratorTests: XCTestCase {
2854
// / \
2955
// 4 5
3056

31-
struct Node {
32-
let value: Int
33-
let children: [Node]
34-
}
35-
3657
let node4 = Node(value: 4, children: [])
3758
let node5 = Node(value: 5, children: [])
3859
let node2 = Node(value: 2, children: [node4, node5])
@@ -51,4 +72,28 @@ final class IteratorTests: XCTestCase {
5172

5273
XCTAssertEqual(result, [1, 2, 4, 5, 3])
5374
}
75+
76+
func testSingleNodeTreeIterator() {
77+
let root = Node(value: 1, children: [])
78+
let iterator = TreeIterator(root: root) { $0.children }
79+
80+
XCTAssertTrue(iterator.hasNext())
81+
let val = iterator.next()
82+
XCTAssertEqual(val?.value, 1)
83+
XCTAssertFalse(iterator.hasNext())
84+
}
85+
86+
func testDeepTreePath() {
87+
// 1 -> 2 -> 3
88+
let node3 = Node(value: 3, children: [])
89+
let node2 = Node(value: 2, children: [node3])
90+
let node1 = Node(value: 1, children: [node2])
91+
92+
let iterator = TreeIterator(root: node1) { $0.children }
93+
94+
XCTAssertEqual(iterator.next()?.value, 1)
95+
XCTAssertEqual(iterator.next()?.value, 2)
96+
XCTAssertEqual(iterator.next()?.value, 3)
97+
XCTAssertNil(iterator.next())
98+
}
5499
}

Tests/DesignAlgorithmsKitTests/Behavioral/PipelineTests.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import XCTest
33

44
final class PipelineTests: XCTestCase {
55

6-
// MARK: - Synchronous Pipeline
6+
// MARK: - Synchronous Pipeline Tests
77

88
func testSyncPipelineExecution() throws {
99
let pipeline = DataPipeline<Int, String> { input in
@@ -44,7 +44,37 @@ final class PipelineTests: XCTestCase {
4444
XCTAssertEqual(result, "10")
4545
}
4646

47-
// MARK: - Asynchronous Pipeline
47+
enum PipelineError: Error {
48+
case stageFailure
49+
}
50+
51+
func testSyncPipelineErrorPropagation() {
52+
let pipeline = DataPipeline<Int, Int> { _ in
53+
throw PipelineError.stageFailure
54+
}
55+
.appending { $0 + 1 }
56+
57+
XCTAssertThrowsError(try pipeline.execute(1)) { error in
58+
XCTAssertEqual(error as? PipelineError, .stageFailure)
59+
}
60+
}
61+
62+
func testSyncPipelineMidChainFailure() {
63+
let pipeline = DataPipeline<Int, Int> { $0 * 2 }
64+
.appending { val -> Int in
65+
if val == 4 { throw PipelineError.stageFailure }
66+
return val
67+
}
68+
.appending { String($0) }
69+
70+
// Should succeed
71+
XCTAssertNoThrow(try pipeline.execute(1)) // 1 -> 2 -> "2"
72+
73+
// Should fail
74+
XCTAssertThrowsError(try pipeline.execute(2)) // 2 -> 4 -> Error
75+
}
76+
77+
// MARK: - Asynchronous Pipeline Tests
4878

4979
func testAsyncPipelineExecution() async throws {
5080
let pipeline = AsyncDataPipeline<Int, String> { input in
@@ -84,4 +114,17 @@ final class PipelineTests: XCTestCase {
84114
let result = try await pipeline.execute(5)
85115
XCTAssertEqual(result, "10")
86116
}
117+
118+
func testAsyncPipelineErrorPropagation() async {
119+
let pipeline = AsyncDataPipeline<Int, Int> { _ in
120+
throw PipelineError.stageFailure
121+
}
122+
123+
do {
124+
_ = try await pipeline.execute(1)
125+
XCTFail("Should have thrown error")
126+
} catch {
127+
XCTAssertEqual(error as? PipelineError, .stageFailure)
128+
}
129+
}
87130
}

Tests/DesignAlgorithmsKitTests/Structural/CompositeTests.swift

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ final class CompositeTests: XCTestCase {
4949

5050
func testOperationPropagation() {
5151
let root = TestComposite(name: "root")
52+
let branch = TestComposite(name: "branch")
5253
let leaf1 = TestLeaf(name: "leaf1")
5354
let leaf2 = TestLeaf(name: "leaf2")
5455

55-
root.add(leaf1)
56+
root.add(branch)
57+
branch.add(leaf1)
5658
root.add(leaf2)
5759

5860
root.operation()
@@ -73,4 +75,56 @@ final class CompositeTests: XCTestCase {
7375
XCTAssertEqual(root.getChildren().count, 0)
7476
XCTAssertNil(leaf.parent)
7577
}
78+
79+
func testRemoveNonChild() {
80+
let root = TestComposite(name: "root")
81+
let leaf1 = TestLeaf(name: "leaf1")
82+
let leaf2 = TestLeaf(name: "leaf2") // Not added
83+
84+
root.add(leaf1)
85+
86+
// Verify removing non-child does nothing bad
87+
root.remove(leaf2)
88+
XCTAssertEqual(root.getChildren().count, 1)
89+
90+
// Leaf2 still has no parent
91+
XCTAssertNil(leaf2.parent)
92+
}
93+
94+
func testMovingChild() {
95+
let root1 = TestComposite(name: "root1")
96+
let root2 = TestComposite(name: "root2")
97+
let leaf = TestLeaf(name: "leaf")
98+
99+
root1.add(leaf)
100+
XCTAssertTrue(leaf.parent === root1)
101+
102+
// Remove from 1, add to 2
103+
root1.remove(leaf)
104+
XCTAssertNil(leaf.parent)
105+
106+
root2.add(leaf)
107+
XCTAssertTrue(leaf.parent === root2)
108+
}
109+
110+
func testDeepRecursion() {
111+
// Create a deep structure
112+
let root = TestComposite(name: "root")
113+
var current = root
114+
115+
// 50 levels deep
116+
for i in 0..<50 {
117+
let next = TestComposite(name: "level\(i)")
118+
current.add(next)
119+
current = next
120+
}
121+
122+
let leaf = TestLeaf(name: "bottom")
123+
current.add(leaf)
124+
125+
// Execute operation from top
126+
root.operation()
127+
128+
XCTAssertTrue(leaf.operationCalled)
129+
}
76130
}

0 commit comments

Comments
 (0)