diff --git a/Examples/ExampleApp/ExampleApp.xcodeproj/project.pbxproj b/Examples/ExampleApp/ExampleApp.xcodeproj/project.pbxproj index 3536922..e1e17b9 100644 --- a/Examples/ExampleApp/ExampleApp.xcodeproj/project.pbxproj +++ b/Examples/ExampleApp/ExampleApp.xcodeproj/project.pbxproj @@ -16,8 +16,6 @@ 9506D5702AB5882100472C0D /* View+isLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9506D56F2AB5882100472C0D /* View+isLoading.swift */; }; 9506D5732AB5C8FA00472C0D /* ViewModelDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9506D5722AB5C8FA00472C0D /* ViewModelDemo.swift */; }; 9506D5752AB5C98800472C0D /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9506D5742AB5C98800472C0D /* Root.swift */; }; - 9553409A2AB6BD3200A9C3D9 /* DemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955340992AB6BD3200A9C3D9 /* DemoView.swift */; }; - 9553409C2AB6D64200A9C3D9 /* DemoView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9553409B2AB6D64200A9C3D9 /* DemoView2.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -31,8 +29,6 @@ 9506D56F2AB5882100472C0D /* View+isLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+isLoading.swift"; sourceTree = ""; }; 9506D5722AB5C8FA00472C0D /* ViewModelDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelDemo.swift; sourceTree = ""; }; 9506D5742AB5C98800472C0D /* Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Root.swift; sourceTree = ""; }; - 955340992AB6BD3200A9C3D9 /* DemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoView.swift; sourceTree = ""; }; - 9553409B2AB6D64200A9C3D9 /* DemoView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoView2.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -75,8 +71,6 @@ 9506D5712AB5888200472C0D /* Helpers */, 9506D55D2AB5876700472C0D /* Assets.xcassets */, 9506D55F2AB5876700472C0D /* Preview Content */, - 955340992AB6BD3200A9C3D9 /* DemoView.swift */, - 9553409B2AB6D64200A9C3D9 /* DemoView2.swift */, ); path = ExampleApp; sourceTree = ""; @@ -182,10 +176,8 @@ 9506D56E2AB5880B00472C0D /* LoadingButton.swift in Sources */, 9506D55C2AB5876400472C0D /* ViewDemo.swift in Sources */, 9506D55A2AB5876400472C0D /* App.swift in Sources */, - 9553409C2AB6D64200A9C3D9 /* DemoView2.swift in Sources */, 9506D5702AB5882100472C0D /* View+isLoading.swift in Sources */, 9506D5752AB5C98800472C0D /* Root.swift in Sources */, - 9553409A2AB6BD3200A9C3D9 /* DemoView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/ExampleApp/ExampleApp/DemoView.swift b/Examples/ExampleApp/ExampleApp/DemoView.swift deleted file mode 100644 index c50301d..0000000 --- a/Examples/ExampleApp/ExampleApp/DemoView.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright © 2023 Dennis Müller and all collaborators -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import SwiftUI -import Processed - -struct DemoView: View { - - @Loadable<[Int]> var numbers - - var body: some View { - List { - Button("Load Numbers") { - loadNumbers() - } - .disabled(numbers.isLoading) - switch numbers { - case .absent: - EmptyView() - case .loading: - ProgressView() - .frame(height: .infinity) - case .error(let error): - Text("An error occurred: \(error.localizedDescription)") - case .loaded(let numbers): - ForEach(numbers, id: \.self) { number in - Text(String(number)) - } - } - } - .animation(.default, value: numbers) - } - - @MainActor func loadNumbers() { - $numbers.load { - try await Task.sleep(for: .seconds(2)) - return [0, 1, 2, 42, 73] - } - } -} - - - -#Preview { - DemoView() -} diff --git a/Examples/ExampleApp/ExampleApp/DemoView2.swift b/Examples/ExampleApp/ExampleApp/DemoView2.swift deleted file mode 100644 index 0ef9031..0000000 --- a/Examples/ExampleApp/ExampleApp/DemoView2.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright © 2023 Dennis Müller and all collaborators -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import SwiftUI -import Processed - -enum LoadingState { - case absent - case loading - case error(Error) - case loaded(Value) -} - -struct DemoView2: View { - - @State var numbers: LoadingState<[Int]> = .absent - @State var task: Task? - - var body: some View { - List { - Button("Load Numbers") { - loadNumbers() - } - switch numbers { - case .absent: - EmptyView() - case .loading: - ProgressView() - .frame(height: .infinity) - case .error(let error): - Text("An error occurred: \(error.localizedDescription)") - case .loaded(let numbers): - ForEach(numbers, id: \.self) { number in - Text(String(number)) - } - } - } - } - - @MainActor func loadNumbers() { -// $numbers.load { -// try await Task.sleep(for: .seconds(2)) -// return [0, 1, 2, 42, 73] -// } - } -} - - - -#Preview { - DemoView2() -} diff --git a/README.md b/README.md index c270ec0..d5566f0 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ To cancel an ongoing task, simply call `$numbers.cancel()` or throw a `CancelLoa
Use LoadableState in Classes -If you prefer to keep your state in a view model, or if you would like to use Processed completeley outside of SwiftUI, you can also do all the things from above inside a class. However, it works slightly differently because of the nature of SwiftUI property wrappers (they hold `@State` properties inside, which don't work outside the SwiftUI environment). +If you prefer to keep your state in a view model, or if you would like to use Processed completely outside of SwiftUI, you can also do all the things from above inside a class. However, it works slightly differently because of the nature of SwiftUI property wrappers (they hold `@State` properties inside, which don't work outside the SwiftUI environment). You simply have to conform your class to the `LoadableSupport` protocol that implements the same `load`, `cancel` and `reset` methods as the `@Loadable` property wrapper, but this time defined on `self`: diff --git a/Sources/Processed/Loadable/LoadableSupport.swift b/Sources/Processed/Loadable/LoadableSupport.swift index 23afb92..25052ed 100644 --- a/Sources/Processed/Loadable/LoadableSupport.swift +++ b/Sources/Processed/Loadable/LoadableSupport.swift @@ -34,13 +34,27 @@ public protocol LoadableSupport: AnyObject { priority: TaskPriority?, block: @escaping () async throws -> Value ) - + + func load( + _ loadableState: ReferenceWritableKeyPath>, + silently runSilently: Bool, + priority: TaskPriority?, + block: @escaping () async throws -> Value + ) async + func load( _ loadableState: ReferenceWritableKeyPath>, silently runSilently: Bool, priority: TaskPriority?, block: @escaping (_ yield: (_ state: LoadableState) -> Void) async throws -> Void ) + + func load( + _ loadableState: ReferenceWritableKeyPath>, + silently runSilently: Bool, + priority: TaskPriority?, + block: @escaping (_ yield: (_ state: LoadableState) -> Void) async throws -> Void + ) async } extension LoadableSupport { @@ -73,24 +87,36 @@ extension LoadableSupport { ) tasks[identifier]?.cancel() tasks[identifier] = Task(priority: priority) { - defer { - // Cleanup + defer { // Cleanup tasks[identifier] = nil } - - do { - if !runSilently { self[keyPath: loadableState] = .loading } - self[keyPath: loadableState] = try await .loaded(block()) - } catch is CancellationError { - // Task was cancelled. Don't change the state anymore - } catch is CancelLoadable { - self[keyPath: loadableState] = .absent - } catch { - self[keyPath: loadableState] = .error(error) - } + await load( + loadableState, + silently: runSilently, + priority: priority, + block: block + ) } } - + + public func load( + _ loadableState: ReferenceWritableKeyPath>, + silently runSilently: Bool = false, + priority: TaskPriority? = nil, + block: @escaping () async throws -> Value + ) async { + do { + if !runSilently { self[keyPath: loadableState] = .loading } + self[keyPath: loadableState] = try await .loaded(block()) + } catch is CancellationError { + // Task was cancelled. Don't change the state anymore + } catch is CancelLoadable { + self[keyPath: loadableState] = .absent + } catch { + self[keyPath: loadableState] = .error(error) + } + } + public func load( _ loadableState: ReferenceWritableKeyPath>, silently runSilently: Bool = false, @@ -103,23 +129,35 @@ extension LoadableSupport { ) tasks[identifier]?.cancel() tasks[identifier] = Task(priority: priority) { - defer { - // Cleanup + defer { // Cleanup tasks[identifier] = nil } - - do { - if !runSilently { self[keyPath: loadableState] = .loading } - try await block { state in - self[keyPath: loadableState] = state - } - } catch is CancellationError { - // Task was cancelled. Don't change the state anymore - } catch is CancelLoadable { - self[keyPath: loadableState] = .absent - } catch { - self[keyPath: loadableState] = .error(error) + await load( + loadableState, + silently: runSilently, + priority: priority, + block: block + ) + } + } + + public func load( + _ loadableState: ReferenceWritableKeyPath>, + silently runSilently: Bool = false, + priority: TaskPriority? = nil, + block: @escaping (_ yield: (_ state: LoadableState) -> Void) async throws -> Void + ) async { + do { + if !runSilently { self[keyPath: loadableState] = .loading } + try await block { state in + self[keyPath: loadableState] = state } + } catch is CancellationError { + // Task was cancelled. Don't change the state anymore + } catch is CancelLoadable { + self[keyPath: loadableState] = .absent + } catch { + self[keyPath: loadableState] = .error(error) } } } diff --git a/Sources/Processed/Process/ProcessSupport.swift b/Sources/Processed/Process/ProcessSupport.swift index 5a37040..5d9ffa0 100644 --- a/Sources/Processed/Process/ProcessSupport.swift +++ b/Sources/Processed/Process/ProcessSupport.swift @@ -164,6 +164,7 @@ extension ProcessSupport { self[keyPath: processState] = .running(process) } try await block() + print("JOJO") self[keyPath: processState] = .finished(process) } catch is CancellationError { // Task was cancelled. Don't change the state anymore diff --git a/Tests/ProcessedTests/Helpers/LoadableContainer.swift b/Tests/ProcessedTests/Helpers/LoadableContainer.swift index 7bb5531..cf4c95c 100644 --- a/Tests/ProcessedTests/Helpers/LoadableContainer.swift +++ b/Tests/ProcessedTests/Helpers/LoadableContainer.swift @@ -23,16 +23,16 @@ @testable import Processed import SwiftUI -@MainActor class LoadableContainer { - private(set) var stateHistory: [LoadableState] +@MainActor final class LoadableContainer: LoadableSupport { + private(set) var loadableHistory: [LoadableState] var task: Task? - var state: LoadableState { - didSet { stateHistory.append(state) } + var loadable: LoadableState { + didSet { loadableHistory.append(loadable) } } init(initialState: LoadableState = .absent) { - self.state = initialState - self.stateHistory = [state] + self.loadable = initialState + self.loadableHistory = [loadable] } var taskBinding: Binding?> { @@ -43,11 +43,11 @@ import SwiftUI } } - var stateBinding: Binding> { + var loadableBinding: Binding> { .init { - self.state + self.loadable } set: { newValue in - self.state = newValue + self.loadable = newValue } } } diff --git a/Tests/ProcessedTests/Helpers/ProcessContainer.swift b/Tests/ProcessedTests/Helpers/ProcessContainer.swift index cbbae20..39a6fe9 100644 --- a/Tests/ProcessedTests/Helpers/ProcessContainer.swift +++ b/Tests/ProcessedTests/Helpers/ProcessContainer.swift @@ -24,15 +24,15 @@ import SwiftUI @MainActor final class ProcessContainer: ProcessSupport { - private(set) var stateHistory: [ProcessState] + private(set) var processHistory: [ProcessState] var task: Task? var process: ProcessState { - didSet { stateHistory.append(process) } + didSet { processHistory.append(process) } } init(initialState: ProcessState = .idle) { self.process = initialState - self.stateHistory = [process] + self.processHistory = [process] } var taskBinding: Binding?> { diff --git a/Tests/ProcessedTests/LoadableInClassTests.swift b/Tests/ProcessedTests/LoadableInClassTests.swift new file mode 100644 index 0000000..f2ea611 --- /dev/null +++ b/Tests/ProcessedTests/LoadableInClassTests.swift @@ -0,0 +1,151 @@ +// +// Copyright © 2023 Dennis Müller and all collaborators +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import XCTest +@testable import Processed +import SwiftUI + +private struct EquatableError: Error, Equatable {} + +final class LoadableInClassTests: XCTestCase { + @MainActor func testBasic() async throws { + let container = LoadableContainer() + + await container.load(\.loadable) { + return 42 + } + + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .loaded(42)]) + } + + @MainActor func testBasicYielding() async throws { + let container = LoadableContainer() + + await container.load(\.loadable) { yield in + yield(.loaded(42)) + yield(.loaded(73)) + } + + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .loaded(42), .loaded(73)]) + } + + @MainActor func testReset() async throws { + let container = LoadableContainer() + + await container.load(\.loadable) { + return 42 + } + + container.reset(\.loadable) + + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .loaded(42), .absent]) + } + + @MainActor func testCancel() async throws { + let container = LoadableContainer() + + let task = Task { + await container.load(\.loadable) { + try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC) + XCTFail("Should not get here!") + throw EquatableError() + } + } + + task.cancel() + await task.value + + XCTAssertEqual(container.loadableHistory, [.absent, .loading]) + } + + @MainActor func testResetError() async throws { + let container = LoadableContainer() + + await container.load(\.loadable) { + throw CancelLoadable() + } + + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .absent]) + } + + @MainActor func testErrorStates() async throws { + let container = LoadableContainer() + + await container.load(\.loadable) { + throw EquatableError() + } + + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .error(EquatableError())]) + } + + @MainActor func testRunSilently() async throws { + let container = LoadableContainer() + + await container.load(\.loadable, silently: true) { + return 42 + } + + XCTAssertEqual(container.loadableHistory, [.absent, .loaded(42)]) + } + + @MainActor func testRunTwice() async throws { + let container = LoadableContainer() + + await container.load(\.loadable) { + return 42 + } + + await container.load(\.loadable) { + return 73 + } + + XCTAssertEqual(container.loadableHistory, [ + .absent, + .loading, + .loaded(42), + .loading, + .loaded(73) + ]) + } + + @MainActor func testRunTwiceFailFirst() async throws { + let container = LoadableContainer() + + await container.load(\.loadable) { + throw EquatableError() + } + + await container.load(\.loadable) { + return 42 + } + + XCTAssertEqual(container.loadableHistory, [ + .absent, + .loading, + .error(EquatableError()), + .loading, + .loaded(42) + ]) + } +} + + diff --git a/Tests/ProcessedTests/LoadableTests.swift b/Tests/ProcessedTests/LoadableTests.swift index 9ea9ff4..b2837dd 100644 --- a/Tests/ProcessedTests/LoadableTests.swift +++ b/Tests/ProcessedTests/LoadableTests.swift @@ -30,30 +30,30 @@ private struct EquatableError: Error, Equatable {} @MainActor func testBasic() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load { return 42 } - XCTAssertEqual(container.stateHistory, [.absent, .loading, .loaded(42)]) + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .loaded(42)]) } @MainActor func testBasicYielding() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load { yield in yield(.loaded(42)) yield(.loaded(73)) } - XCTAssertEqual(container.stateHistory, [.absent, .loading, .loaded(42), .loaded(73)]) + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .loaded(42), .loaded(73)]) } @MainActor func testMultipleYielding() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load { yield in yield(.loaded(42)) @@ -64,34 +64,34 @@ private struct EquatableError: Error, Equatable {} yield(.loaded(100)) } - XCTAssertEqual(container.stateHistory, [.absent, .loading, .loaded(42), .loaded(73), .loading, .loaded(100)]) + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .loaded(42), .loaded(73), .loading, .loaded(100)]) } @MainActor func testRunSilently() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load(silently: true) { return 42 } - XCTAssertEqual(container.stateHistory, [.absent, .loaded(42)]) + XCTAssertEqual(container.loadableHistory, [.absent, .loaded(42)]) } @MainActor func testRunSilentlyWithYielding() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load(silently: true) { yield in yield(.loaded(42)) } - XCTAssertEqual(container.stateHistory, [.absent, .loaded(42)]) + XCTAssertEqual(container.loadableHistory, [.absent, .loaded(42)]) } @MainActor func testReset() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load { return 42 @@ -99,23 +99,23 @@ private struct EquatableError: Error, Equatable {} binding.reset() - XCTAssertEqual(container.stateHistory, [.absent, .loading, .loaded(42), .absent]) + XCTAssertEqual(container.loadableHistory, [.absent, .loading, .loaded(42), .absent]) } @MainActor func testResetThrow() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load { throw CancelLoadable() } - XCTAssertEqual(container.stateHistory, [.absent, .loading]) + XCTAssertEqual(container.loadableHistory, [.absent, .loading]) } @MainActor func testResetThrowAndCancel() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) await binding.load { throw CancelLoadable() @@ -123,12 +123,12 @@ private struct EquatableError: Error, Equatable {} binding.cancel() - XCTAssertEqual(container.stateHistory, [.absent, .loading]) + XCTAssertEqual(container.loadableHistory, [.absent, .loading]) } @MainActor func testCancel() async throws { let container = LoadableContainer() - let binding = Loadable.Binding(state: container.stateBinding, task: container.taskBinding) + let binding = Loadable.Binding(state: container.loadableBinding, task: container.taskBinding) let task = Task { await binding.load { @@ -141,6 +141,6 @@ private struct EquatableError: Error, Equatable {} task.cancel() await task.value - XCTAssertEqual(container.stateHistory, [.absent, .loading,]) + XCTAssertEqual(container.loadableHistory, [.absent, .loading,]) } } diff --git a/Tests/ProcessedTests/ProcessInClassTests.swift b/Tests/ProcessedTests/ProcessInClassTests.swift index 48334d5..fa3f803 100644 --- a/Tests/ProcessedTests/ProcessInClassTests.swift +++ b/Tests/ProcessedTests/ProcessInClassTests.swift @@ -35,7 +35,114 @@ final class ProcessInClassTests: XCTestCase { return } - XCTAssertEqual(container.stateHistory, [.idle, .running(process), .finished(process)]) + XCTAssertEqual(container.processHistory, [.idle, .running(process), .finished(process)]) + } + + @MainActor func testReset() async throws { + let container = ProcessContainer() + let process = SingleProcess(id: "1") + + await container.run(\.process, as: process) { + return + } + + container.reset(\.process) + + XCTAssertEqual(container.processHistory, [.idle, .running(process), .finished(process), .idle]) + } + + @MainActor func testCancel() async throws { + let container = ProcessContainer() + let process = SingleProcess(id: "1") + + let task = Task { + await container.run(\.process, as: process) { + try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC) + XCTFail("Should not get here!") + } + } + + task.cancel() + await task.value + + XCTAssertEqual(container.processHistory, [.idle, .running(process)]) + } + + @MainActor func testResetError() async throws { + let container = ProcessContainer() + let process = SingleProcess(id: "1") + + await container.run(\.process, as: process) { + throw CancelProcess() + } + + XCTAssertEqual(container.processHistory, [.idle, .running(process), .idle]) + } + + @MainActor func testErrorStates() async throws { + let container = ProcessContainer() + let process = SingleProcess(id: "1") + + await container.run(\.process, as: process) { + throw EquatableError() + } + + XCTAssertEqual(container.processHistory, [.idle, .running(process), .failed(process: process, error: EquatableError())]) + } + + @MainActor func testRunSilently() async throws { + let container = ProcessContainer() + let process = SingleProcess(id: "1") + + await container.run(\.process, as: process, silently: true) { + return + } + + XCTAssertEqual(container.processHistory, [.idle, .finished(process)]) + } + + @MainActor func testRunTwice() async throws { + let container = ProcessContainer() + let process = SingleProcess(id: "1") + let secondProcess = SingleProcess(id: "2") + + await container.run(\.process, as: process) { + return + } + + await container.run(\.process, as: secondProcess) { + return + } + + XCTAssertEqual(container.processHistory, [ + .idle, + .running(process), + .finished(process), + .running(secondProcess), + .finished(secondProcess) + ]) + } + + @MainActor func testRunTwiceFailFirst() async throws { + let container = ProcessContainer() + let process = SingleProcess(id: "1") + let secondProcess = SingleProcess(id: "2") + + await container.run(\.process, as: process) { + throw EquatableError() + } + + await container.run(\.process, as: secondProcess) { + return + } + + XCTAssertEqual(container.processHistory, [ + .idle, + .running(process), + .failed(process: process, error: EquatableError()), + .running(secondProcess), + .finished(secondProcess) + ]) } } diff --git a/Tests/ProcessedTests/ProcessTests.swift b/Tests/ProcessedTests/ProcessTests.swift index 37a1c0c..ddce262 100644 --- a/Tests/ProcessedTests/ProcessTests.swift +++ b/Tests/ProcessedTests/ProcessTests.swift @@ -37,7 +37,7 @@ final class ProcessTests: XCTestCase { return } - XCTAssertEqual(container.stateHistory, [.idle, .running(process), .finished(process)]) + XCTAssertEqual(container.processHistory, [.idle, .running(process), .finished(process)]) } @MainActor func testReset() async throws { @@ -51,7 +51,7 @@ final class ProcessTests: XCTestCase { binding.reset() - XCTAssertEqual(container.stateHistory, [.idle, .running(process), .finished(process), .idle]) + XCTAssertEqual(container.processHistory, [.idle, .running(process), .finished(process), .idle]) } @MainActor func testCancel() async throws { @@ -69,7 +69,7 @@ final class ProcessTests: XCTestCase { task.cancel() await task.value - XCTAssertEqual(container.stateHistory, [.idle, .running(process)]) + XCTAssertEqual(container.processHistory, [.idle, .running(process)]) } @MainActor func testResetError() async throws { @@ -81,7 +81,7 @@ final class ProcessTests: XCTestCase { throw CancelProcess() } - XCTAssertEqual(container.stateHistory, [.idle, .running(process)]) + XCTAssertEqual(container.processHistory, [.idle, .running(process)]) } @MainActor func testErrorStates() async throws { @@ -93,7 +93,7 @@ final class ProcessTests: XCTestCase { throw EquatableError() } - XCTAssertEqual(container.stateHistory, [.idle, .running(process), .failed(process: process, error: EquatableError())]) + XCTAssertEqual(container.processHistory, [.idle, .running(process), .failed(process: process, error: EquatableError())]) } @MainActor func testRunSilently() async throws { @@ -105,7 +105,7 @@ final class ProcessTests: XCTestCase { return } - XCTAssertEqual(container.stateHistory, [.idle, .finished(process)]) + XCTAssertEqual(container.processHistory, [.idle, .finished(process)]) } @MainActor func testRunTwice() async throws { @@ -122,7 +122,7 @@ final class ProcessTests: XCTestCase { return } - XCTAssertEqual(container.stateHistory, [ + XCTAssertEqual(container.processHistory, [ .idle, .running(process), .finished(process), @@ -145,7 +145,7 @@ final class ProcessTests: XCTestCase { return } - XCTAssertEqual(container.stateHistory, [ + XCTAssertEqual(container.processHistory, [ .idle, .running(process), .failed(process: process, error: EquatableError()),