Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 122 additions & 11 deletions Tests/DesignAlgorithmsKitTests/Behavioral/ObserverTests.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
//
// ObserverTests.swift
// DesignAlgorithmsKitTests
//
// Unit tests for Observer Pattern
//

import XCTest
@testable import DesignAlgorithmsKit

final class ObserverTests: XCTestCase {

// MARK: - Basic Functionality

func testObserverPattern() {
// Given
class TestObservable: BaseObservable {}
Expand Down Expand Up @@ -66,9 +62,7 @@ final class ObserverTests: XCTestCase {
let id: String
var receivedEvents: [Any] = []

init(id: String) {
self.id = id
}
init(id: String) { self.id = id }

func didReceiveNotification(from observable: any Observable, event: Any) {
receivedEvents.append(event)
Expand All @@ -88,5 +82,122 @@ final class ObserverTests: XCTestCase {
XCTAssertEqual(observer1.receivedEvents.count, 1)
XCTAssertEqual(observer2.receivedEvents.count, 1)
}

// MARK: - Weak Reference Tests

func testWeakReference() {
class TestObservable: BaseObservable {}
class TestObserver: Observer {
func didReceiveNotification(from observable: any Observable, event: Any) {}
}

let observable = TestObservable()

// Create an observer in a local scope so it gets deallocated
var observer: TestObserver? = TestObserver()
weak var weakObserver = observer

observable.addObserver(observer!)
XCTAssertNotNil(weakObserver)

// Remove strong reference
observer = nil

// Verify deallocation
XCTAssertNil(weakObserver, "Observer should have been deallocated")

// Verify notification doesn't crash
observable.notifyObservers(event: "test")

// Verify cleanup on next add
// The implementation cleans up on addObserver
observable.addObserver(TestObserver())
// Cannot easily inspect internal array count without reflection or subclassing exposing it,
// but 'addObserver' is the trigger for cleanup in BaseObservable.
}

// MARK: - Reentrancy and Concurrency

func testReentrancySafe() {
// Test removing self during notification
class TestObservable: BaseObservable {}
class TestObserver: Observer {
var observable: TestObservable?
var receivedCount = 0

func didReceiveNotification(from observable: any Observable, event: Any) {
receivedCount += 1
if let obs = self.observable {
obs.removeObserver(self)
}
}
}

let observable = TestObservable()
let observer = TestObserver()
observer.observable = observable

observable.addObserver(observer)

// First notification triggers removal
observable.notifyObservers(event: "1")
XCTAssertEqual(observer.receivedCount, 1)

// Second notification should not reach observer
observable.notifyObservers(event: "2")
XCTAssertEqual(observer.receivedCount, 1)
}

func testConcurrentAccess() {
class TestObservable: BaseObservable {}
class TestObserver: Observer {
func didReceiveNotification(from observable: any Observable, event: Any) {
// Do work
}
}

let observable = TestObservable()
let iterations = 1000
let expectation = self.expectation(description: "Concurrent observer access")
expectation.expectedFulfillmentCount = iterations

DispatchQueue.concurrentPerform(iterations: iterations) { i in
let observer = TestObserver()
observable.addObserver(observer)

if i % 2 == 0 {
observable.notifyObservers(event: i)
}

if i % 3 == 0 {
observable.removeObserver(observer)
}

expectation.fulfill()
}

waitForExpectations(timeout: 5.0)
}

func testDuplicateAddition() {
class TestObservable: BaseObservable {}
class TestObserver: Observer {
var count = 0
func didReceiveNotification(from observable: any Observable, event: Any) {
count += 1
}
}

let observable = TestObservable()
let observer = TestObserver()

// Add twice
observable.addObserver(observer)
observable.addObserver(observer)

observable.notifyObservers(event: "test")

// Should only be notified once
XCTAssertEqual(observer.count, 1)
}
}

130 changes: 56 additions & 74 deletions Tests/DesignAlgorithmsKitTests/Behavioral/StrategyTests.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
//
// StrategyTests.swift
// DesignAlgorithmsKitTests
//
// Unit tests for Strategy Pattern
//

import XCTest
@testable import DesignAlgorithmsKit

final class StrategyTests: XCTestCase {

// MARK: - Basic Implementation

func testStrategyPattern() {
// Given
struct AdditionStrategy: Strategy {
let strategyID = "addition"

func execute(_ a: Int, _ b: Int) -> Int {
return a + b
}
func execute(_ a: Int, _ b: Int) -> Int { return a + b }
}

struct MultiplicationStrategy: Strategy {
let strategyID = "multiplication"

func execute(_ a: Int, _ b: Int) -> Int {
return a * b
}
func execute(_ a: Int, _ b: Int) -> Int { return a * b }
}

// When
Expand All @@ -39,92 +29,84 @@ final class StrategyTests: XCTestCase {
XCTAssertEqual(result2, 15)
}

func testStrategyContext() {
// Given
struct TestStrategy: Strategy {
let strategyID = "test"
}

// When
let context = StrategyContext(strategy: TestStrategy())

// Then
XCTAssertEqual(context.getStrategy().strategyID, "test")
}

func testStrategyContextSetStrategy() {
func testContextSwitching() {
// Given
struct TestStrategy: Strategy {
let strategyID: String
let val: Int
}

// When
let context = StrategyContext(strategy: TestStrategy(strategyID: "strategy1"))
XCTAssertEqual(context.getStrategy().strategyID, "strategy1")
let s1 = TestStrategy(strategyID: "s1", val: 1)
let s2 = TestStrategy(strategyID: "s2", val: 2)

context.setStrategy(TestStrategy(strategyID: "strategy2"))
let context = StrategyContext(strategy: s1)
XCTAssertEqual(context.getStrategy().val, 1)

// Then
XCTAssertEqual(context.getStrategy().strategyID, "strategy2")
context.setStrategy(s2)
XCTAssertEqual(context.getStrategy().val, 2)

context.setStrategy(s1)
XCTAssertEqual(context.getStrategy().val, 1)
}

// MARK: - Inheritance and Types

func testBaseStrategy() {
// Given
let baseStrategy = BaseStrategy(strategyID: "base-test")

// Then
XCTAssertEqual(baseStrategy.strategyID, "base-test")
}

func testBaseStrategyInheritance() {
// Given
class CustomStrategy: BaseStrategy {
init() {
super.init(strategyID: "custom")
}
init() { super.init(strategyID: "custom") }
func op() -> String { return "op" }
}

// When
let strategy = CustomStrategy()

// Then
XCTAssertEqual(strategy.strategyID, "custom")
XCTAssertEqual(strategy.op(), "op")
}

func testStrategyContextGetStrategy() {
// Given
struct TestStrategy: Strategy {
let strategyID = "test"
}

let context = StrategyContext(strategy: TestStrategy())
// MARK: - Polymorphism

protocol MathStrategy: Strategy {
func calculate(_ a: Int, _ b: Int) -> Int
}

struct Add: MathStrategy {
var strategyID = "add"
func calculate(_ a: Int, _ b: Int) -> Int { a + b }
}

struct Subtract: MathStrategy {
var strategyID = "subtract"
func calculate(_ a: Int, _ b: Int) -> Int { a - b }
}

func testPolymorphicContext() {
let context = StrategyContext<BoxedMathStrategy>(strategy: BoxedMathStrategy(Add()))

// When
let retrievedStrategy = context.getStrategy()
XCTAssertEqual(context.getStrategy().calculate(10, 5), 15)

// Then
XCTAssertEqual(retrievedStrategy.strategyID, "test")
context.setStrategy(BoxedMathStrategy(Subtract()))
XCTAssertEqual(context.getStrategy().calculate(10, 5), 5)
}

func testStrategyContextMultipleStrategies() {
// Given
struct TestStrategy: Strategy {
let strategyID: String
// Type erasure wrapper for the test because StrategyContext is generic over a specific concrete type
// or a protocol if used as an existentials (but StrategyContext<P> requires P: Strategy).
// Protocols conforming to protocols don't satisfy P: Strategy for generic constraints in Swift
// without `any` or box, testing here verifies the usage pattern.
struct BoxedMathStrategy: MathStrategy {
let strategyID: String
private let _calculate: (Int, Int) -> Int

init<S: MathStrategy>(_ strategy: S) {
self.strategyID = strategy.strategyID
self._calculate = strategy.calculate
}

// When
let context = StrategyContext(strategy: TestStrategy(strategyID: "strategy1"))
context.setStrategy(TestStrategy(strategyID: "strategy2"))

// Then
XCTAssertEqual(context.getStrategy().strategyID, "strategy2")
}
}

// Extension to make strategies executable for testing
extension Strategy {
func execute(_ a: Int, _ b: Int) -> Int {
return 0 // Default implementation
func calculate(_ a: Int, _ b: Int) -> Int {
_calculate(a, b)
}
}
}

Loading