91 lines
3.9 KiB
Swift
91 lines
3.9 KiB
Swift
// DictationProgressTests.swift
|
|
// OSGKeyboard · Keyboard Extension Tests
|
|
//
|
|
// Staged microphone progress fill: monotonic, never completes on its own,
|
|
// steps forward at the ASR → polish milestone, and learns polish latency.
|
|
|
|
@testable import OSGKeyboardShared
|
|
import XCTest
|
|
|
|
final class DictationProgressTests: XCTestCase {
|
|
private let start: TimeInterval = 1_000
|
|
|
|
private func timeline(polishingAfter seconds: TimeInterval? = nil,
|
|
expected: TimeInterval = 3) -> DictationProgressTimeline {
|
|
var timeline = DictationProgressTimeline(utteranceID: UUID(), transcribingStartedAt: start)
|
|
if let seconds {
|
|
timeline.markPolishing(at: start + seconds, characterCount: 40, expectedSeconds: expected)
|
|
}
|
|
return timeline
|
|
}
|
|
|
|
func testTranscribingStaysBelowCeiling() {
|
|
let ceiling = DictationProgressEstimator.transcribingCeiling
|
|
XCTAssertLessThan(DictationProgressEstimator.fraction(for: timeline(), at: start + 3), ceiling)
|
|
// Long waits converge onto the ceiling (exp underflows to exactly it), never past it.
|
|
XCTAssertLessThanOrEqual(DictationProgressEstimator.fraction(for: timeline(), at: start + 60), ceiling)
|
|
XCTAssertEqual(DictationProgressEstimator.fraction(for: timeline(), at: start), 0)
|
|
}
|
|
|
|
func testFractionIsMonotonicAcrossMilestone() {
|
|
let subject = timeline(polishingAfter: 0.8)
|
|
var previous = -1.0
|
|
for step in 0...600 {
|
|
let value = DictationProgressEstimator.fraction(for: subject, at: start + Double(step) * 0.05)
|
|
XCTAssertGreaterThanOrEqual(value, previous)
|
|
previous = value
|
|
}
|
|
}
|
|
|
|
func testPolishingStepsPastFloorAndNeverCompletes() {
|
|
let subject = timeline(polishingAfter: 0.8)
|
|
let afterBlend = DictationProgressEstimator.fraction(for: subject, at: start + 0.8 + 0.3)
|
|
XCTAssertGreaterThanOrEqual(afterBlend, DictationProgressEstimator.polishingFloor)
|
|
let muchLater = DictationProgressEstimator.fraction(for: subject, at: start + 600)
|
|
XCTAssertLessThan(muchLater, 1)
|
|
XCTAssertLessThanOrEqual(muchLater, DictationProgressEstimator.polishingCeiling)
|
|
}
|
|
|
|
func testRepeatedRawReadyKeepsFirstMilestone() {
|
|
var subject = timeline(polishingAfter: 1)
|
|
subject.markPolishing(at: start + 5, characterCount: 999, expectedSeconds: 30)
|
|
XCTAssertEqual(subject.polishingStartedAt, start + 1)
|
|
XCTAssertEqual(subject.polishCharacterCount, 40)
|
|
}
|
|
|
|
func testCalibrationMovesTowardObservedLatencyWithinBounds() throws {
|
|
let suite = "DictationProgressTests.\(UUID().uuidString)"
|
|
let defaults = try XCTUnwrap(UserDefaults(suiteName: suite))
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let calibration = DictationPolishLatencyCalibration(defaults: defaults)
|
|
|
|
let initial = calibration.expectedSeconds(forCharacterCount: 100)
|
|
calibration.record(observedSeconds: 8, characterCount: 100)
|
|
XCTAssertGreaterThan(calibration.expectedSeconds(forCharacterCount: 100), initial)
|
|
|
|
for _ in 0..<50 { calibration.record(observedSeconds: 0.01, characterCount: 0) }
|
|
XCTAssertEqual(
|
|
calibration.overheadSeconds,
|
|
DictationPolishLatencyCalibration.overheadRange.lowerBound,
|
|
accuracy: 0.001
|
|
)
|
|
calibration.record(observedSeconds: -1, characterCount: 0)
|
|
XCTAssertEqual(
|
|
calibration.overheadSeconds,
|
|
DictationPolishLatencyCalibration.overheadRange.lowerBound,
|
|
accuracy: 0.001
|
|
)
|
|
}
|
|
|
|
@MainActor
|
|
func testTimelineClearsWhenProcessingEnds() {
|
|
let state = KeyboardState()
|
|
state.phase = .processing
|
|
state.dictationProgress = timeline()
|
|
state.phase = .processing
|
|
XCTAssertNotNil(state.dictationProgress)
|
|
state.phase = .idle
|
|
XCTAssertNil(state.dictationProgress)
|
|
}
|
|
}
|