From 4fd695c2cb4d06ea295a565f354677a9b2601f42 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 3 Jul 2026 20:30:37 +0200 Subject: [PATCH] Reset the elapsed seconds after processing for the RecordingViewModel view model in the Recording package target. --- .../Recording/RecordingViewModel.swift | 33 ++++++++++--------- .../Recording/RecordingViewModelTests.swift | 27 ++++++++++----- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Packages/Features/Sources/Recording/RecordingViewModel.swift b/Packages/Features/Sources/Recording/RecordingViewModel.swift index e5cf0dd..3a00f5e 100644 --- a/Packages/Features/Sources/Recording/RecordingViewModel.swift +++ b/Packages/Features/Sources/Recording/RecordingViewModel.swift @@ -17,11 +17,11 @@ extension RecordingView { var state: State = .notRecording /// The number of seconds spent recording, excluding any time spent paused. - private(set) var secondsElapsed: Int = 0 + private(set) var elapsedSeconds: Int = 0 - /// The task that increments ``secondsElapsed`` every second while recording. + /// The task that increments ``elapsedSeconds`` every second while recording. @ObservationIgnored - private var timerTask: Task? + private var taskTimer: Task? // MARK: Computed @@ -65,7 +65,7 @@ extension RecordingView { /// The elapsed recording time, formatted as `mm:ss` for the timer label. var textTimer: String { Duration - .seconds(secondsElapsed) + .seconds(elapsedSeconds) .formatted(.time(pattern: .minuteSecond(padMinuteToLength: 2))) } @@ -112,7 +112,7 @@ extension RecordingView { /// /// Starts the timer when a recording begins or resumes, stops it in every other state, and kicks off ``processInput()`` when the input is sent. /// - /// - Parameter shouldRestartTimer: Whether ``secondsElapsed`` should be reset to zero before the timer starts, which is the case for + /// - Parameter shouldRestartTimer: Whether ``elapsedSeconds`` should be reset to zero before the timer starts, which is the case for /// a new recording as opposed to one resuming from a pause. func updatedState( shouldRestartTimer: Bool @@ -140,9 +140,9 @@ private extension RecordingView.Model { // MARK: Methods - /// Processes the recorded input, returning the model to the not-recording state when finished. + /// Processes the recorded input, resetting ``elapsedSeconds`` and returning the model to the not-recording state when finished. /// - /// Currently a placeholder that simulates the work with a five-second delay. + /// Currently a placeholder that simulates the work with a two-second delay. func processInput() async { guard state == .processing else { return @@ -150,21 +150,22 @@ private extension RecordingView.Model { try? await Task.sleep(for: .seconds(2)) + elapsedSeconds = 0 state = .notRecording } - /// Starts the timer task, which increments ``secondsElapsed`` once per second until it is cancelled. Any previously running timer task is cancelled first. + /// Starts the timer task, which increments ``elapsedSeconds`` once per second until it is cancelled. Any previously running timer task is cancelled first. /// - /// - Parameter shouldRestartTimer: Whether ``secondsElapsed`` should be reset to zero before the timer starts. + /// - Parameter shouldRestartTimer: Whether ``elapsedSeconds`` should be reset to zero before the timer starts. func startTimer( _ shouldRestartTimer: Bool ) { if shouldRestartTimer { - secondsElapsed = 0 + elapsedSeconds = 0 } - timerTask?.cancel() - timerTask = Task { [weak self] in + taskTimer?.cancel() + taskTimer = Task { [weak self] in while !Task.isCancelled { try? await Task.sleep(for: .seconds(1)) @@ -172,15 +173,15 @@ private extension RecordingView.Model { return } - self.secondsElapsed += 1 + self.elapsedSeconds += 1 } } } - /// Stops the timer task, if any, keeping ``secondsElapsed`` at its current value. + /// Stops the timer task, if any, keeping ``elapsedSeconds`` at its current value. func stopTimer() { - timerTask?.cancel() - timerTask = nil + taskTimer?.cancel() + taskTimer = nil } } diff --git a/Packages/Features/Tests/Recording/RecordingViewModelTests.swift b/Packages/Features/Tests/Recording/RecordingViewModelTests.swift index 610c1c3..0a20684 100644 --- a/Packages/Features/Tests/Recording/RecordingViewModelTests.swift +++ b/Packages/Features/Tests/Recording/RecordingViewModelTests.swift @@ -18,7 +18,7 @@ struct RecordingViewModelTests { let model = Model() #expect(model.state == .notRecording) - #expect(model.secondsElapsed == 0) + #expect(model.elapsedSeconds == 0) #expect(model.textTimer == "00:00") } @@ -178,7 +178,7 @@ struct RecordingViewModelTests { try await Task.sleep(for: .seconds(1.2)) - #expect(model.secondsElapsed >= 1) + #expect(model.elapsedSeconds >= 1) #expect(model.textTimer == "00:01") model.state = .notRecording @@ -196,12 +196,12 @@ struct RecordingViewModelTests { model.state = .paused model.updatedState(shouldRestartTimer: false) - let secondsWhenPaused = model.secondsElapsed + let secondsWhenPaused = model.elapsedSeconds try await Task.sleep(for: .seconds(1.2)) #expect(secondsWhenPaused >= 1) - #expect(model.secondsElapsed == secondsWhenPaused) + #expect(model.elapsedSeconds == secondsWhenPaused) } @Test func `keeps elapsed seconds when resumed`() async throws { @@ -215,12 +215,12 @@ struct RecordingViewModelTests { model.state = .paused model.updatedState(shouldRestartTimer: false) - let secondsWhenPaused = model.secondsElapsed + let secondsWhenPaused = model.elapsedSeconds model.state = .recording model.updatedState(shouldRestartTimer: false) - #expect(model.secondsElapsed == secondsWhenPaused) + #expect(model.elapsedSeconds == secondsWhenPaused) model.state = .notRecording model.updatedState(shouldRestartTimer: false) @@ -240,7 +240,7 @@ struct RecordingViewModelTests { model.state = .recording model.updatedState(shouldRestartTimer: true) - #expect(model.secondsElapsed == 0) + #expect(model.elapsedSeconds == 0) model.state = .notRecording model.updatedState(shouldRestartTimer: false) @@ -254,15 +254,26 @@ struct RecordingViewModelTests { @Suite("Processing") struct Processing { - @Test func `returns to not recording`() async throws { + @Test func `returns to not recording with a reset timer`() 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) + + #expect(model.elapsedSeconds >= 1) + model.state = .processing model.updatedState(shouldRestartTimer: false) try await Task.sleep(for: .seconds(2.5)) #expect(model.state == .notRecording) + #expect(model.elapsedSeconds == 0) } }