Implemented the resumed interrupted recordings and allowed processing discards on the RecordingViewModel view model in the Recording package target.
This commit is contained in:
@@ -14,12 +14,14 @@ struct RecordingViewModelTests {
|
||||
@Suite("Initial state")
|
||||
struct InitialState {
|
||||
|
||||
@Test func `initial state is not recording with a zeroed timer`() {
|
||||
@Test
|
||||
func `initial state is not recording with a zeroed timer`() {
|
||||
let model = Model()
|
||||
|
||||
#expect(model.state == .notRecording)
|
||||
#expect(model.elapsedSeconds == 0)
|
||||
#expect(model.error == nil)
|
||||
#expect(model.textAlertMessage == nil)
|
||||
#expect(model.textTimer == "00:00")
|
||||
#expect(model.transcription == nil)
|
||||
}
|
||||
@@ -68,7 +70,7 @@ struct RecordingViewModelTests {
|
||||
|
||||
@Test(arguments: zip(
|
||||
[Model.State.notRecording, .recording, .paused, .processing],
|
||||
[Model.State.notRecording, .recording, .notRecording, .processing]
|
||||
[Model.State.notRecording, .recording, .notRecording, .notRecording]
|
||||
))
|
||||
func `pressed discard transitions to the expected state`(
|
||||
from initial: Model.State,
|
||||
@@ -136,6 +138,46 @@ struct RecordingViewModelTests {
|
||||
#expect(model.isProcessing == expected)
|
||||
}
|
||||
|
||||
@Test(arguments: zip(
|
||||
[Model.State.notRecording, .recording, .paused, .processing],
|
||||
[
|
||||
LocalizedStringResource.viewRecordingButtonMainLabelRecord,
|
||||
.viewRecordingButtonMainLabelPause,
|
||||
.viewRecordingButtonMainLabelResume,
|
||||
.viewRecordingButtonMainLabelRecord,
|
||||
]
|
||||
))
|
||||
func `main label matches the state`(
|
||||
for state: Model.State,
|
||||
expected: LocalizedStringResource
|
||||
) {
|
||||
let model = Model()
|
||||
|
||||
model.drive(to: state)
|
||||
|
||||
#expect(model.labelMain == expected)
|
||||
}
|
||||
|
||||
@Test(arguments: zip(
|
||||
[Model.State.notRecording, .recording, .paused, .processing],
|
||||
[
|
||||
LocalizedStringResource.viewRecordingButtonSendLabelSend,
|
||||
.viewRecordingButtonSendLabelSend,
|
||||
.viewRecordingButtonSendLabelSend,
|
||||
.viewRecordingButtonSendLabelProcessing,
|
||||
]
|
||||
))
|
||||
func `send label matches the state`(
|
||||
for state: Model.State,
|
||||
expected: LocalizedStringResource
|
||||
) {
|
||||
let model = Model()
|
||||
|
||||
model.drive(to: state)
|
||||
|
||||
#expect(model.labelSend == expected)
|
||||
}
|
||||
|
||||
@Test(arguments: zip(
|
||||
[Model.State.notRecording, .recording, .paused, .processing],
|
||||
[false, false, false, true]
|
||||
@@ -155,7 +197,7 @@ struct RecordingViewModelTests {
|
||||
[Model.State.notRecording, .recording, .paused, .processing],
|
||||
[false, false, true, true]
|
||||
))
|
||||
func `discard button is visible only while paused`(
|
||||
func `discard button is visible while paused or processing`(
|
||||
for state: Model.State,
|
||||
expected: Bool
|
||||
) {
|
||||
@@ -204,7 +246,8 @@ struct RecordingViewModelTests {
|
||||
@Suite("Timer")
|
||||
struct Timer {
|
||||
|
||||
@Test func `ticks while recording`() async throws {
|
||||
@Test
|
||||
func `ticks while recording`() async throws {
|
||||
let model = Model()
|
||||
|
||||
model.pressedMain()
|
||||
@@ -217,7 +260,8 @@ struct RecordingViewModelTests {
|
||||
model.pressedMain()
|
||||
}
|
||||
|
||||
@Test func `stops while paused`() async throws {
|
||||
@Test
|
||||
func `stops while paused`() async throws {
|
||||
let model = Model()
|
||||
|
||||
model.pressedMain()
|
||||
@@ -234,7 +278,8 @@ struct RecordingViewModelTests {
|
||||
#expect(model.elapsedSeconds == secondsWhenPaused)
|
||||
}
|
||||
|
||||
@Test func `keeps elapsed seconds when resumed`() async throws {
|
||||
@Test
|
||||
func `keeps elapsed seconds when resumed`() async throws {
|
||||
let model = Model()
|
||||
|
||||
model.pressedMain()
|
||||
@@ -252,7 +297,8 @@ struct RecordingViewModelTests {
|
||||
model.pressedMain()
|
||||
}
|
||||
|
||||
@Test func `restarts for a new recording`() async throws {
|
||||
@Test
|
||||
func `restarts for a new recording`() async throws {
|
||||
let model = Model(
|
||||
transcribe: TranscribingMock()
|
||||
)
|
||||
@@ -283,7 +329,8 @@ struct RecordingViewModelTests {
|
||||
@Suite("Capturer")
|
||||
struct Capturer {
|
||||
|
||||
@Test func `starts the capture for a new recording`() async throws {
|
||||
@Test
|
||||
func `starts the capture for a new recording`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
@@ -296,7 +343,8 @@ struct RecordingViewModelTests {
|
||||
model.pressedMain()
|
||||
}
|
||||
|
||||
@Test func `pauses the capture while paused`() async throws {
|
||||
@Test
|
||||
func `pauses the capture while paused`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
@@ -308,7 +356,8 @@ struct RecordingViewModelTests {
|
||||
#expect(capturer.calls == ["start", "pause"])
|
||||
}
|
||||
|
||||
@Test func `resumes the capture after a pause`() async throws {
|
||||
@Test
|
||||
func `resumes the capture after a pause`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
@@ -323,7 +372,8 @@ struct RecordingViewModelTests {
|
||||
model.pressedMain()
|
||||
}
|
||||
|
||||
@Test func `stops the capture when the input is sent`() async throws {
|
||||
@Test
|
||||
func `stops the capture when the input is sent`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(
|
||||
capturer: capturer,
|
||||
@@ -337,7 +387,8 @@ struct RecordingViewModelTests {
|
||||
#expect(capturer.calls == ["start", "pause", "stop"])
|
||||
}
|
||||
|
||||
@Test func `stops the capture when a paused recording is discarded`() async throws {
|
||||
@Test
|
||||
func `stops the capture when a paused recording is discarded`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
@@ -352,7 +403,8 @@ struct RecordingViewModelTests {
|
||||
#expect(model.elapsedSeconds == 0)
|
||||
}
|
||||
|
||||
@Test func `serializes the calls when states change rapidly`() async throws {
|
||||
@Test
|
||||
func `serializes the calls when states change rapidly`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
|
||||
capturer.delay = .seconds(0.2)
|
||||
@@ -374,7 +426,8 @@ struct RecordingViewModelTests {
|
||||
model.pressedMain()
|
||||
}
|
||||
|
||||
@Test func `falls back to not recording when the capture fails`() async throws {
|
||||
@Test
|
||||
func `falls back to not recording when the capture fails`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
|
||||
capturer.error = ErrorMock()
|
||||
@@ -396,7 +449,8 @@ struct RecordingViewModelTests {
|
||||
@Suite("Errors")
|
||||
struct Errors {
|
||||
|
||||
@Test func `surfaces a capture failure`() async throws {
|
||||
@Test
|
||||
func `surfaces a capture failure`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
|
||||
capturer.error = ErrorMock()
|
||||
@@ -409,9 +463,11 @@ struct RecordingViewModelTests {
|
||||
|
||||
#expect(model.error == .captureFailed)
|
||||
#expect(model.state == .notRecording)
|
||||
#expect(model.textAlertMessage == .viewRecordingAlertErrorMessageCapture)
|
||||
}
|
||||
|
||||
@Test func `surfaces a denied microphone permission`() async throws {
|
||||
@Test
|
||||
func `surfaces a denied microphone permission`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
|
||||
capturer.error = AudioCapturingError.permissionNotGranted
|
||||
@@ -423,9 +479,11 @@ struct RecordingViewModelTests {
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
#expect(model.error == .permissionDenied)
|
||||
#expect(model.textAlertMessage == .viewRecordingAlertErrorMessagePermission)
|
||||
}
|
||||
|
||||
@Test func `surfaces unavailable speech model assets`() async throws {
|
||||
@Test
|
||||
func `surfaces unavailable speech model assets`() async throws {
|
||||
let transcriber = TranscribingMock()
|
||||
|
||||
transcriber.error = AudioTranscribingError.assetsNotInstalled
|
||||
@@ -439,10 +497,12 @@ struct RecordingViewModelTests {
|
||||
try await Task.sleep(for: .seconds(0.5))
|
||||
|
||||
#expect(model.error == .assetsUnavailable)
|
||||
#expect(model.textAlertMessage == .viewRecordingAlertErrorMessageAssets)
|
||||
#expect(model.transcription == nil)
|
||||
}
|
||||
|
||||
@Test func `surfaces a transcription failure`() async throws {
|
||||
@Test
|
||||
func `surfaces a transcription failure`() async throws {
|
||||
let transcriber = TranscribingMock()
|
||||
|
||||
transcriber.error = ErrorMock()
|
||||
@@ -456,10 +516,12 @@ struct RecordingViewModelTests {
|
||||
try await Task.sleep(for: .seconds(0.5))
|
||||
|
||||
#expect(model.error == .transcriptionFailed)
|
||||
#expect(model.textAlertMessage == .viewRecordingAlertErrorMessageTranscription)
|
||||
#expect(model.transcription == nil)
|
||||
}
|
||||
|
||||
@Test func `clears the error when dismissed`() async throws {
|
||||
@Test
|
||||
func `clears the error when dismissed`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
|
||||
capturer.error = ErrorMock()
|
||||
@@ -475,9 +537,11 @@ struct RecordingViewModelTests {
|
||||
model.dismissedError()
|
||||
|
||||
#expect(model.error == nil)
|
||||
#expect(model.textAlertMessage == nil)
|
||||
}
|
||||
|
||||
@Test func `clears the error when a new recording starts`() async throws {
|
||||
@Test
|
||||
func `clears the error when a new recording starts`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
|
||||
capturer.error = ErrorMock()
|
||||
@@ -506,7 +570,8 @@ struct RecordingViewModelTests {
|
||||
@Suite("Interruptions")
|
||||
struct Interruptions {
|
||||
|
||||
@Test func `pauses an ongoing recording when the capture is interrupted`() async throws {
|
||||
@Test
|
||||
func `pauses an ongoing recording when the capture is interrupted`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
@@ -522,7 +587,8 @@ struct RecordingViewModelTests {
|
||||
#expect(capturer.calls == ["start", "pause"])
|
||||
}
|
||||
|
||||
@Test func `changes nothing when the capture is interrupted while paused`() async throws {
|
||||
@Test
|
||||
func `changes nothing when the capture is interrupted while paused`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
@@ -539,6 +605,68 @@ struct RecordingViewModelTests {
|
||||
#expect(capturer.calls == ["start", "pause"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func `resumes an interrupted recording when the interruption ends with the resume hint`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
model.pressedMain()
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
capturer.interrupt()
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
capturer.endInterruption(shouldResume: true)
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
#expect(model.state == .recording)
|
||||
#expect(capturer.calls == ["start", "pause", "resume"])
|
||||
|
||||
model.pressedMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
func `stays paused when the interruption ends without the resume hint`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
model.pressedMain()
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
capturer.interrupt()
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
capturer.endInterruption(shouldResume: false)
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
#expect(model.state == .paused)
|
||||
#expect(capturer.calls == ["start", "pause"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func `never resumes a recording the user paused`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let model = Model(capturer: capturer)
|
||||
|
||||
model.pressedMain()
|
||||
model.pressedMain()
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
capturer.endInterruption(shouldResume: true)
|
||||
|
||||
try await Task.sleep(for: .seconds(0.1))
|
||||
|
||||
#expect(model.state == .paused)
|
||||
#expect(capturer.calls == ["start", "pause"])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Processing
|
||||
@@ -547,7 +675,8 @@ struct RecordingViewModelTests {
|
||||
@Suite("Processing")
|
||||
struct Processing {
|
||||
|
||||
@Test func `returns to not recording with a reset timer and a transcription`() async throws {
|
||||
@Test
|
||||
func `returns to not recording with a reset timer and a transcription`() async throws {
|
||||
let model = Model(
|
||||
transcribe: TranscribingMock()
|
||||
)
|
||||
@@ -569,7 +698,8 @@ struct RecordingViewModelTests {
|
||||
#expect(model.transcription != nil)
|
||||
}
|
||||
|
||||
@Test func `clears the transcription when the transcriber fails`() async throws {
|
||||
@Test
|
||||
func `clears the transcription when the transcriber fails`() async throws {
|
||||
let transcriber = TranscribingMock()
|
||||
|
||||
transcriber.error = ErrorMock()
|
||||
@@ -587,7 +717,34 @@ struct RecordingViewModelTests {
|
||||
#expect(model.transcription == nil)
|
||||
}
|
||||
|
||||
@Test func `clears the transcription when a new recording starts`() async throws {
|
||||
@Test
|
||||
func `discarding while processing cancels the transcription`() async throws {
|
||||
let capturer = CapturingMock()
|
||||
let transcriber = TranscribingMock()
|
||||
|
||||
transcriber.delay = .seconds(0.5)
|
||||
|
||||
let model = Model(
|
||||
capturer: capturer,
|
||||
transcribe: transcriber
|
||||
)
|
||||
|
||||
model.drive(to: .processing)
|
||||
|
||||
try await Task.sleep(for: .seconds(0.2))
|
||||
|
||||
model.pressedDiscard()
|
||||
|
||||
try await Task.sleep(for: .seconds(0.6))
|
||||
|
||||
#expect(model.state == .notRecording)
|
||||
#expect(model.elapsedSeconds == 0)
|
||||
#expect(model.error == nil)
|
||||
#expect(model.transcription == nil)
|
||||
}
|
||||
|
||||
@Test
|
||||
func `clears the transcription when a new recording starts`() async throws {
|
||||
let model = Model(
|
||||
transcribe: TranscribingMock()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user