114 lines
3.3 KiB
Swift
114 lines
3.3 KiB
Swift
// ModifierStateTests.swift
|
|
// Unit tests for ModifierState — the sticky-modifier observable for ModifierBar.
|
|
//
|
|
// ModifierState is pure state (no UIKit, no networking) so all tests are
|
|
// synchronous. The class is @MainActor; every test method is also marked
|
|
// @MainActor to satisfy Swift 6 strict concurrency.
|
|
|
|
import XCTest
|
|
@testable import piRemote
|
|
|
|
@MainActor
|
|
final class ModifierStateTests: XCTestCase {
|
|
|
|
// Fresh instance per test.
|
|
private var state: ModifierState!
|
|
|
|
override func setUp() async throws {
|
|
state = ModifierState()
|
|
}
|
|
|
|
override func tearDown() async throws {
|
|
state = nil
|
|
}
|
|
|
|
// MARK: - Initial state
|
|
|
|
/// Both published properties must be `false` immediately after init.
|
|
func testInitialState_bothFalse() {
|
|
XCTAssertFalse(state.ctrlActive, "ctrlActive should start as false")
|
|
XCTAssertFalse(state.isRepeating, "isRepeating should start as false")
|
|
}
|
|
|
|
// MARK: - toggleCtrl
|
|
|
|
/// First toggle arms the Ctrl modifier.
|
|
func testToggleCtrl_armsModifier() {
|
|
state.toggleCtrl()
|
|
XCTAssertTrue(state.ctrlActive)
|
|
}
|
|
|
|
/// Second toggle disarms it.
|
|
func testToggleCtrl_disarmsModifier() {
|
|
state.toggleCtrl() // arm
|
|
state.toggleCtrl() // disarm
|
|
XCTAssertFalse(state.ctrlActive)
|
|
}
|
|
|
|
/// Six successive toggles cycle true/false/true/false/true/false correctly.
|
|
func testMultipleToggles_cycleCorrectly() {
|
|
for i in 1...6 {
|
|
state.toggleCtrl()
|
|
let expected = (i % 2 == 1)
|
|
XCTAssertEqual(
|
|
state.ctrlActive, expected,
|
|
"After \(i) toggle(s) ctrlActive should be \(expected)"
|
|
)
|
|
}
|
|
}
|
|
|
|
/// `toggleCtrl` must not affect `isRepeating`.
|
|
func testToggleCtrl_doesNotAffectIsRepeating() {
|
|
state.isRepeating = true
|
|
state.toggleCtrl()
|
|
XCTAssertTrue(state.isRepeating, "toggleCtrl must not touch isRepeating")
|
|
}
|
|
|
|
// MARK: - reset
|
|
|
|
/// `reset()` clears `ctrlActive` when it was armed.
|
|
func testReset_clearsCtrActive() {
|
|
state.toggleCtrl()
|
|
XCTAssertTrue(state.ctrlActive) // precondition
|
|
|
|
state.reset()
|
|
XCTAssertFalse(state.ctrlActive)
|
|
}
|
|
|
|
/// `reset()` clears `isRepeating` when it was set.
|
|
func testReset_clearsIsRepeating() {
|
|
state.isRepeating = true
|
|
state.reset()
|
|
XCTAssertFalse(state.isRepeating)
|
|
}
|
|
|
|
/// `reset()` clears both properties simultaneously.
|
|
func testReset_clearsAllState() {
|
|
state.toggleCtrl()
|
|
state.isRepeating = true
|
|
|
|
state.reset()
|
|
|
|
XCTAssertFalse(state.ctrlActive, "ctrlActive must be false after reset()")
|
|
XCTAssertFalse(state.isRepeating, "isRepeating must be false after reset()")
|
|
}
|
|
|
|
/// Calling `reset()` on an already-neutral state must not crash or change values.
|
|
func testReset_isIdempotent() {
|
|
// state is already all-false from setUp
|
|
state.reset()
|
|
state.reset()
|
|
XCTAssertFalse(state.ctrlActive)
|
|
XCTAssertFalse(state.isRepeating)
|
|
}
|
|
|
|
/// `reset()` after many toggles returns to neutral.
|
|
func testReset_afterManyToggles_returnsToNeutral() {
|
|
for _ in 0..<10 { state.toggleCtrl() }
|
|
state.isRepeating = true
|
|
state.reset()
|
|
XCTAssertFalse(state.ctrlActive)
|
|
XCTAssertFalse(state.isRepeating)
|
|
}
|
|
}
|