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
75 changes: 71 additions & 4 deletions Sources/OneWay/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,44 @@ extension Store {
/// - timeout: The maximum amount of time (in seconds) to wait for the store to finish
/// processing before timing out. Defaults to 2 seconds.
/// - sourceLocation: The source location for tracking the test location.
public func expect<Property: Equatable>(
#if swift(>=6)
public func expect<Property>(
_ keyPath: KeyPath<State, Property> & Sendable,
_ input: Property,
timeout: TimeInterval = 2,
sourceLocation: Testing.SourceLocation = #_sourceLocation
) async where Property: Sendable & Equatable {
var isTimeout = false
let start = CFAbsoluteTimeGetCurrent()
await Task.detached(priority: .background) {
await Task.yield()
}.value
await Task.yield()
while !isIdle {
await Task.detached(priority: .background) {
await Task.yield()
}.value
await Task.yield()
let elapsedTime = CFAbsoluteTimeGetCurrent() - start
if elapsedTime > timeout {
isTimeout = true
break
}
}
let result = state[keyPath: keyPath]
if isTimeout && result != input {
Issue.record("Exceeded timeout of \(timeout) seconds", sourceLocation: sourceLocation)
} else {
#expect(result == input, sourceLocation: sourceLocation)
}
}
#else
public func expect<Property>(
_ keyPath: KeyPath<State, Property>,
_ input: Property,
timeout: TimeInterval = 2,
sourceLocation: Testing.SourceLocation = #_sourceLocation
) async {
) async where Property: Sendable & Equatable {
var isTimeout = false
let start = CFAbsoluteTimeGetCurrent()
await Task.detached(priority: .background) {
Expand All @@ -197,6 +229,7 @@ extension Store {
#expect(result == input, sourceLocation: sourceLocation)
}
}
#endif
}
#endif

Expand All @@ -216,13 +249,46 @@ extension Store {
/// processing before timing out. Defaults to 2 seconds.
/// - file: The file path from which the function is called (default is the current file).
/// - line: The line number from which the function is called (default is the current line).
public func xctExpect<Property: Equatable>(
#if swift(>=6)
public func xctExpect<Property>(
_ keyPath: KeyPath<State, Property> & Sendable,
_ input: Property,
timeout: TimeInterval = 2,
file: StaticString = #filePath,
line: UInt = #line
) async where Property: Sendable & Equatable {
var isTimeout = false
let start = CFAbsoluteTimeGetCurrent()
await Task.detached(priority: .background) {
await Task.yield()
}.value
await Task.yield()
while !isIdle {
await Task.detached(priority: .background) {
await Task.yield()
}.value
await Task.yield()
let elapsedTime = CFAbsoluteTimeGetCurrent() - start
if elapsedTime > timeout {
isTimeout = true
break
}
}
let result = state[keyPath: keyPath]
if isTimeout && result != input {
XCTFail("Exceeded timeout of \(timeout) seconds", file: file, line: line)
} else {
XCTAssertEqual(result, input, file: file, line: line)
}
}
#else
public func xctExpect<Property>(
_ keyPath: KeyPath<State, Property>,
_ input: Property,
timeout: TimeInterval = 2,
file: StaticString = #filePath,
line: UInt = #line
) async {
) async where Property: Sendable & Equatable {
var isTimeout = false
let start = CFAbsoluteTimeGetCurrent()
await Task.detached(priority: .background) {
Expand All @@ -247,5 +313,6 @@ extension Store {
XCTAssertEqual(result, input, file: file, line: line)
}
}
#endif
}
#endif
37 changes: 32 additions & 5 deletions Sources/OneWay/ViewStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ where R.Action: Sendable, R.State: Sendable & Equatable {
let (stream, continuation) = AsyncStream<State>.makeStream()
self.states = AsyncViewStateSequence(stream)
self.continuation = continuation
self.task = Task { [weak self] in
self.task = Task { @MainActor [weak self] in
guard let states = await self?.store.states else { return }
for await state in states {
guard let self else { break }
Expand All @@ -83,15 +83,19 @@ where R.Action: Sendable, R.State: Sendable & Equatable {
///
/// - Parameter action: An action defined in the reducer.
public func send(_ action: Action) {
Task { await store.send(action) }
Task { @MainActor in
await store.send(action)
}
}

/// Removes all actions and effects in the queue and re-binds for global states.
///
/// - Note: This is useful when you need to call `bind()` again. Because you can't call `bind()`
/// directly
public func reset() {
Task { await store.reset() }
Task { @MainActor in
await store.reset()
}
}
}

Expand All @@ -114,14 +118,25 @@ extension ViewStore {
/// - timeout: The maximum amount of time (in seconds) to wait for the store to finish
/// processing before timing out. Defaults to 2 seconds.
/// - sourceLocation: The source location for tracking the test location.
#if swift(>=6)
public func expect<Property>(
_ keyPath: KeyPath<State, Property> & Sendable,
_ input: Property,
timeout: TimeInterval = 2,
sourceLocation: Testing.SourceLocation = #_sourceLocation
) async where Property: Sendable & Equatable {
await store.expect(keyPath, input, timeout: timeout, sourceLocation: sourceLocation)
}
#else
public func expect<Property: Equatable>(
_ keyPath: KeyPath<State, Property>,
_ input: Property,
timeout: TimeInterval = 2,
sourceLocation: Testing.SourceLocation = #_sourceLocation
) async {
) async where Property: Sendable & Equatable {
await store.expect(keyPath, input, timeout: timeout, sourceLocation: sourceLocation)
}
#endif
}
#endif

Expand All @@ -140,15 +155,27 @@ extension ViewStore {
/// processing before timing out. Defaults to 2 seconds.
/// - file: The file path from which the function is called (default is the current file).
/// - line: The line number from which the function is called (default is the current line).
#if swift(>=6)
public func xctExpect<Property: Equatable>(
_ keyPath: KeyPath<State, Property> & Sendable,
_ input: Property,
timeout: TimeInterval = 2,
file: StaticString = #filePath,
line: UInt = #line
) async where Property: Sendable & Equatable {
await store.xctExpect(keyPath, input, timeout: timeout, file: file, line: line)
}
#else
public func xctExpect<Property: Equatable>(
_ keyPath: KeyPath<State, Property>,
_ input: Property,
timeout: TimeInterval = 2,
file: StaticString = #filePath,
line: UInt = #line
) async {
) async where Property: Sendable & Equatable {
await store.xctExpect(keyPath, input, timeout: timeout, file: file, line: line)
}
#endif
}
#endif
#endif