// 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) } }