How to Test Keyframe Functionality in Palmier Pro: A Swift Testing Guide
You test keyframe functionality in Palmier Pro by writing unit tests that exercise KeyframeTrack mutations and verify interpolation sampling using Swift’s Testing framework.
Testing animation keyframes is critical for video editor reliability. In the palmier-io/palmier-pro repository, the keyframe system centers on generic KeyframeTrack<Value> types that manage timeline mutations and mathematical interpolation. This guide demonstrates how to validate track operations, interpolation modes, and clip-level integrations using the concrete implementation found in Sources/PalmierPro/Models/Keyframe.swift.
Understanding the Core Architecture
Before writing tests, familiarize yourself with the three fundamental types defined in Sources/PalmierPro/Models/Keyframe.swift:
Keyframe<Value>– Stores a timeline frame index, a generic value (e.g., opacity, position), and an outgoing interpolation mode (linear,hold, orsmooth).KeyframeTrack<Value>– A sorted array of keyframes (lines 7–25) that exposes core mutating operations (upsert,remove,move) and guarantees that frames remain sorted and duplicates collapse to the latest write.KeyframeInterpolatable– A protocol implemented byDouble,AnimPair, andCropextensions that enables thesample(at:fallback:)method to calculate interpolated values between keyframes (lines 31–50).
The Clip model in Sources/PalmierPro/Models/Timeline.swift attaches these tracks to video clips, while Sources/PalmierPro/Inspector/Keyframes/KeyframesLane.swift provides the UI layer that reads the same public APIs.
Setting Up the Swift Testing Framework
The test suite lives in Tests/PalmierProTests/Timeline/KeyframeTests.swift. Palmier Pro uses Swift Testing (the modern successor to XCTest) to validate behavior. Import the PalmierPro module and create focused test functions that isolate specific track operations, mutation invariants, and interpolation accuracy.
Testing Track Mutations
Verify that upsert, remove, and move maintain sorted order and enforce the "last-write-wins" rule. When you call upsert on an existing frame, the new value must replace the old one without breaking the sort order.
import Testing
@testable import PalmierPro
@Test func trackMutationMaintainsSortOrder() {
var track = KeyframeTrack<Double>()
// Insert out-of-order
track.upsert(Keyframe(frame: 20, value: 2.0))
track.upsert(Keyframe(frame: 5, value: 0.5))
track.upsert(Keyframe(frame: 10, value: 1.0))
// Track automatically sorts by frame
#expect(track.keyframes.map(\.frame) == [5, 10, 20])
// Move keyframe while preserving order
track.move(from: 5, to: 15)
#expect(track.keyframes.map(\.frame) == [10, 15, 20])
#expect(track.keyframes[1].value == 0.5)
}
Validating Interpolation Logic
Test the sample(at:fallback:) method to ensure empty tracks return the fallback value, single keyframes clamp across the entire timeline, and interpolation modes produce mathematically correct results.
@Test func linearInterpolationSampling() {
let linear = KeyframeTrack<Double>()
linear.upsert(Keyframe(frame: 0, value: 0, interpolationOut: .linear))
linear.upsert(Keyframe(frame: 10, value: 10))
#expect(linear.sample(at: 3, fallback: 0) == 3) // Linear lerp
#expect(linear.sample(at: 5, fallback: 0) == 5)
}
@Test func holdInterpolationBehavior() {
// Hold interpolation keeps the left keyframe's value until the next frame
let hold = KeyframeTrack<Double>()
hold.upsert(Keyframe(frame: 0, value: 0, interpolationOut: .hold))
hold.upsert(Keyframe(frame: 10, value: 10))
#expect(hold.sample(at: 1, fallback: 0) == 0)
#expect(hold.sample(at: 9, fallback: 0) == 0)
#expect(hold.sample(at: 10, fallback: 0) == 10)
}
Testing Clip-Level Frame Translation
Clip extensions (lines 94–128 in Keyframe.swift) provide helpers like upsertKeyframe, moveKeyframe, and keyframeFrames that translate between absolute timeline frames and clip-relative offsets. Test these to ensure frame calculations remain accurate when a clip starts at a non-zero timeline position.
@Test func clipKeyframeFrameTranslation() {
var clip = Clip(startFrame: 100, endFrame: 200)
// Insert at absolute timeline frame 110
clip.upsertKeyframe(in: \.opacityTrack, frame: 110, value: 0.8)
// Helper converts to clip-relative frame 10
#expect(clip.opacityTrack?.keyframes.first?.frame == 10)
// Move using absolute coordinates
clip.moveKeyframe(for: .opacity, from: 110, to: 150)
#expect(clip.keyframeFrames(for: .opacity) == [150])
}
Summary
- Keyframe testing validates both data structure integrity (sorted order, duplicate handling) and mathematical interpolation accuracy.
- Test location: Place unit tests in
Tests/PalmierProTests/Timeline/KeyframeTests.swiftusing Swift Testing syntax. - Track mutations: Verify that
upsert,remove, andmovemaintain sorted order and enforce last-write-wins semantics. - Interpolation: Confirm that
sample(at:fallback:)correctly handles empty tracks, single keyframes, andlinear/hold/smoothmodes. - Clip integration: Test absolute-to-relative frame translation via
Cliphelper methods to ensure timeline accuracy.
Frequently Asked Questions
What interpolation modes does Palmier Pro support?
Palmier Pro supports linear, hold, and smooth interpolation modes, controlled by the interpolationOut property on each Keyframe. The sample(at:fallback:) method in KeyframeTrack uses the left-hand keyframe’s interpolation setting to calculate values between frames, as implemented in Sources/PalmierPro/Models/Keyframe.swift (lines 31–50).
How does KeyframeTrack handle duplicate frames?
When you call upsert with a keyframe at an existing frame index, the track enforces a "last-write-wins" rule. The new value replaces the existing one at that index, and the internal array remains sorted. This invariant is guaranteed by the KeyframeTrack implementation (lines 7–25) and should be verified in mutation tests.
Can I test custom animation properties beyond opacity and position?
Yes. Because KeyframeTrack is generic, adding a new property like blur only requires adding a KeyframeTrack<BlurValue>? field to the Clip model in Sources/PalmierPro/Models/Timeline.swift. The existing generic tests in KeyframeTests.swift will automatically validate the new track’s behavior without requiring additional test code.
How do I test frame translation between timeline and clip coordinates?
Use the Clip extension methods such as upsertKeyframe and moveKeyframe, which automatically convert absolute timeline frames to clip-relative offsets. Verify the conversion by asserting that the underlying track’s frame values match the expected relative offsets after performing operations on absolute timeline positions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →