Zum Inhalt

apple/PhysarumApp/Tests/PresetSlotsTests.swift

import XCTest
import PhysarumEngine
@testable import Physarum

final class PresetSlotsTests: XCTestCase {
    private func preset(_ name: String) -> Preset {
        var p = PresetCatalog.fallbackPreset; p.name = name; p.builtIn = false; return p
    }

    func testFillAssignsSlotIdAndIsNotUserOwned() {
        var s = PresetSlots()
        XCTAssertEqual(s.count, 24)
        s.fill(index: 3, preset: preset("Aurora"), userOwned: false)
        XCTAssertEqual(s.preset(at: 3)?.id, "slot-03")
        XCTAssertEqual(s.preset(at: 3)?.name, "Aurora")
        XCTAssertFalse(s.isUserOwned(3))
        XCTAssertEqual(s.filled.map { $0.id }, ["slot-03"])
    }

    func testOverwriteMarksUserOwnedAndKeepsSlotId() {
        var s = PresetSlots()
        s.fill(index: 3, preset: preset("Aurora"), userOwned: false)
        s.overwrite(slotID: "slot-03", preset: preset("Mine"))
        XCTAssertEqual(s.preset(at: 3)?.id, "slot-03")
        XCTAssertEqual(s.preset(at: 3)?.name, "Mine")
        XCTAssertTrue(s.isUserOwned(3))
    }

    func testClearEmptiesSlot() {
        var s = PresetSlots()
        s.fill(index: 3, preset: preset("Aurora"), userOwned: true)
        s.clear(slotID: "slot-03")
        XCTAssertNil(s.preset(at: 3))
        XCTAssertFalse(s.isUserOwned(3))
        XCTAssertTrue(s.filled.isEmpty)
    }

    func testFirstEmptyIndexFindsLowestGap() {
        var s = PresetSlots()
        s.fill(index: 0, preset: preset("A"), userOwned: false)
        s.fill(index: 1, preset: preset("B"), userOwned: false)
        XCTAssertEqual(s.firstEmptyIndex, 2)
    }
}

extension PresetSlotsTests {
    func testPinUnpinAndRotation() {
        var s = PresetSlots()
        s.fill(index: 0, preset: preset("A"), userOwned: false)
        s.fill(index: 1, preset: preset("B"), userOwned: false)
        s.fill(index: 2, preset: preset("C"), userOwned: false)
        XCTAssertEqual(s.rotation.map { $0.id }, ["slot-00", "slot-01", "slot-02"])  // nothing pinned → all filled

        s.pin("slot-02"); s.pin("slot-00")
        XCTAssertTrue(s.isPinned("slot-00"))
        XCTAssertEqual(s.rotation.map { $0.id }, ["slot-00", "slot-02"])  // pinned subset, in slot order

        s.unpin("slot-00")
        XCTAssertEqual(s.rotation.map { $0.id }, ["slot-02"])
    }

    func testPinIsIdempotentAndClearingRemovesPin() {
        var s = PresetSlots()
        s.fill(index: 0, preset: preset("A"), userOwned: true)
        s.pin("slot-00"); s.pin("slot-00")
        XCTAssertEqual(s.pinned.count, 1)
        s.clear(slotID: "slot-00")
        XCTAssertFalse(s.isPinned("slot-00"))     // clearing a slot drops its pin
        XCTAssertTrue(s.rotation.isEmpty)
    }
}

extension PresetSlotsTests {
    private func factory(_ names: [String], version: Int, pins: Set<String> = []) -> PresetSlots.Factory {
        .init(presets: names.map { preset($0) }, version: version, pinnedSlotIDs: pins)
    }

    func testSeedFillsEmptyOnFirstRun() {
        var s = PresetSlots()
        s.seed(factory(["A", "B"], version: 1))
        XCTAssertEqual(s.filled.map { $0.name }, ["A", "B"])
        XCTAssertEqual(s.seedVersion, 1)
        XCTAssertFalse(s.isUserOwned(0))   // seeded slots track the default
    }

    func testSeedUpdateSkipsUserOwnedButFillsUntouched() {
        var s = PresetSlots()
        s.seed(factory(["A", "B"], version: 1))
        s.overwrite(slotID: "slot-00", preset: preset("Mine"))   // user edits slot 0 → owned
        s.seed(factory(["A2", "B2", "C2"], version: 2))          // newer factory
        XCTAssertEqual(s.preset(at: 0)?.name, "Mine")            // user-owned slot untouched
        XCTAssertEqual(s.preset(at: 1)?.name, "B2")              // untouched slot takes new default
        XCTAssertEqual(s.preset(at: 2)?.name, "C2")              // new default fills empty slot
        XCTAssertEqual(s.seedVersion, 2)
    }

    func testSeedNoOpWhenVersionNotNewer() {
        var s = PresetSlots()
        s.seed(factory(["A"], version: 2))
        s.seed(factory(["X"], version: 2))   // same version → no change
        XCTAssertEqual(s.preset(at: 0)?.name, "A")
    }

    func testResetToFactoryClearsUserOwnedAndPins() {
        var s = PresetSlots()
        s.seed(factory(["A", "B"], version: 1, pins: ["slot-00"]))
        s.overwrite(slotID: "slot-00", preset: preset("Mine"))
        s.pin("slot-00")
        s.resetToFactory(factory(["A", "B"], version: 1, pins: ["slot-01"]))
        XCTAssertEqual(s.preset(at: 0)?.name, "A")          // back to factory
        XCTAssertFalse(s.isUserOwned(0))                    // ownership cleared
        XCTAssertEqual(s.pinned, ["slot-01"])               // pins reset to factory pins
    }
}

extension PresetSlotsTests {
    func testKeepOnlyPinnedCompactsAndRemapsPins() {
        var s = PresetSlots()
        s.fill(index: 0, preset: preset("A"), userOwned: false)
        s.fill(index: 1, preset: preset("B"), userOwned: true)
        s.fill(index: 2, preset: preset("C"), userOwned: false)
        s.fill(index: 5, preset: preset("D"), userOwned: true)
        s.pin("slot-01"); s.pin("slot-05")                  // keep B and D
        s.keepOnlyPinned()
        XCTAssertEqual(s.filled.map { $0.name }, ["B", "D"])   // compacted to the front, in slot order
        XCTAssertEqual(s.preset(at: 0)?.id, "slot-00")         // re-id'd to their new slot ids
        XCTAssertEqual(s.preset(at: 1)?.id, "slot-01")
        XCTAssertNil(s.preset(at: 2))                          // the rest are emptied
        XCTAssertTrue(s.isUserOwned(0))                        // userOwned preserved (B, D were owned)
        XCTAssertTrue(s.isUserOwned(1))
        XCTAssertEqual(s.pinned, ["slot-00", "slot-01"])       // pins remapped to the new ids
        XCTAssertEqual(s.rotation.map { $0.name }, ["B", "D"]) // rotation = the kept set
    }

    func testKeepOnlyPinnedIsNoOpWhenNothingPinned() {
        var s = PresetSlots()
        s.fill(index: 0, preset: preset("A"), userOwned: false)
        s.fill(index: 1, preset: preset("B"), userOwned: false)
        s.keepOnlyPinned()
        XCTAssertEqual(s.filled.map { $0.name }, ["A", "B"])   // untouched — would otherwise wipe everything
    }
}