Added the recording flow reporting and command listening on the RecordingViewModel view model in the Recording package target.

This commit is contained in:
2026-07-05 22:00:43 +02:00
parent d473504d75
commit cfe6b1b408
6 changed files with 426 additions and 8 deletions
@@ -0,0 +1,53 @@
import Foundation
import Recording
/// A reporting service that records the lifecycle reports it receives, in call order, along with the values of the last report.
@MainActor
final class ReportingMock: Reporting {
// MARK: Properties
/// The names of the reports received by the service, in call order.
private(set) var reports: [String] = []
/// The anchor received with the last started or resumed report, or `nil` when none arrived yet.
private(set) var lastAnchor: Date?
/// The elapsed recording time received with the last paused or processing report, or `nil` when none arrived yet.
private(set) var lastElapsed: TimeInterval?
// MARK: Methods
func started(
at anchor: Date
) async {
reports.append("started")
lastAnchor = anchor
}
func paused(
elapsed: TimeInterval
) async {
reports.append("paused")
lastElapsed = elapsed
}
func resumed(
anchor: Date
) async {
reports.append("resumed")
lastAnchor = anchor
}
func processing(
elapsed: TimeInterval
) async {
reports.append("processing")
lastElapsed = elapsed
}
func ended() async {
reports.append("ended")
}
}
@@ -1,3 +1,4 @@
import Commanding
import SwiftUI
import Testing
@@ -764,6 +765,199 @@ struct RecordingViewModelTests {
}
// MARK: Reporter
@MainActor
@Suite("Reporter")
struct Reporter {
@Test
func `reports the start of a new recording with a current anchor`() async throws {
let reporter = ReportingMock()
let model = Model(reporter: reporter)
let before = Date.now
model.pressedMain()
try await Task.sleep(for: .seconds(0.1))
#expect(reporter.reports == ["started"])
#expect(reporter.lastAnchor ?? .distantPast >= before)
#expect(reporter.lastAnchor ?? .distantFuture <= .now)
model.pressedMain()
}
@Test
func `reports a pause with the elapsed recording time`() async throws {
let reporter = ReportingMock()
let model = Model(reporter: reporter)
model.pressedMain()
try await Task.sleep(for: .seconds(1.2))
model.pressedMain()
try await Task.sleep(for: .seconds(0.1))
#expect(reporter.reports == ["started", "paused"])
#expect(reporter.lastElapsed ?? 0 >= 1)
}
@Test
func `reports a resumption with an anchor moved back by the elapsed recording time`() async throws {
let reporter = ReportingMock()
let model = Model(reporter: reporter)
model.pressedMain()
try await Task.sleep(for: .seconds(1.2))
model.pressedMain()
model.pressedMain()
try await Task.sleep(for: .seconds(0.1))
#expect(reporter.reports == ["started", "paused", "resumed"])
#expect(reporter.lastAnchor ?? .distantFuture <= Date(timeIntervalSinceNow: -1))
model.pressedMain()
}
@Test
func `reports the processing and end of a sent recording`() async throws {
let reporter = ReportingMock()
let model = Model(
transcribe: TranscribingMock(),
reporter: reporter
)
model.drive(to: .processing)
try await Task.sleep(for: .seconds(0.5))
#expect(reporter.reports == ["started", "paused", "processing", "ended"])
}
@Test
func `reports the end of a discarded recording`() async throws {
let reporter = ReportingMock()
let model = Model(reporter: reporter)
model.drive(to: .paused)
model.pressedDiscard()
try await Task.sleep(for: .seconds(0.1))
#expect(reporter.reports == ["started", "paused", "ended"])
}
@Test
func `reports a pause when the capture is interrupted`() async throws {
let capturer = CapturingMock()
let reporter = ReportingMock()
let model = Model(
capturer: capturer,
reporter: reporter
)
model.pressedMain()
try await Task.sleep(for: .seconds(0.1))
capturer.interrupt()
try await Task.sleep(for: .seconds(0.1))
#expect(reporter.reports == ["started", "paused"])
}
}
// MARK: Commands
@MainActor
@Suite("Commands")
struct Commands {
@Test
func `a toggle command pauses the ongoing recording`() async throws {
let capturer = CapturingMock()
let commander = RecordingCommander()
let model = Model(
capturer: capturer,
commander: commander
)
model.pressedMain()
try await Task.sleep(for: .seconds(0.1))
commander.send(.toggle)
try await Task.sleep(for: .seconds(0.1))
#expect(model.state == .paused)
#expect(capturer.calls == ["start", "pause"])
}
@Test
func `a toggle command resumes a paused recording`() async throws {
let capturer = CapturingMock()
let commander = RecordingCommander()
let model = Model(
capturer: capturer,
commander: commander
)
model.drive(to: .paused)
try await Task.sleep(for: .seconds(0.1))
commander.send(.toggle)
try await Task.sleep(for: .seconds(0.1))
#expect(model.state == .recording)
#expect(capturer.calls == ["start", "pause", "resume"])
model.pressedMain()
}
@Test
func `a discard command throws a paused recording away`() async throws {
let commander = RecordingCommander()
let model = Model(commander: commander)
model.drive(to: .paused)
try await Task.sleep(for: .seconds(0.1))
commander.send(.discard)
try await Task.sleep(for: .seconds(0.1))
#expect(model.state == .notRecording)
#expect(model.elapsedSeconds == 0)
}
@Test
func `commands are ignored before the first recording starts`() async throws {
let commander = RecordingCommander()
let model = Model(commander: commander)
commander.send(.toggle)
try await Task.sleep(for: .seconds(0.1))
#expect(model.state == .notRecording)
}
}
}
// MARK: - Helpers