import SwiftUI import Testing @testable import Recording @Suite("Recording view model") struct RecordingViewModelTests { typealias Model = RecordingView.Model // MARK: Initial state @MainActor @Suite("Initial state") struct InitialState { @Test func `initial state is not recording with a zeroed timer`() { let model = Model() #expect(model.state == .notRecording) #expect(model.secondsElapsed == 0) #expect(model.textTimer == "00:00") } } // MARK: Button presses @MainActor @Suite("Button presses") struct ButtonPresses { @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [Model.State.recording, .paused, .recording, .processing] )) func `pressed main transitions to the expected state`( from initial: Model.State, to expected: Model.State ) { let model = Model() model.state = initial model.pressedMain() #expect(model.state == expected) } @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [Model.State.notRecording, .recording, .processing, .processing] )) func `pressed send transitions to the expected state`( from initial: Model.State, to expected: Model.State ) { let model = Model() model.state = initial model.pressedSend() #expect(model.state == expected) } } // MARK: Computed properties @MainActor @Suite("Computed properties") struct ComputedProperties { @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [ImageResource.Icon.record, .Icon.pause, .Icon.record, .Icon.record] )) func `main icon matches the state`( for state: Model.State, expected: ImageResource ) { let model = Model() model.state = state #expect(model.iconMain == expected) } @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [nil, nil, ImageResource.Icon.send, nil] )) func `send icon matches the state`( for state: Model.State, expected: ImageResource? ) { let model = Model() model.state = state #expect(model.iconSend == expected) } @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [false, false, false, true] )) func `is processing only while processing`( for state: Model.State, expected: Bool ) { let model = Model() model.state = state #expect(model.isProcessing == expected) } @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [false, false, false, true] )) func `main button is disabled only while processing`( for state: Model.State, expected: Bool ) { let model = Model() model.state = state #expect(model.shouldDisableMain == expected) } @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [false, false, true, true] )) func `send button is visible while paused or processing`( for state: Model.State, expected: Bool ) { let model = Model() model.state = state #expect(model.shouldShowSend == expected) } @Test(arguments: zip( [Model.State.notRecording, .recording, .paused, .processing], [false, true, true, true] )) func `timer is visible unless not recording`( for state: Model.State, expected: Bool ) { let model = Model() model.state = state #expect(model.shouldShowTimer == expected) } } // MARK: Timer @MainActor @Suite("Timer") struct Timer { @Test func `ticks while recording`() async throws { let model = Model() model.state = .recording model.updatedState(shouldRestartTimer: true) try await Task.sleep(for: .seconds(1.2)) #expect(model.secondsElapsed >= 1) #expect(model.textTimer == "00:01") model.state = .notRecording model.updatedState(shouldRestartTimer: false) } @Test func `stops while paused`() async throws { let model = Model() model.state = .recording model.updatedState(shouldRestartTimer: true) try await Task.sleep(for: .seconds(1.2)) model.state = .paused model.updatedState(shouldRestartTimer: false) let secondsWhenPaused = model.secondsElapsed try await Task.sleep(for: .seconds(1.2)) #expect(secondsWhenPaused >= 1) #expect(model.secondsElapsed == secondsWhenPaused) } @Test func `keeps elapsed seconds when resumed`() async throws { let model = Model() model.state = .recording model.updatedState(shouldRestartTimer: true) try await Task.sleep(for: .seconds(1.2)) model.state = .paused model.updatedState(shouldRestartTimer: false) let secondsWhenPaused = model.secondsElapsed model.state = .recording model.updatedState(shouldRestartTimer: false) #expect(model.secondsElapsed == secondsWhenPaused) model.state = .notRecording model.updatedState(shouldRestartTimer: false) } @Test func `restarts for a new recording`() async throws { let model = Model() model.state = .recording model.updatedState(shouldRestartTimer: true) try await Task.sleep(for: .seconds(1.2)) model.state = .paused model.updatedState(shouldRestartTimer: false) model.state = .recording model.updatedState(shouldRestartTimer: true) #expect(model.secondsElapsed == 0) model.state = .notRecording model.updatedState(shouldRestartTimer: false) } } // MARK: Processing @MainActor @Suite("Processing") struct Processing { @Test func `returns to not recording`() async throws { let model = Model() model.state = .processing model.updatedState(shouldRestartTimer: false) try await Task.sleep(for: .seconds(2.5)) #expect(model.state == .notRecording) } } }