Files
OSGKeyboard/OSGKeyboardTests/RecordButtonGesturePolicyTests.swift
Rocky 498f407585 feat(account): add managed credits and cloud gateway
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
2026-08-20 11:43:21 +08:00

123 lines
4.3 KiB
Swift

// RecordButtonGesturePolicyTests.swift
// OSGKeyboard · Tests
@testable import OSGKeyboardShared
import XCTest
final class RecordButtonGesturePolicyTests: XCTestCase {
// MARK: - Hold
func testHoldOnIdleStartsEditAndOwnsThePress() {
let action = RecordButtonGesturePolicy.holdAction(
phase: .idleReady,
isEnabled: true,
supportsEditLongPress: true
)
XCTAssertEqual(action, .beginEditLastInput)
XCTAssertTrue(RecordButtonGesturePolicy.consumesPress(action))
}
func testHoldWithoutEditActionLeavesThePressToTheTap() {
let action = RecordButtonGesturePolicy.holdAction(
phase: .idleReady,
isEnabled: true,
supportsEditLongPress: false
)
XCTAssertEqual(action, .none)
XCTAssertFalse(RecordButtonGesturePolicy.consumesPress(action))
// Release still starts plain dictation, so the hold is not a dead key.
XCTAssertEqual(
RecordButtonGesturePolicy.tapAction(phase: .idleReady, isEnabled: true),
.toggle
)
}
func testHoldWhileRecordingStops() {
let action = RecordButtonGesturePolicy.holdAction(
phase: .recording,
isEnabled: true,
supportsEditLongPress: true
)
XCTAssertEqual(action, .toggle)
XCTAssertTrue(RecordButtonGesturePolicy.consumesPress(action))
}
/// The press that opened an edit round is still down when the phase
/// reaches `.preparing`; a hold there must not end the round it started.
func testHoldWhilePreparingNeverActsOnItsOwn() {
let action = RecordButtonGesturePolicy.holdAction(
phase: .preparing,
isEnabled: true,
supportsEditLongPress: true
)
XCTAssertEqual(action, .none)
XCTAssertFalse(RecordButtonGesturePolicy.consumesPress(action))
}
func testHoldWhileProcessingIsInert() {
XCTAssertEqual(
RecordButtonGesturePolicy.holdAction(
phase: .processing,
isEnabled: true,
supportsEditLongPress: true
),
.none
)
}
// MARK: - Tap
func testTapCancelsWhilePreparingAndStopsWhileRecording() {
XCTAssertEqual(RecordButtonGesturePolicy.tapAction(phase: .preparing, isEnabled: true), .toggle)
XCTAssertEqual(RecordButtonGesturePolicy.tapAction(phase: .recording, isEnabled: true), .toggle)
}
func testTapWhileProcessingIsInert() {
XCTAssertEqual(RecordButtonGesturePolicy.tapAction(phase: .processing, isEnabled: true), .none)
}
/// The unavailable mic must stay tappable so the user can reach the reason.
func testTapOnUnavailableMicStillReportsThrough() {
XCTAssertEqual(
RecordButtonGesturePolicy.tapAction(phase: .idleUnavailable, isEnabled: false),
.toggle
)
}
func testDisabledMicSwallowsTapsInLivePhases() {
XCTAssertEqual(RecordButtonGesturePolicy.tapAction(phase: .recording, isEnabled: false), .none)
XCTAssertEqual(RecordButtonGesturePolicy.tapAction(phase: .preparing, isEnabled: false), .none)
}
// MARK: - One press, one action
/// Full edit round: hold arms, phase advances under the finger, and the
/// release must stay swallowed no matter which phase it lands in.
func testSinglePressProducesExactlyOneActionAcrossPhaseFlips() {
let hold = RecordButtonGesturePolicy.holdAction(
phase: .idleReady,
isEnabled: true,
supportsEditLongPress: true
)
XCTAssertEqual(hold, .beginEditLastInput)
var armed = RecordButtonGesturePolicy.consumesPress(hold)
XCTAssertTrue(armed)
for landingPhase in [RecordButton.Phase.preparing, .recording] {
// Release handling: an armed press is consumed, never replayed.
var delivered: RecordButtonGestureAction = .none
if armed {
armed = false
} else {
delivered = RecordButtonGesturePolicy.tapAction(
phase: landingPhase,
isEnabled: true
)
}
XCTAssertEqual(delivered, .none, "release in \(landingPhase) must not act")
armed = true
}
}
}