140 lines
5.5 KiB
Swift
140 lines
5.5 KiB
Swift
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))
|
|
}
|
|
}
|