Skip to content

test(transitions): add coverage for renderers/gpu.ts pure helpers + registry#258

Merged
walterlow merged 2 commits into
stagingfrom
test/gpu-transitions-coverage
May 21, 2026
Merged

test(transitions): add coverage for renderers/gpu.ts pure helpers + registry#258
walterlow merged 2 commits into
stagingfrom
test/gpu-transitions-coverage

Conversation

@walterlow
Copy link
Copy Markdown
Owner

@walterlow walterlow commented May 21, 2026

Summary

The 1293-line `shared/timeline/transitions/renderers/gpu.ts` had no test file after the dead-code purge in PR #252. Adds focused coverage for what's testable without a real `OffscreenCanvas`:

Pure helpers (exported and tested for invariants):

  • `clamp01` — clamping above/below the [0,1] range, including ±Infinity
  • `smoothStep` — endpoints, midpoint (0.5 for symmetric interval), zero-width interval (epsilon guard), monotonicity at the midpoint
  • `getNumericProperty` — present number, missing key, undefined object, non-number types, NaN/Infinity
  • `seededRandom` — determinism, range [0, 1), distinct values for nearby seeds
  • `fadeOpacity` — outgoing/incoming endpoints, constant-power crossfade asserted at midpoint (so a future "just use linear opacity" rewrite fails loudly)
  • `crossDissolveT` — endpoints, midpoint, out-of-range clamping, monotonicity

Registry smoke test: `registerGpuTransitions` on a fresh `TransitionRegistry` registers exactly 15 known IDs, each with `renderCanvas` and a matching `gpuTransitionId`. The IDs are persisted on projects and are looked up by `TransitionPipeline` keyed by `gpuTransitionId`, so adding/removing/renaming one is a schema-affecting change worth flagging in CI.

What this PR is NOT

Per-renderer pixel tests were skipped intentionally — jsdom can't run `OffscreenCanvas` without the `canvas` npm package, so they'd reduce to mock-call assertions with low signal. The renderers' geometric/pixel correctness is best caught by visual review or browser-environment smoke tests.

Diff shape

  • +39 tests (5 added new export keywords to existing helpers, 1 new test file)
  • Full suite: 367 files / 2304 tests pass
  • tsc clean, lint 0/0

Summary by CodeRabbit

  • Tests

    • Added a comprehensive test suite for GPU transition renderers validating numeric math, easing behavior, opacity/crossfade semantics, random determinism, and exact transition registration.
  • Refactor

    • Made GPU transition helper utilities publicly available to improve API surface and reuse.

Review Change Stack

…egistry

The file had no test coverage after the dead-code purge in PR #252.
Covers what's testable without a real OffscreenCanvas:

  - 6 pure helpers (clamp01, smoothStep, getNumericProperty,
    seededRandom, fadeOpacity, crossDissolveT) — exported and
    tested for invariants. fadeOpacity's constant-power crossfade is
    asserted explicitly so a future "just use linear opacity" rewrite
    fails loudly.

  - Registry smoke: registerGpuTransitions on a fresh TransitionRegistry
    registers exactly 15 known IDs, each with renderCanvas and a
    matching gpuTransitionId. Locks the GPU transition contract — the
    IDs appear on persisted projects and are looked up by the GPU
    pipeline (TransitionPipeline) keyed by gpuTransitionId, so
    adding/removing one is a schema-affecting change worth flagging.

Per-renderer pixel tests skipped — jsdom can't run OffscreenCanvas
without the canvas npm package, so they'd be mock-call assertions
with low signal.

39 tests added. Full suite: 367 files / 2304 tests pass.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
freecut Ready Ready Preview, Comment May 21, 2026 1:37pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 80ffe3fd-6cd3-41d2-b2d4-6e874e799ab9

📥 Commits

Reviewing files that changed from the base of the PR and between fd2c846 and 610f50d.

📒 Files selected for processing (1)
  • src/shared/timeline/transitions/renderers/gpu.test.ts

📝 Walkthrough

Walkthrough

Exports six GPU helper functions from src/shared/timeline/transitions/renderers/gpu.ts and adds gpu.test.ts, which validates numeric/easing helpers, effect interpolation behavior, and the registerGpuTransitions registration contract against a stable EXPECTED_GPU_TRANSITION_IDS list.

Changes

GPU Transition Utilities

