Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 17 additions & 27 deletions .cursor/plans/sequence_feature_implementation_84c01c97.plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,54 @@ overview: implement a new Sequence class that allows controling playback of muti
todos:
- id: motion-sequence-class
content: Create Sequence class in packages/motion/src/Sequence.ts
status: pending
status: completed
- id: motion-sequence-types
content: Add SequenceOptions type to packages/motion/src/types.ts
status: pending
status: completed
dependencies:
- motion-sequence-class
- id: motion-sequence-export
content: Export Sequence and SequenceOptions from packages/motion/src/index.ts
status: pending
status: completed
dependencies:
- motion-sequence-class
- motion-sequence-types
- id: motion-get-sequence
content: Implement getSequence() function in packages/motion/src/motion.ts and export it
status: pending
status: completed
dependencies:
- motion-sequence-class
- motion-sequence-types
- id: interact-types
content: Update types in packages/interact/src/types.ts (SequenceOptionsConfig, SequenceConfig, SequenceConfigRef, InteractConfig, Interaction)
status: pending
status: completed
dependencies:
- motion-sequence-types
- id: interact-cache-types
content: Update InteractCache type to include sequences field
status: pending
status: completed
dependencies:
- interact-types
- id: interact-parse-config
content: Update parseConfig in packages/interact/src/core/Interact.ts to handle sequences
status: pending
status: completed
dependencies:
- interact-types
- interact-cache-types
- id: interact-add
content: Update effect processing in packages/interact/src/core/add.ts to create Sequence instances
status: pending
status: completed
dependencies:
- motion-get-sequence
- interact-parse-config
- id: interact-sequence-cache
content: Implement Sequence caching on Interact class (sequenceCache static property and getEffect() endpoint)
status: pending
status: completed
dependencies:
- interact-add
- id: interact-handlers
content: Update trigger handlers (viewEnter.ts, click.ts, etc.) to support Sequence instances
status: pending
status: completed
dependencies:
- interact-add
- id: tests-unit
Expand Down Expand Up @@ -165,10 +165,8 @@ type getSequence = (
) => Sequence;
```

The `getSequence()` funciton has 2 flows:

- If passed `animations: AnimationGroupArgs` it creates a `Sequence` from a single effect definition applied to multiple targets.
- If passed `animations: AnimationGroupArgs[]` it creates a `Sequence` from a each effect definition in the array.
The `getSequence()` funciton is passed `animations: AnimationGroupArgs[]` it creates a `Sequence` from a each effect definition in the array.
If an `Effect` in the array resolves to multiple elements, each resulting instance becomes an effect in the array.

## Part 2: @wix/interact Package Changes

Expand All @@ -186,15 +184,9 @@ export type SequenceOptionsConfig = {
};

// New SequenceConfig type
export type SequenceConfig = SequenceOptionsConfig &
(
| {
effect: Effect | EffectRef;
}
| {
effects: (Effect | EffectRef)[];
}
);
export type SequenceConfig = SequenceOptionsConfig & {
effects: (Effect | EffectRef)[];
};

// New SequenceConfigRef type
export type SequenceConfigRef = {
Expand Down Expand Up @@ -260,12 +252,10 @@ Modify `packages/interact/src/core/Interact.ts`:
2. Process `interaction.sequences` array:

- Resolve `sequenceId` references from `config.sequences`
- Process each effect within the sequence:
- Either as list of multiple effects as `effects: Effect[]`
- Or a single `effect: Effect` declaration, generating a list of effects on multiple target elements
- Process each effect within the sequence
- Generate unique IDs for sequence effects

3. Track sequence membership for effects (needed for delay calculation)
1. Track sequence membership for effects (needed for delay calculation)

### 2.4 Update Effect Processing in `add.ts`

Expand Down
211 changes: 211 additions & 0 deletions .cursor/plans/sequences_feature_tests_e12d5b15.plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
---
name: Sequences Feature Tests
overview: Create comprehensive test suites for the Sequences feature across both `@wix/motion` and `@wix/interact` packages, covering the Sequence class, getSequence function, AnimationGroup.applyOffset, config parsing, add/remove flows, listContainer interactions, and sequence caching.
todos:
- id: skeleton-motion
content: 'Create test file skeletons with describe/test titles for motion package: Sequence.spec.ts, applyOffset tests in AnimationGroup.spec.ts, getSequence.spec.ts'
status: completed
- id: skeleton-interact
content: 'Create test file skeleton with describe/test titles for interact package: sequences.spec.ts (suites A-G)'
status: completed
- id: impl-sequence-class
content: 'Implement Sequence.spec.ts tests: constructor, offset calculation, applyOffsets, inherited playback API, onFinish'
status: completed
- id: impl-apply-offset
content: Implement applyOffset() tests in AnimationGroup.spec.ts
status: completed
- id: impl-get-sequence
content: 'Implement getSequence.spec.ts tests: AnimationGroupArgs[] flow, options forwarding, edge cases'
status: completed
- id: impl-interact-config
content: 'Implement sequences.spec.ts Suite A: config parsing tests'
status: completed
- id: impl-interact-source
content: 'Implement sequences.spec.ts Suite B: sequence processing from source element via add()'
status: completed
- id: impl-interact-target
content: 'Implement sequences.spec.ts Suite C: cross-element sequence processing via addEffectsForTarget'
status: completed
- id: impl-interact-list
content: 'Implement sequences.spec.ts Suite D: sequence with listContainer -- add, addListItems, remove flows'
status: completed
- id: impl-interact-cleanup
content: 'Implement sequences.spec.ts Suite E: removal and cleanup tests'
status: completed
- id: impl-interact-cache
content: 'Implement sequences.spec.ts Suite F: Interact.getSequence caching tests'
status: completed
- id: impl-interact-mql
content: 'Implement sequences.spec.ts Suite G: media query condition tests on sequences'
status: completed
isProject: false
---

# Sequences Feature Test Plan

## Phase 1: Motion Package Tests

### 1.1 Create `packages/motion/test/Sequence.spec.ts`

Unit tests for the `Sequence` class in `[packages/motion/src/Sequence.ts](packages/motion/src/Sequence.ts)`. Follow the same `createMockAnimation` pattern from `[packages/motion/test/AnimationGroup.spec.ts](packages/motion/test/AnimationGroup.spec.ts)`.

**Test suites:**

- **Constructor**
- creates Sequence with empty groups array
- creates Sequence from multiple AnimationGroups
- flattens all child animations into parent `animations` array
- stores `animationGroups` reference
- defaults: delay=0, offset=0, offsetEasing=linear
- accepts custom delay, offset, and offsetEasing function
- resolves named offsetEasing string (e.g. `'quadIn'`) via `getJsEasing`
- resolves cubic-bezier offsetEasing string
- falls back to linear for invalid/unknown offsetEasing string
- **Offset calculation (calculateOffsets)**
- single group returns [0]
- linear easing with 5 groups and offset=200 produces [0, 200, 400, 600, 800]
- quadIn easing with 5 groups and offset=200 produces [0, 50, 200, 450, 800] (spec example)
- sineOut easing produces expected non-linear offsets
- floors fractional offsets via `| 0`
- **applyOffsets (via ready promise)**
- applies delay + calculated offset to each group via `group.applyOffset()`
- skips `applyOffset` when additionalDelay is 0
- waits for all group ready promises before applying offsets
- **Inherited playback API (from AnimationGroup)**
- `play()` plays all flattened animations
- `pause()` pauses all flattened animations
- `reverse()` reverses all flattened animations
- `cancel()` cancels all flattened animations
- `setPlaybackRate()` sets rate on all flattened animations
- `playState` returns from first animation
- **onFinish (overridden)**
- calls callback when all animation groups finish
- does not call callback if any group's `finished` rejects
- logs warning on interrupted animation
- handles empty groups array

### 1.2 Add `applyOffset` tests to `packages/motion/test/AnimationGroup.spec.ts`

Add a new `describe('applyOffset()')` section:

- adds offset to each animation's effect delay via `updateTiming`
- accumulates with existing delay
- skips animations with no effect
- handles empty animations array

### 1.3 Create `packages/motion/test/getSequence.spec.ts`

Tests for the `getSequence()` function in `[packages/motion/src/motion.ts](packages/motion/src/motion.ts)`. Must mock `getAnimation` / `getWebAnimation` as done in `[packages/motion/test/motion.spec.ts](packages/motion/test/motion.spec.ts)`.

**Test suites:**

- **AnimationGroupArgs[] flow**
- creates Sequence with one AnimationGroup per resolved target element
- handles a single entry with HTMLElement target
- handles a single entry with HTMLElement[] target (each element becomes its own group)
- handles a single entry with string selector target via `querySelectorAll`
- handles a single entry with null target (passed through to getAnimation)
- creates Sequence with one group per entry
- each entry independently resolves its target
- **Options forwarding**
- passes SequenceOptions (delay, offset, offsetEasing) to Sequence constructor
- passes context.reducedMotion to getAnimation
- **Edge cases**
- skips entries where getAnimation returns non-AnimationGroup
- returns Sequence with empty groups when all entries fail

## Phase 2: Interact Package Tests

### 2.1 Create `packages/interact/test/sequences.spec.ts`

Integration tests for sequence handling in the interact package. Follow the mock patterns from `[packages/interact/test/web.spec.ts](packages/interact/test/web.spec.ts)` with the `@wix/motion` mock, but also mock `getSequence` to return a mock Sequence object.

The `@wix/motion` mock needs to be extended to include:

```typescript
getSequence: vi.fn().mockReturnValue({
play: vi.fn(), cancel: vi.fn(), onFinish: vi.fn(),
pause: vi.fn(), reverse: vi.fn(), progress: vi.fn(),
persist: vi.fn(), isCSS: false, playState: 'idle',
ready: Promise.resolve(), animations: [], animationGroups: [],
}),
```

**Suite A: Config parsing (parseConfig via Interact.create)**

- parses inline sequence on interaction with `effects` array
- parses `sequenceId` reference from `config.sequences`
- merges inline overrides onto referenced sequence
- auto-generates sequenceId when not provided
- warns when referencing unknown sequenceId
- caches sequences in `dataCache.sequences`
- stores sequence effects in `interactions[target].sequences` for cross-element targets
- does not create cross-element entry when sequence effect targets same key as source (only `_processSequences` handles it)
- handles interaction with sequences but no effects (effects array is omitted/empty)

**Suite B: Sequence processing via `add()` -- source element**

- creates Sequence when source element is added with viewEnter trigger
- creates Sequence when source element is added with click trigger
- passes correct AnimationGroupArgs built from effect definitions
- resolves effectId references from config.effects
- skips sequence when target controller is not yet registered
- does not duplicate sequence on re-add (caching via `addedInteractions`)
- passes pre-created Sequence as `animation` option to trigger handler
- passes selectorCondition to handler options
- silently skips unresolved sequenceId reference at runtime (`_processSequences` returns early)
- skips entire sequence when any effect target element is missing (`_buildAnimationGroupArgsFromSequence` returns null)

**Suite C: Sequence processing via `addEffectsForTarget()` -- cross-element**

- creates Sequence when target element is added after source
- creates Sequence when source element is added after target
- handles sequences where effects target different keys
- skips variation when interaction-level MQL does not match and falls through to next variation
- skips when source controller is not yet registered
- `addEffectsForTarget` returns true when sequences exist even without effects

**Suite D: Sequence with listContainer**

- creates Sequence for each list item when source has listContainer
- creates new Sequence per `addListItems` call with unique cache key (each call uses `${cacheKey}::${generateId()}`)
- handles removing list items (via `removeListItems`) and subsequent re-add
- processes sequence effects from listContainer elements
- does not create duplicate sequence when list items overlap with existing
- skips sequence when listElements provided but no effects matched the listContainer (`usedListElements` guard)
- cross-element target: creates new Sequence per `addListItems` call for target sequences

**Suite E: Sequence removal and cleanup**

- `remove()` cleans up sequence cache entries for the removed key
- `Interact.destroy()` clears sequenceCache
- `deleteController()` removes sequence-related `addedInteractions` entries
- `clearInteractionStateForKey` removes sequenceCache entries by key prefix (`${key}::seq::`)

**Suite F: Interact.getSequence caching**

- returns cached Sequence for same cacheKey
- creates new Sequence for different cacheKey
- passes sequenceOptions and animationGroupArgs to motion's `getSequence`

**Suite G: Media query conditions on sequences**

- skips sequence when sequence-level condition does not match
- skips individual effect within sequence when effect-level condition does not match
- sets up media query listener for sequence conditions
- sets up media query listener for effect-level conditions within sequence

## Phase 3: Implementation Approach

Each phase above will be implemented in order:

1. First create all spec files with `describe`/`test` **skeletons only** (titles, no bodies)
2. Implement motion package tests (Sequence.spec.ts, applyOffset in AnimationGroup.spec.ts, getSequence.spec.ts)
3. Implement interact package sequence tests (sequences.spec.ts) suite by suite

### Key mock patterns to reuse

- `createMockAnimation()` from `AnimationGroup.spec.ts` for motion tests
- `vi.mock('@wix/motion', ...)` from `web.spec.ts` for interact tests, extended with `getSequence`
- `InteractionController` + `add()` helper for interact element setup
- `addListItems` import for list container tests
12 changes: 2 additions & 10 deletions packages/interact/dev/sequences-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ type SequenceOptions = {
/**
* The SequenceConfig type
*/
type SequenceConfig = SequenceOptions & ({
effect: Effect;
} | {
type SequenceConfig = SequenceOptions & {
effects: Effect[];
});
};
```

## The `Sequence.delay`
Expand Down Expand Up @@ -119,12 +117,6 @@ const sinOut = (t) => Math.sin((t * Math.PI) / 2);
- Note that `Sequence` does not have a `target`, so all of its API endpoints that involve an element target should be written accordingly, or not exist if not relevant.
- In the `@eix/interact` package `Sequence`s will be created from an `InteractConfig` for every declaration inside `Interaction.sequences`.

## Add `Sequence.effect` property

Another option for creating/declaring a `Sequence` from a single Effect on multiple elements is to use a new `effect` property as follows:

If `effect` is specified, a `Sequence` is created from the list of generated effects of the specified `Effect` on each of the matching target elements.

# Appendix

## A CSS solution in a futuristic world where CSS math functions are widely supported
Expand Down
Loading