Skip to content
Open
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
12 changes: 12 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ let package = Package(
name: "tinyTCA",
targets: ["tinyTCA"]
),
.library(
name: "tinyTCATestUtils",
targets: ["tinyTCATestUtils"]
)
],
targets: [
.target(
Expand All @@ -22,6 +26,14 @@ let package = Package(
// Strict concurrency checking (recommended for Swift 6)
.enableExperimentalFeature("StrictConcurrency")
]
),
.target(
name: "tinyTCATestUtils",
dependencies: ["tinyTCA"]
),
.testTarget(
name: "tinyTCATests",
dependencies: ["tinyTCA", "tinyTCATestUtils"]
)
]
)
1 change: 0 additions & 1 deletion Sources/tinyTCA/tinyTCA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public final class Store<F: Feature>: ObservableObject {
}
}


/// A two-way binding to the store's state for SwiftUI
public var binding: Binding<F.State> {
Binding(
Expand Down
23 changes: 23 additions & 0 deletions Sources/tinyTCATestUtils/TestStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation
import Combine
@testable import tinyTCA

/// A test store for testing state changes in isolation.
public final class TestStore<F: Feature> {
public init(state: F.State, feature: F) {
self.state = state
self.feature = feature
}

public init(feature: F) {
self.state = feature.initialState
self.feature = feature
}

let feature: F
public private(set) var state: F.State

public func send(_ action: F.Action) {
try? feature.reducer(state: &state, action: action)
}
}
60 changes: 60 additions & 0 deletions Tests/tinyTCATests/tinyTCATests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@testable import tinyTCA
import tinyTCATestUtils
import Testing

@Suite("Counter Feature Tests")
struct CounterFeatureTests {
@Test("Counter increments and decrements correctly")
func testCounter() async throws {
let testStore = TestStore(feature: CounterFeature())

testStore.send(.increment)
#expect(testStore.state.count == 1)

testStore.send(.decrement)
#expect(testStore.state.count == 0)

testStore.send(.delayedIncrement)
#expect(testStore.state.count == 0)
}
}

struct CounterFeature: Feature {
struct State: Equatable {
var count = 0
}

enum Action: Equatable {
case increment
case decrement
case delayedIncrement
case delayedIncrementResponse
}

var initialState: State { State() }

func reducer(state: inout State, action: Action) throws {
switch action {
case .increment:
state.count += 1
case .decrement:
state.count -= 1
case .delayedIncrement:
// No state change, the effect handles the logic
break
case .delayedIncrementResponse:
state.count += 1
}
}

// not tested yet
func effect(for action: Action, state: State) async throws -> Action? {
switch action {
case .delayedIncrement:
try await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
return .delayedIncrementResponse
default:
return nil
}
}
}