rainbow-dragon/Tests/ForgePanelTests/PanelTests.swift

141 lines
5.5 KiB
Swift
Raw Permalink Normal View History

import XCTest
import Foundation
import ForgeGrade
import ForgeColor
@testable import ForgePanel
final class ControlMapTests: XCTestCase {
// Trackball delta maps to CDL offset per ball (lift/gamma/gain zones map to SOP).
func testTrackballToCDL() {
var grade = PanelGradeState()
let map = ControlMap.waveDefault
// Gain ball (ball 3) X delta -> slope red+.
let action = map.action(for: .encoder(id: TangentControls.gainBallX, delta: 10))
guard case .adjustCDL(let param, let amount) = action else { return XCTFail("wrong action") }
XCTAssertEqual(param, .slopeRed)
XCTAssertEqual(amount, 10 * map.sensitivity(for: .slopeRed), accuracy: 1e-8)
grade.apply(action!)
XCTAssertEqual(grade.cdl.slope.x, 1 + 10 * map.sensitivity(for: .slopeRed), accuracy: 1e-6)
}
// Master ring adjusts all three slope channels.
func testMasterRing() {
var grade = PanelGradeState()
let map = ControlMap.waveDefault
let action = map.action(for: .encoder(id: TangentControls.gainRing, delta: 5))
guard case .adjustCDL(let param, _) = action, param == .slopeMaster else {
return XCTFail("wrong mapping")
}
grade.apply(action!)
XCTAssertEqual(grade.cdl.slope.x, grade.cdl.slope.y, accuracy: 1e-7)
XCTAssertEqual(grade.cdl.slope.y, grade.cdl.slope.z, accuracy: 1e-7)
XCTAssertGreaterThan(grade.cdl.slope.x, 1)
}
// Sat knob.
func testSatKnob() {
var grade = PanelGradeState()
let map = ControlMap.waveDefault
let action = map.action(for: .encoder(id: TangentControls.satKnob, delta: -20))
grade.apply(action!)
XCTAssertLessThan(grade.cdl.saturation, 1)
}
// Buttons dispatch commands.
func testButtons() {
let map = ControlMap.waveDefault
XCTAssertEqual(map.action(for: .button(id: TangentControls.bypassButton, pressed: true)), .toggleBypass)
XCTAssertEqual(map.action(for: .button(id: TangentControls.grabStillButton, pressed: true)), .grabStill)
XCTAssertEqual(map.action(for: .button(id: TangentControls.nextSlotButton, pressed: true)), .nextSlot)
XCTAssertEqual(map.action(for: .button(id: TangentControls.resetButton, pressed: true)), .resetGrade)
// Release events ignored.
XCTAssertNil(map.action(for: .button(id: TangentControls.bypassButton, pressed: false)))
}
// Unmapped control -> nil.
func testUnmappedControl() {
XCTAssertNil(ControlMap.waveDefault.action(for: .encoder(id: 9999, delta: 1)))
}
// Reset action restores identity.
func testReset() {
var grade = PanelGradeState()
grade.apply(.adjustCDL(param: .slopeRed, amount: 0.5))
grade.apply(.adjustCDL(param: .offsetBlue, amount: -0.1))
XCTAssertNotEqual(grade.cdl, CDL.identity)
grade.apply(.resetGrade)
XCTAssertEqual(grade.cdl, CDL.identity)
}
// Display text for current params (panel readout strings).
func testDisplayText() {
var grade = PanelGradeState()
grade.apply(.adjustCDL(param: .slopeRed, amount: 0.234))
let text = grade.displayText(for: .slopeRed)
XCTAssertEqual(text, "SlpR 1.234")
}
}
final class TangentProtocolTests: XCTestCase {
// Frame encode: u32 LE length + JSON payload. Round trip.
func testFraming() throws {
let msg = TangentMessage.controlUpdate(.encoder(id: 42, delta: -3))
let framed = TangentWire.encode(msg)
var buf = framed
let decoded = try XCTUnwrap(TangentWire.decodeFirst(&buf))
guard case .controlUpdate(.encoder(let id, let delta)) = decoded else {
return XCTFail("wrong decode")
}
XCTAssertEqual(id, 42)
XCTAssertEqual(delta, -3)
XCTAssertTrue(buf.isEmpty)
}
// Partial frames buffer until complete.
func testPartialFrames() throws {
let msg = TangentMessage.controlUpdate(.button(id: 7, pressed: true))
let framed = TangentWire.encode(msg)
var buf = Data(framed.prefix(3))
XCTAssertNil(try TangentWire.decodeFirst(&buf))
buf.append(framed.suffix(from: 3))
XCTAssertNotNil(try TangentWire.decodeFirst(&buf))
}
// Two frames in one read both decode.
func testTwoFramesOneBuffer() throws {
var buf = TangentWire.encode(.controlUpdate(.encoder(id: 1, delta: 1)))
buf.append(TangentWire.encode(.controlUpdate(.encoder(id: 2, delta: 2))))
let first = try XCTUnwrap(TangentWire.decodeFirst(&buf))
let second = try XCTUnwrap(TangentWire.decodeFirst(&buf))
guard case .controlUpdate(.encoder(let id1, _)) = first,
case .controlUpdate(.encoder(let id2, _)) = second else {
return XCTFail()
}
XCTAssertEqual(id1, 1)
XCTAssertEqual(id2, 2)
}
// Display update encodes.
func testDisplayUpdate() throws {
let msg = TangentMessage.displayUpdate(line: 0, text: "SlpR 1.234")
var buf = TangentWire.encode(msg)
let decoded = try XCTUnwrap(TangentWire.decodeFirst(&buf))
guard case .displayUpdate(let line, let text) = decoded else { return XCTFail() }
XCTAssertEqual(line, 0)
XCTAssertEqual(text, "SlpR 1.234")
}
// Garbage payload throws.
func testGarbageRejected() {
var lenBytes = Data()
var len = UInt32(4).littleEndian
withUnsafeBytes(of: &len) { lenBytes.append(contentsOf: $0) }
var buf = lenBytes + Data("zzzz".utf8)
XCTAssertThrowsError(try TangentWire.decodeFirst(&buf))
}
}