Layer / File(s) Summary
Export GPU utility helpers
src/shared/timeline/transitions/renderers/gpu.ts
Six internal helper functions are exported: numeric/easing utilities (clamp01, smoothStep, getNumericProperty) and effect interpolation helpers (seededRandom, fadeOpacity, crossDissolveT). Only visibility changed.
Test GPU utilities and validate contracts
src/shared/timeline/transitions/renderers/gpu.test.ts
New test module defines EXPECTED_GPU_TRANSITION_IDS and validates utility behavior (clamping, smoothstep edges/zero-width, numeric property fallbacks, seeded randomness determinism/range), fade opacity and constant-power crossfade semantics, crossDissolveT easing endpoints/midpoint/clamping/monotonicity, and that registerGpuTransitions registers the exact expected renderer IDs with matching renderer properties and transition definitions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • walterlow/freecut#141: Related to fade blending behavior; both PRs touch fade math and renderer crossfade implementations.
  • walterlow/freecut#140: Related to GPU shader migration and registration metadata (gpuTransitionId) used in the registration contract tests.
  • walterlow/freecut#208: Adds GPU transition implementations and registration that align with the IDs validated in these tests.

Poem

A rabbit tests each GPU glide and hop,
Six helpers leapt from shadows to the top,
Clamp, smooth, seed, fade — numbers in a row,
Transitions hum, the renderers all know. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and concisely summarizes the main change: adding test coverage for the gpu.ts pure helper functions and the registry. It is specific, clear, and accurately reflects the primary focus of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/gpu-transitions-coverage

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/shared/timeline/transitions/renderers/gpu.test.ts (1)

1-11: ⚡ Quick win

Update guidance for src/shared/timeline/transitions/renderers/gpu.test.ts

  • Keep vite-plus/test here (the repo’s tests consistently use it).
  • Don’t add an explicit src/test/setup.ts import: vite.config.ts already sets test.environment: 'jsdom' and test.setupFiles: ['./src/test/setup.ts'].
  • @/* exists in tsconfig.json, but nearby transition renderer tests also use relative imports, so this isn’t a correctness issue (optional to align on @/* for consistency).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/timeline/transitions/renderers/gpu.test.ts` around lines 1 - 11,
Keep the existing top imports as-is: retain "vite-plus/test" (so the test uses
describe/expect/it from that module) and the relative imports of
TransitionRegistry and functions (clamp01, crossDissolveT, fadeOpacity,
getNumericProperty, registerGpuTransitions, seededRandom, smoothStep); do not
add an explicit import of "src/test/setup.ts" because vite.config.ts already
configures test.environment and test.setupFiles, and ensure no changes replace
the relative module paths with an absolute "`@/`..." alias unless you
intentionally want to standardize all nearby tests to that style.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/shared/timeline/transitions/renderers/gpu.test.ts`:
- Around line 1-11: Keep the existing top imports as-is: retain "vite-plus/test"
(so the test uses describe/expect/it from that module) and the relative imports
of TransitionRegistry and functions (clamp01, crossDissolveT, fadeOpacity,
getNumericProperty, registerGpuTransitions, seededRandom, smoothStep); do not
add an explicit import of "src/test/setup.ts" because vite.config.ts already
configures test.environment and test.setupFiles, and ensure no changes replace
the relative module paths with an absolute "`@/`..." alias unless you
intentionally want to standardize all nearby tests to that style.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a8e2401b-8d5f-42d8-b23a-1455687249b2

📥 Commits

Reviewing files that changed from the base of the PR and between 8220726 and fd2c846.

📒 Files selected for processing (2)
  • src/shared/timeline/transitions/renderers/gpu.test.ts
  • src/shared/timeline/transitions/renderers/gpu.ts

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 21, 2026

Greptile Summary

Adds a focused test file for the pure helper functions and registry smoke test in gpu.ts, and exports six previously-private helpers to make them importable. No production logic is changed.

  • gpu.ts: Six helper functions (clamp01, smoothStep, getNumericProperty, seededRandom, fadeOpacity, crossDissolveT) gain export keyword only; all implementation unchanged.
  • gpu.test.ts: 39 new tests cover invariants for each helper and assert the registry contains exactly 15 stable GPU transition IDs, each with a renderCanvas method and matching gpuTransitionId.

Confidence Score: 4/5

Safe to merge — only export keyword additions to production code, no logic changes.

The smoothStep "is smooth at the midpoint" test has an inverted comment and an upper bound too loose to catch a linear-opacity rewrite — the exact invariant the PR description highlights as the test's purpose. The test will pass even if smoothStep is replaced with a linear ramp, undermining the regression guard it claims to provide.

gpu.test.ts — the smoothStep monotonicity assertions near line 75

Important Files Changed

Filename Overview
src/shared/timeline/transitions/renderers/gpu.test.ts New 205-line test file covering pure helpers and registry smoke test. Logic assertions are correct but one comment in the smoothStep "is smooth" test is factually backwards (claims slope is shallower than linear when it is steeper), and the upper bound is too loose to guard the invariant it claims to protect.
src/shared/timeline/transitions/renderers/gpu.ts Only change is adding export keyword to six helper functions (clamp01, smoothStep, getNumericProperty, seededRandom, fadeOpacity, crossDissolveT) to make them importable by the new test file. No logic changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[gpu.test.ts] -->|imports| B[gpu.ts pure helpers]
    A -->|imports| C[TransitionRegistry]
    B --> D[clamp01]
    B --> E[smoothStep]
    B --> F[getNumericProperty]
    B --> G[seededRandom]
    B --> H[fadeOpacity]
    B --> I[crossDissolveT]
    A -->|calls| J[registerGpuTransitions]
    J -->|populates| C
    C -->|asserts 15 IDs| K[Registry smoke test]
Loading

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/shared/timeline/transitions/renderers/gpu.test.ts:75-83
**Misleading comment and loose upper bound in "is smooth" test**

The inline comment `// shallower than linear (0.02)` is factually wrong. The derivative of `3t²-2t³` at `t=0.5` is `6t(1-t) = 1.5`, so the finite difference over `[0.49, 0.51]` is approximately `0.02 × 1.5 = 0.03`*steeper* than the linear `0.02`, not shallower. The upper bound `0.1` is also five times the linear value and lets any function with slope ≤ 5 pass, meaning a near-linear step or a triangle wave at the midpoint would not be caught. As written this test mainly checks monotonicity (which the prior test already confirms) and doesn't protect against a "just use linear" rewrite the PR description says it should guard against.

### Issue 2 of 2
src/shared/timeline/transitions/renderers/gpu.test.ts:184-194
Each `it.each` iteration builds a brand-new `TransitionRegistry` and calls `registerGpuTransitions` for all 15 entries, so the function is invoked 15 times in this single test block alone (on top of the two other `describe` tests in `registerGpuTransitions`). Using a shared `beforeAll` registry avoids the redundant work and matches the pattern used in the surrounding `it` tests that already create a shared registry per describe block.

```suggestion
  it.each(EXPECTED_GPU_TRANSITION_IDS)(
    'registers "%s" with a renderCanvas method and a matching gpuTransitionId',
    (id) => {
      // Use the registry already populated by the first test in this describe block.
      const registry = new TransitionRegistry()
      registerGpuTransitions(registry)
      const renderer = registry.getRenderer(id)
      expect(renderer, `${id} renderer should be registered`).toBeDefined()
      expect(typeof renderer?.renderCanvas, `${id} should have renderCanvas`).toBe('function')
      expect(renderer?.gpuTransitionId, `${id} should set gpuTransitionId`).toBe(id)
    },
  )
```

Reviews (1): Last reviewed commit: "test(transitions): add coverage for rend..." | Re-trigger Greptile

Comment on lines +75 to +83
it('handles a zero-width interval (edge0 === edge1) without NaN', () => {
const value = smoothStep(0.5, 0.5, 0.6)
expect(Number.isFinite(value)).toBe(true)
expect(value).toBe(1)
})
})

describe('getNumericProperty', () => {
it('returns the property when it is a finite number', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Misleading comment and loose upper bound in "is smooth" test

The inline comment // shallower than linear (0.02) is factually wrong. The derivative of 3t²-2t³ at t=0.5 is 6t(1-t) = 1.5, so the finite difference over [0.49, 0.51] is approximately 0.02 × 1.5 = 0.03steeper than the linear 0.02, not shallower. The upper bound 0.1 is also five times the linear value and lets any function with slope ≤ 5 pass, meaning a near-linear step or a triangle wave at the midpoint would not be caught. As written this test mainly checks monotonicity (which the prior test already confirms) and doesn't protect against a "just use linear" rewrite the PR description says it should guard against.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/shared/timeline/transitions/renderers/gpu.test.ts
Line: 75-83

Comment:
**Misleading comment and loose upper bound in "is smooth" test**

The inline comment `// shallower than linear (0.02)` is factually wrong. The derivative of `3t²-2t³` at `t=0.5` is `6t(1-t) = 1.5`, so the finite difference over `[0.49, 0.51]` is approximately `0.02 × 1.5 = 0.03`*steeper* than the linear `0.02`, not shallower. The upper bound `0.1` is also five times the linear value and lets any function with slope ≤ 5 pass, meaning a near-linear step or a triangle wave at the midpoint would not be caught. As written this test mainly checks monotonicity (which the prior test already confirms) and doesn't protect against a "just use linear" rewrite the PR description says it should guard against.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Comment on lines +184 to +194
it.each(EXPECTED_GPU_TRANSITION_IDS)(
'registers "%s" with a renderCanvas method and a matching gpuTransitionId',
(id) => {
const registry = new TransitionRegistry()
registerGpuTransitions(registry)
const renderer = registry.getRenderer(id)
expect(renderer, `${id} renderer should be registered`).toBeDefined()
expect(typeof renderer?.renderCanvas, `${id} should have renderCanvas`).toBe('function')
expect(renderer?.gpuTransitionId, `${id} should set gpuTransitionId`).toBe(id)
},
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Each it.each iteration builds a brand-new TransitionRegistry and calls registerGpuTransitions for all 15 entries, so the function is invoked 15 times in this single test block alone (on top of the two other describe tests in registerGpuTransitions). Using a shared beforeAll registry avoids the redundant work and matches the pattern used in the surrounding it tests that already create a shared registry per describe block.

Suggested change
it.each(EXPECTED_GPU_TRANSITION_IDS)(
'registers "%s" with a renderCanvas method and a matching gpuTransitionId',
(id) => {
const registry = new TransitionRegistry()
registerGpuTransitions(registry)
const renderer = registry.getRenderer(id)
expect(renderer, `${id} renderer should be registered`).toBeDefined()
expect(typeof renderer?.renderCanvas, `${id} should have renderCanvas`).toBe('function')
expect(renderer?.gpuTransitionId, `${id} should set gpuTransitionId`).toBe(id)
},
)
it.each(EXPECTED_GPU_TRANSITION_IDS)(
'registers "%s" with a renderCanvas method and a matching gpuTransitionId',
(id) => {
// Use the registry already populated by the first test in this describe block.
const registry = new TransitionRegistry()
registerGpuTransitions(registry)
const renderer = registry.getRenderer(id)
expect(renderer, `${id} renderer should be registered`).toBeDefined()
expect(typeof renderer?.renderCanvas, `${id} should have renderCanvas`).toBe('function')
expect(renderer?.gpuTransitionId, `${id} should set gpuTransitionId`).toBe(id)
},
)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/shared/timeline/transitions/renderers/gpu.test.ts
Line: 184-194

Comment:
Each `it.each` iteration builds a brand-new `TransitionRegistry` and calls `registerGpuTransitions` for all 15 entries, so the function is invoked 15 times in this single test block alone (on top of the two other `describe` tests in `registerGpuTransitions`). Using a shared `beforeAll` registry avoids the redundant work and matches the pattern used in the surrounding `it` tests that already create a shared registry per describe block.

```suggestion
  it.each(EXPECTED_GPU_TRANSITION_IDS)(
    'registers "%s" with a renderCanvas method and a matching gpuTransitionId',
    (id) => {
      // Use the registry already populated by the first test in this describe block.
      const registry = new TransitionRegistry()
      registerGpuTransitions(registry)
      const renderer = registry.getRenderer(id)
      expect(renderer, `${id} renderer should be registered`).toBeDefined()
      expect(typeof renderer?.renderCanvas, `${id} should have renderCanvas`).toBe('function')
      expect(renderer?.gpuTransitionId, `${id} should set gpuTransitionId`).toBe(id)
    },
  )
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Two P2 comments on the new gpu.test.ts:

1. The "is smooth" test had its math backwards in both the comment and
   the bound. d/dt(3t²-2t³) at t=0.5 is 1.5, so the central difference
   over [0.49, 0.51] is ~0.03 — STEEPER than the linear 0.02, not
   shallower as the comment claimed. The upper bound 0.1 was also too
   loose to catch a "just use linear" rewrite. Replaced with two
   independent assertions:
     - eased S-shape: smoothStep(0.25) < 0.25 and smoothStep(0.75) >
       0.75 (a linear curve fails both)
     - midpoint slope in a tight band [0.025, 0.035] around the
       analytical 0.03 (a linear curve at 0.02 or a step at 0 fails)

2. it.each rebuilt the registry on every iteration — 15 redundant
   registrations per test block. Hoisted the registry construction to
   a shared const inside the describe (the registry is the unit under
   test and never mutates, so reuse is safe and matches the pattern
   used in the surrounding describe blocks).

39 tests still pass; tsc clean, lint 0/0.
@walterlow walterlow merged commit 6783ddc into staging May 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant