Zum Inhalt

apple/PhysarumApp/Tests/UndoRedoTests.swift

import XCTest
@testable import Physarum
import PhysarumEngine

final class UndoRedoTests: XCTestCase {
    private func moveSpeed0() -> ParamDescriptor {
        ParamRegistry.all.first { $0.id == .moveSpeed(0) }!
    }

    func testPushUndoThenUndoRestoresPreviousValue() {
        let state = AppState()
        let d = moveSpeed0()
        let original = d.get(state.edited)

        state.pushUndo()                 // snapshot pre-edit
        d.set(&state.edited, original + 0.5)
        XCTAssertEqual(d.get(state.edited), original + 0.5, accuracy: 1e-4)

        XCTAssertTrue(state.canUndo)
        state.undoEdit()
        XCTAssertEqual(d.get(state.edited), original, accuracy: 1e-4)
    }

    func testRedoReappliesUndoneEdit() {
        let state = AppState()
        let d = moveSpeed0()
        let original = d.get(state.edited)

        state.pushUndo()
        d.set(&state.edited, original + 0.5)
        state.undoEdit()
        XCTAssertTrue(state.canRedo)
        state.redoEdit()
        XCTAssertEqual(d.get(state.edited), original + 0.5, accuracy: 1e-4)
    }

    func testNewEditAfterUndoInvalidatesRedo() {
        let state = AppState()
        let d = moveSpeed0()
        let base = d.get(state.edited)

        state.pushUndo(); d.set(&state.edited, base + 0.5)
        state.undoEdit()
        XCTAssertTrue(state.canRedo)

        state.pushUndo()                 // a new committed edit
        d.set(&state.edited, base + 0.9)
        XCTAssertFalse(state.canRedo, "redo must be cleared after a new edit")
    }

    func testUndoStackIsCapped() {
        let state = AppState()
        for _ in 0..<80 { state.pushUndo() }
        XCTAssertLessThanOrEqual(state.undoStack.count, 50)
    }

    func testSelectingPresetClearsHistory() {
        // Two known slots so there is a second preset to switch to (independent of the bundle).
        func p(_ n: String) -> Preset { var x = PresetCatalog.fallbackPreset; x.name = n; x.builtIn = false; return x }
        let factory = PresetSlots.Factory(presets: [p("A"), p("B")], version: 1)
        let state = AppState(slotStore: SlotStore(backend: InMemorySlotBackend(), factory: factory))
        state.pushUndo()
        XCTAssertTrue(state.canUndo)
        // select a different preset than the current one
        let other = state.presets.first { $0.id != state.selectedID }!
        state.select(id: other.id)
        XCTAssertFalse(state.canUndo)
        XCTAssertFalse(state.canRedo)
    }
}