Integrated the RecordingService and the TranscribingService protocols into the RecordingView view in the Recording package target.

This commit is contained in:
2026-07-03 21:37:40 +02:00
parent 31b778a36e
commit 376be4940b
4 changed files with 316 additions and 21 deletions
@@ -20,6 +20,7 @@ struct RecordingViewModelTests {
#expect(model.state == .notRecording)
#expect(model.elapsedSeconds == 0)
#expect(model.textTimer == "00:00")
#expect(model.textTranscription == nil)
}
}
@@ -248,14 +249,107 @@ struct RecordingViewModelTests {
}
// MARK: Recorder
@MainActor
@Suite("Recorder")
struct Recorder {
@Test func `starts the capture for a new recording`() async throws {
let recorder = RecordingServiceMock()
let model = Model(recorder: recorder)
model.state = .recording
model.updatedState(shouldRestartTimer: true)
try await Task.sleep(for: .seconds(0.1))
#expect(recorder.countStart == 1)
#expect(recorder.countResume == 0)
model.state = .notRecording
model.updatedState(shouldRestartTimer: false)
}
@Test func `pauses the capture while paused`() async throws {
let recorder = RecordingServiceMock()
let model = Model(recorder: recorder)
model.state = .recording
model.updatedState(shouldRestartTimer: true)
model.state = .paused
model.updatedState(shouldRestartTimer: false)
try await Task.sleep(for: .seconds(0.1))
#expect(recorder.countPause == 1)
}
@Test func `resumes the capture after a pause`() async throws {
let recorder = RecordingServiceMock()
let model = Model(recorder: recorder)
model.state = .recording
model.updatedState(shouldRestartTimer: true)
model.state = .paused
model.updatedState(shouldRestartTimer: false)
model.state = .recording
model.updatedState(shouldRestartTimer: false)
try await Task.sleep(for: .seconds(0.1))
#expect(recorder.countStart == 1)
#expect(recorder.countResume == 1)
model.state = .notRecording
model.updatedState(shouldRestartTimer: false)
}
@Test func `stops the capture when the input is sent`() async throws {
let recorder = RecordingServiceMock()
let model = Model(
recorder: recorder,
transcriber: TranscribingServiceMock()
)
model.state = .processing
model.updatedState(shouldRestartTimer: false)
try await Task.sleep(for: .seconds(0.5))
#expect(recorder.countStop == 1)
}
@Test func `falls back to not recording when the capture fails`() async throws {
let recorder = RecordingServiceMock()
recorder.error = ErrorMock()
let model = Model(recorder: recorder)
model.state = .recording
model.updatedState(shouldRestartTimer: true)
try await Task.sleep(for: .seconds(0.1))
#expect(model.state == .notRecording)
}
}
// MARK: Processing
@MainActor
@Suite("Processing")
struct Processing {
@Test func `returns to not recording with a reset timer`() async throws {
let model = Model()
@Test func `returns to not recording with a reset timer and a transcription`() async throws {
let model = Model(
transcriber: TranscribingServiceMock()
)
model.state = .recording
model.updatedState(shouldRestartTimer: true)
@@ -270,12 +364,136 @@ struct RecordingViewModelTests {
model.state = .processing
model.updatedState(shouldRestartTimer: false)
try await Task.sleep(for: .seconds(2.5))
try await Task.sleep(for: .seconds(0.5))
#expect(model.state == .notRecording)
#expect(model.elapsedSeconds == 0)
#expect(model.textTranscription != nil)
}
@Test func `clears the transcription when the transcriber fails`() async throws {
let transcriber = TranscribingServiceMock()
transcriber.error = ErrorMock()
let model = Model(
transcriber: transcriber
)
model.state = .processing
model.updatedState(shouldRestartTimer: false)
try await Task.sleep(for: .seconds(0.5))
#expect(model.state == .notRecording)
#expect(model.elapsedSeconds == 0)
#expect(model.textTranscription == nil)
}
@Test func `clears the transcription when a new recording starts`() async throws {
let model = Model(
transcriber: TranscribingServiceMock()
)
model.state = .processing
model.updatedState(shouldRestartTimer: false)
try await Task.sleep(for: .seconds(0.5))
#expect(model.textTranscription != nil)
model.state = .recording
model.updatedState(shouldRestartTimer: true)
#expect(model.textTranscription == nil)
model.state = .notRecording
model.updatedState(shouldRestartTimer: false)
}
}
}
// MARK: - Mocks
/// A recording service that counts its invocations and can be configured to fail.
@MainActor
private final class RecordingServiceMock: RecordingService {
/// The error the service throws from every method, or `nil` when it should succeed.
var error: Error?
/// The number of times ``start()`` has been called.
private(set) var countStart = 0
/// The number of times ``pause()`` has been called.
private(set) var countPause = 0
/// The number of times ``resume()`` has been called.
private(set) var countResume = 0
/// The number of times ``stop()`` has been called.
private(set) var countStop = 0
func start() async throws {
countStart += 1
try throwConfiguredError()
}
func pause() async throws {
countPause += 1
try throwConfiguredError()
}
func resume() async throws {
countResume += 1
try throwConfiguredError()
}
func stop() async throws -> Data {
countStop += 1
try throwConfiguredError()
return .init()
}
private func throwConfiguredError() throws {
if let error {
throw error
}
}
}
/// A transcribing service with a configurable delay, output, and failure.
@MainActor
private final class TranscribingServiceMock: TranscribingService {
/// The duration of the simulated transcription work.
var delay: Duration = .seconds(0.2)
/// The error the service throws, or `nil` when it should succeed.
var error: Error?
/// The transcription the service returns.
var transcription = "This is a mocked transcription."
func transcribe(_ audio: Data) async throws -> String {
try await Task.sleep(for: delay)
if let error {
throw error
}
return transcription
}
}
/// An error to configure the service mocks with.
private struct ErrorMock: Error {}