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, or smooth).
  • 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 by Double, AnimPair, and Crop extensions that enables the sample(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.swift using Swift Testing syntax.
  • Track mutations: Verify that upsert, remove, and move maintain sorted order and enforce last-write-wins semantics.
  • Interpolation: Confirm that sample(at:fallback:) correctly handles empty tracks, single keyframes, and linear/hold/smooth modes.
  • Clip integration: Test absolute-to-relative frame translation via Clip helper 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →