Files
hyperframes/packages/studio/src/utils/canvasNudgeGate.test.ts
T
Miguel Ángelandukimsanov 8e5b18f740 fix(studio): continuation of #2280 (#2285)
* feat(studio): timeline collision and placement model

What: new pure module timelineCollision — zone-aware drop placement
(clampTrackToZone, resolveZoneDropPlacement, resolveInsertRow,
resolvePlacement, lane/overlap predicates) with its full test suite.

Why: the no-overlap core of the NLE clip-drag engine; plain functions, no
DOM, no React, no store writes.

How: new files only; type-only imports from the existing playerStore.
First runtime consumer arrives with the drag-engine PRs.

Test plan: bunx vitest run timelineCollision.test.ts; tsc --noEmit; fallow
audit clean (all exports test-consumed).

* feat(studio): timeline magnetic snapping

What: new pure module timelineSnapping — snap-target collection and
pixel-threshold time snapping (collectTimelineSnapTargets, snapTimelineTime,
snapMoveToTargets) with tests.

Why: the magnet math for clip drags/trims, reviewable standalone.

How: new files only; type-only playerStore imports; consumers land with the
drag engine.

Test plan: bunx vitest run timelineSnapping.test.ts; tsc --noEmit; fallow
audit clean.

* feat(studio): multi-clip drag preview math

What: new pure module timelineMultiDragPreview — group-drag passenger
offsets and clamped group deltas (isMultiDragActive, multiDragDeltaSeconds,
multiDragPassengerOffsetPx, clampGroupMoveDelta) with tests.

Why: the group-drag math, standalone and DOM-free.

How: new files only; consumed later by TimelineLanes.

Test plan: bunx vitest run timelineMultiDragPreview.test.ts; tsc --noEmit;
fallow audit clean.

* feat(studio): timeline z-stacking sync model

What: new pure module timelineStackingSync — lane order ↔ z-index
reconciliation (laneIsAbove, computeStackingPatches) with tests.

Why: the single source of truth for how timeline lane order maps to canvas
stacking; the ordering rules and tie-breaks live here.

How: new files only; consumed later by timelineZones and the stacking-sync
hook.

Test plan: bunx vitest run timelineStackingSync.test.ts; tsc --noEmit;
fallow audit clean.

* feat(studio): timeline lane-zone model

What: new pure module timelineZones — visual/audio track-zone
classification (classifyZone) and normalizeToZones, which re-packs lanes
into zone-consistent rows; tests cover the stacking/zones interaction.

Why: completes the z-model started in the stacking-sync PR.

How: new files; consumes isAudioTimelineElement (leaf-helpers PR) and
computeStackingPatches (stacking-sync PR); type-only playerStore imports.

Test plan: bunx vitest run timelineZones.test.ts; tsc --noEmit; fallow
audit clean.

* feat(studio): asset click policy and canvas nudge gate

What: two small pure modules with tests — assetClickBehavior (click vs
double-click policy for sidebar assets) and canvasNudgeGate (debounce gate
for arrow-key canvas nudges).

Why: policy dependencies of the upcoming asset card and nudge hook,
reviewable as plain decision tables.

How: new files only.

Test plan: bunx vitest run on both test files; tsc --noEmit; fallow audit
clean.

---------

Co-authored-by: ukimsanov <ular.kimsanov@heygen.com>
2026-07-12 00:19:00 -04:00

32 lines
1.1 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { __resetForTests, acquireCanvasNudgeKeys, canvasNudgeKeysClaimed } from "./canvasNudgeGate";
describe("canvasNudgeGate", () => {
// The claim counter is module-level state; reset it so an unbalanced claim in one
// test can't leak into the next.
beforeEach(() => {
__resetForTests();
});
it("reports claimed while at least one claim is held", () => {
expect(canvasNudgeKeysClaimed()).toBe(false);
const releaseA = acquireCanvasNudgeKeys();
const releaseB = acquireCanvasNudgeKeys();
expect(canvasNudgeKeysClaimed()).toBe(true);
releaseA();
expect(canvasNudgeKeysClaimed()).toBe(true);
releaseB();
expect(canvasNudgeKeysClaimed()).toBe(false);
});
it("makes release idempotent so an effect re-run cannot underflow", () => {
const release = acquireCanvasNudgeKeys();
release();
release();
expect(canvasNudgeKeysClaimed()).toBe(false);
const releaseNext = acquireCanvasNudgeKeys();
expect(canvasNudgeKeysClaimed()).toBe(true);
releaseNext();
});
});