@pyreon/kinetic 0.24.4 → 0.24.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -12
- package/src/Collapse.tsx +0 -166
- package/src/Stagger.tsx +0 -63
- package/src/Transition.tsx +0 -280
- package/src/TransitionGroup.tsx +0 -139
- package/src/__tests__/Collapse.test.tsx +0 -803
- package/src/__tests__/GroupRenderer.test.tsx +0 -434
- package/src/__tests__/StaggerRenderer.test.tsx +0 -523
- package/src/__tests__/Transition.ssr.test.tsx +0 -183
- package/src/__tests__/Transition.test.tsx +0 -403
- package/src/__tests__/TransitionItem.test.tsx +0 -514
- package/src/__tests__/kinetic-modes.ssr.test.tsx +0 -214
- package/src/__tests__/kinetic.browser.test.tsx +0 -327
- package/src/__tests__/kinetic.test.tsx +0 -565
- package/src/__tests__/presets.test.ts +0 -46
- package/src/__tests__/stagger-component-children-hydration.test.tsx +0 -191
- package/src/__tests__/top-level-transition-stagger-function-children.test.tsx +0 -141
- package/src/__tests__/useAnimationEnd.test.ts +0 -194
- package/src/__tests__/useReducedMotion.test.ts +0 -160
- package/src/__tests__/useTransitionState.test.ts +0 -132
- package/src/__tests__/utils.test.ts +0 -139
- package/src/index.ts +0 -15
- package/src/jsx-augment.d.ts +0 -12
- package/src/kinetic/CollapseRenderer.tsx +0 -216
- package/src/kinetic/GroupRenderer.tsx +0 -149
- package/src/kinetic/StaggerRenderer.tsx +0 -94
- package/src/kinetic/TransitionItem.tsx +0 -250
- package/src/kinetic/TransitionRenderer.tsx +0 -230
- package/src/kinetic/createKineticComponent.tsx +0 -224
- package/src/kinetic/types.ts +0 -149
- package/src/kinetic.ts +0 -25
- package/src/presets.ts +0 -66
- package/src/types.ts +0 -118
- package/src/useAnimationEnd.ts +0 -59
- package/src/useReducedMotion.ts +0 -28
- package/src/useTransitionState.ts +0 -62
- package/src/utils.ts +0 -113
|
@@ -1,514 +0,0 @@
|
|
|
1
|
-
import type { VNode } from '@pyreon/core'
|
|
2
|
-
import { h } from '@pyreon/core'
|
|
3
|
-
import { signal } from '@pyreon/reactivity'
|
|
4
|
-
|
|
5
|
-
let _reducedMotion = false
|
|
6
|
-
|
|
7
|
-
vi.mock('../useReducedMotion', () => ({
|
|
8
|
-
useReducedMotion: () => () => _reducedMotion,
|
|
9
|
-
}))
|
|
10
|
-
|
|
11
|
-
import TransitionItem from '../kinetic/TransitionItem'
|
|
12
|
-
|
|
13
|
-
// Mock rAF for deterministic double-rAF testing
|
|
14
|
-
let rafCallbacks: (() => void)[] = []
|
|
15
|
-
const originalRaf = globalThis.requestAnimationFrame
|
|
16
|
-
const originalCaf = globalThis.cancelAnimationFrame
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
vi.useFakeTimers()
|
|
20
|
-
rafCallbacks = []
|
|
21
|
-
|
|
22
|
-
vi.stubGlobal(
|
|
23
|
-
'requestAnimationFrame',
|
|
24
|
-
vi.fn((cb: () => void) => {
|
|
25
|
-
rafCallbacks.push(cb)
|
|
26
|
-
return rafCallbacks.length
|
|
27
|
-
}),
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
afterEach(() => {
|
|
34
|
-
vi.useRealTimers()
|
|
35
|
-
vi.stubGlobal('requestAnimationFrame', originalRaf)
|
|
36
|
-
vi.stubGlobal('cancelAnimationFrame', originalCaf)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const flushRaf = () => {
|
|
40
|
-
const cbs = [...rafCallbacks]
|
|
41
|
-
rafCallbacks = []
|
|
42
|
-
for (const cb of cbs) cb()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const fireTransitionEnd = (el: HTMLElement) => {
|
|
46
|
-
const event = new Event('transitionend', { bubbles: true })
|
|
47
|
-
Object.defineProperty(event, 'target', { value: el })
|
|
48
|
-
el.dispatchEvent(event)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Recursively finds and invokes all refs in a VNode tree,
|
|
53
|
-
* wiring them to the given element.
|
|
54
|
-
*/
|
|
55
|
-
const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
56
|
-
if (!vnode) return
|
|
57
|
-
const visitNode = (node: VNode) => {
|
|
58
|
-
const nodeProps = node.props as Record<string, unknown>
|
|
59
|
-
if (typeof nodeProps?.ref === 'function') {
|
|
60
|
-
;(nodeProps.ref as (element: HTMLElement | null) => void)(el)
|
|
61
|
-
} else if (nodeProps?.ref && typeof nodeProps.ref === 'object') {
|
|
62
|
-
;(nodeProps.ref as { current: HTMLElement | null }).current = el
|
|
63
|
-
}
|
|
64
|
-
if (node.children) {
|
|
65
|
-
const ch = Array.isArray(node.children) ? node.children : [node.children]
|
|
66
|
-
for (const c of ch) {
|
|
67
|
-
if (c && typeof c === 'object' && 'type' in (c as object)) visitNode(c as VNode)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (nodeProps?.children) {
|
|
71
|
-
const pc = Array.isArray(nodeProps.children) ? nodeProps.children : [nodeProps.children]
|
|
72
|
-
for (const c of pc) {
|
|
73
|
-
if (c && typeof c === 'object' && 'type' in (c as object)) visitNode(c as VNode)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (
|
|
77
|
-
nodeProps?.fallback &&
|
|
78
|
-
typeof nodeProps.fallback === 'object' &&
|
|
79
|
-
'type' in (nodeProps.fallback as object)
|
|
80
|
-
) {
|
|
81
|
-
visitNode(nodeProps.fallback as VNode)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
visitNode(vnode)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Helper: call TransitionItem and wire a mock element to refs.
|
|
89
|
-
*/
|
|
90
|
-
const setupTransitionItem = (props: Record<string, unknown>) => {
|
|
91
|
-
const el = document.createElement('div')
|
|
92
|
-
// Real h() instead of a mock literal — same VNode shape as production.
|
|
93
|
-
const child = h('div', { 'data-testid': 'child' }, 'Hello') as VNode
|
|
94
|
-
|
|
95
|
-
const vnode = TransitionItem({
|
|
96
|
-
...props,
|
|
97
|
-
children: child,
|
|
98
|
-
} as any)
|
|
99
|
-
|
|
100
|
-
wireRef(vnode, el)
|
|
101
|
-
|
|
102
|
-
return { vnode, el }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
describe('TransitionItem', () => {
|
|
106
|
-
it('returns a VNode when show returns true', () => {
|
|
107
|
-
const show = () => true
|
|
108
|
-
const child = h('div', null, 'Hello') as VNode
|
|
109
|
-
const vnode = TransitionItem({ show, children: child })
|
|
110
|
-
expect(vnode).not.toBeNull()
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('wraps child in a Show component', () => {
|
|
114
|
-
const show = () => true
|
|
115
|
-
const child = h('div', null, 'Hello') as VNode
|
|
116
|
-
const vnode = TransitionItem({ show, children: child })
|
|
117
|
-
expect(vnode).not.toBeNull()
|
|
118
|
-
expect(typeof vnode?.type).toBe('function')
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('clones child VNode with merged ref', () => {
|
|
122
|
-
const show = () => true
|
|
123
|
-
const child = h('div', null, 'Hello') as VNode
|
|
124
|
-
const vnode = TransitionItem({ show, children: child })
|
|
125
|
-
|
|
126
|
-
// The Show component's children (or fallback) should have a ref prop
|
|
127
|
-
const showProps = vnode?.props as Record<string, unknown>
|
|
128
|
-
const showChildren = showProps?.children as VNode | undefined
|
|
129
|
-
if (showChildren) {
|
|
130
|
-
const childProps = showChildren.props as Record<string, unknown>
|
|
131
|
-
expect(childProps?.ref).toBeDefined()
|
|
132
|
-
expect(typeof childProps?.ref).toBe('function')
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('fires onEnter callback when entering', () => {
|
|
137
|
-
const show = signal(false)
|
|
138
|
-
const onEnter = vi.fn()
|
|
139
|
-
|
|
140
|
-
setupTransitionItem({ show: () => show(), onEnter })
|
|
141
|
-
|
|
142
|
-
show.set(true)
|
|
143
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('fires onLeave callback when leaving', () => {
|
|
147
|
-
const show = signal(true)
|
|
148
|
-
const onLeave = vi.fn()
|
|
149
|
-
|
|
150
|
-
setupTransitionItem({ show: () => show(), onLeave })
|
|
151
|
-
|
|
152
|
-
show.set(false)
|
|
153
|
-
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('applies enter classes when entering', () => {
|
|
157
|
-
const show = signal(false)
|
|
158
|
-
const { el } = setupTransitionItem({
|
|
159
|
-
show: () => show(),
|
|
160
|
-
enter: 'ti-enter',
|
|
161
|
-
enterFrom: 'ti-enter-from',
|
|
162
|
-
enterTo: 'ti-enter-to',
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
show.set(true)
|
|
166
|
-
|
|
167
|
-
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
168
|
-
expect(el.classList.contains('ti-enter-from')).toBe(true)
|
|
169
|
-
expect(el.classList.contains('ti-enter-to')).toBe(false)
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('swaps enterFrom to enterTo after double rAF', () => {
|
|
173
|
-
const show = signal(false)
|
|
174
|
-
const { el } = setupTransitionItem({
|
|
175
|
-
show: () => show(),
|
|
176
|
-
enter: 'ti-enter',
|
|
177
|
-
enterFrom: 'ti-enter-from',
|
|
178
|
-
enterTo: 'ti-enter-to',
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
show.set(true)
|
|
182
|
-
|
|
183
|
-
flushRaf()
|
|
184
|
-
flushRaf()
|
|
185
|
-
|
|
186
|
-
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
187
|
-
expect(el.classList.contains('ti-enter-from')).toBe(false)
|
|
188
|
-
expect(el.classList.contains('ti-enter-to')).toBe(true)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('applies leave classes when leaving', () => {
|
|
192
|
-
const show = signal(true)
|
|
193
|
-
const { el } = setupTransitionItem({
|
|
194
|
-
show: () => show(),
|
|
195
|
-
leave: 'ti-leave',
|
|
196
|
-
leaveFrom: 'ti-leave-from',
|
|
197
|
-
leaveTo: 'ti-leave-to',
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
show.set(false)
|
|
201
|
-
|
|
202
|
-
expect(el.classList.contains('ti-leave')).toBe(true)
|
|
203
|
-
expect(el.classList.contains('ti-leave-from')).toBe(true)
|
|
204
|
-
|
|
205
|
-
flushRaf()
|
|
206
|
-
flushRaf()
|
|
207
|
-
|
|
208
|
-
expect(el.classList.contains('ti-leave-from')).toBe(false)
|
|
209
|
-
expect(el.classList.contains('ti-leave-to')).toBe(true)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('applies enter style transitions', () => {
|
|
213
|
-
const show = signal(false)
|
|
214
|
-
const { el } = setupTransitionItem({
|
|
215
|
-
show: () => show(),
|
|
216
|
-
enterStyle: { opacity: 0 },
|
|
217
|
-
enterToStyle: { opacity: 1 },
|
|
218
|
-
enterTransition: 'opacity 300ms ease',
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
show.set(true)
|
|
222
|
-
|
|
223
|
-
expect(el.style.opacity).toBe('0')
|
|
224
|
-
expect(el.style.transition).toBe('opacity 300ms ease')
|
|
225
|
-
|
|
226
|
-
flushRaf()
|
|
227
|
-
flushRaf()
|
|
228
|
-
|
|
229
|
-
expect(el.style.opacity).toBe('1')
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('applies leave style transitions', () => {
|
|
233
|
-
const show = signal(true)
|
|
234
|
-
const { el } = setupTransitionItem({
|
|
235
|
-
show: () => show(),
|
|
236
|
-
leaveStyle: { opacity: 1 },
|
|
237
|
-
leaveToStyle: { opacity: 0 },
|
|
238
|
-
leaveTransition: 'opacity 200ms ease-in',
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
show.set(false)
|
|
242
|
-
|
|
243
|
-
expect(el.style.opacity).toBe('1')
|
|
244
|
-
expect(el.style.transition).toBe('opacity 200ms ease-in')
|
|
245
|
-
|
|
246
|
-
flushRaf()
|
|
247
|
-
flushRaf()
|
|
248
|
-
|
|
249
|
-
expect(el.style.opacity).toBe('0')
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
it('fires onAfterEnter after transitionend', () => {
|
|
253
|
-
const show = signal(false)
|
|
254
|
-
const onAfterEnter = vi.fn()
|
|
255
|
-
|
|
256
|
-
const { el } = setupTransitionItem({ show: () => show(), onAfterEnter })
|
|
257
|
-
|
|
258
|
-
show.set(true)
|
|
259
|
-
expect(onAfterEnter).not.toHaveBeenCalled()
|
|
260
|
-
|
|
261
|
-
flushRaf()
|
|
262
|
-
flushRaf()
|
|
263
|
-
fireTransitionEnd(el)
|
|
264
|
-
|
|
265
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
it('fires onAfterLeave after transitionend', () => {
|
|
269
|
-
const show = signal(true)
|
|
270
|
-
const onAfterLeave = vi.fn()
|
|
271
|
-
|
|
272
|
-
const { el } = setupTransitionItem({ show: () => show(), onAfterLeave })
|
|
273
|
-
|
|
274
|
-
show.set(false)
|
|
275
|
-
expect(onAfterLeave).not.toHaveBeenCalled()
|
|
276
|
-
|
|
277
|
-
flushRaf()
|
|
278
|
-
flushRaf()
|
|
279
|
-
fireTransitionEnd(el)
|
|
280
|
-
|
|
281
|
-
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
it('cleans up enter classes after transitionend', () => {
|
|
285
|
-
const show = signal(false)
|
|
286
|
-
const { el } = setupTransitionItem({
|
|
287
|
-
show: () => show(),
|
|
288
|
-
enter: 'ti-enter',
|
|
289
|
-
enterFrom: 'ti-enter-from',
|
|
290
|
-
enterTo: 'ti-enter-to',
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
show.set(true)
|
|
294
|
-
flushRaf()
|
|
295
|
-
flushRaf()
|
|
296
|
-
fireTransitionEnd(el)
|
|
297
|
-
|
|
298
|
-
// enter class should be removed on entered stage
|
|
299
|
-
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
it('cleans up transition style on entered stage', () => {
|
|
303
|
-
const show = signal(false)
|
|
304
|
-
const { el } = setupTransitionItem({
|
|
305
|
-
show: () => show(),
|
|
306
|
-
enter: 'ti-enter',
|
|
307
|
-
enterTransition: 'opacity 300ms ease',
|
|
308
|
-
enterStyle: { opacity: 0 },
|
|
309
|
-
enterToStyle: { opacity: 1 },
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
show.set(true)
|
|
313
|
-
expect(el.style.transition).toBe('opacity 300ms ease')
|
|
314
|
-
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
315
|
-
|
|
316
|
-
flushRaf()
|
|
317
|
-
flushRaf()
|
|
318
|
-
fireTransitionEnd(el)
|
|
319
|
-
|
|
320
|
-
expect(el.style.transition).toBe('')
|
|
321
|
-
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
it('appear=true fires onEnter on initial mount', () => {
|
|
325
|
-
const show = signal(true)
|
|
326
|
-
const onEnter = vi.fn()
|
|
327
|
-
|
|
328
|
-
setupTransitionItem({ show: () => show(), appear: true, onEnter })
|
|
329
|
-
|
|
330
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
it('appear=false does not fire onEnter on initial mount when show is true', () => {
|
|
334
|
-
const show = signal(true)
|
|
335
|
-
const onEnter = vi.fn()
|
|
336
|
-
|
|
337
|
-
setupTransitionItem({ show: () => show(), appear: false, onEnter })
|
|
338
|
-
|
|
339
|
-
expect(onEnter).not.toHaveBeenCalled()
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
it('timeout fallback completes transition when transitionend never fires', () => {
|
|
343
|
-
const show = signal(false)
|
|
344
|
-
const onAfterEnter = vi.fn()
|
|
345
|
-
|
|
346
|
-
setupTransitionItem({ show: () => show(), timeout: 1000, onAfterEnter })
|
|
347
|
-
|
|
348
|
-
show.set(true)
|
|
349
|
-
expect(onAfterEnter).not.toHaveBeenCalled()
|
|
350
|
-
|
|
351
|
-
vi.advanceTimersByTime(1000)
|
|
352
|
-
|
|
353
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
it('unmount=false keeps element with display:none when hidden', () => {
|
|
357
|
-
const show = () => false
|
|
358
|
-
const child = h('div', null, 'Hello') as VNode
|
|
359
|
-
const vnode = TransitionItem({ show, unmount: false, children: child })
|
|
360
|
-
|
|
361
|
-
expect(vnode).not.toBeNull()
|
|
362
|
-
// With unmount=false, the fallback should contain a cloned VNode with display:none
|
|
363
|
-
const showProps = vnode?.props as Record<string, unknown>
|
|
364
|
-
if (showProps?.fallback && typeof showProps.fallback === 'object') {
|
|
365
|
-
const fallbackNode = showProps.fallback as VNode
|
|
366
|
-
const fallbackProps = fallbackNode.props as Record<string, unknown>
|
|
367
|
-
const style = fallbackProps?.style as Record<string, unknown> | undefined
|
|
368
|
-
expect(style?.display).toBe('none')
|
|
369
|
-
}
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
it('unmount=false fallback has a merged ref', () => {
|
|
373
|
-
const show = () => false
|
|
374
|
-
const child = h('div', null, 'Hello') as VNode
|
|
375
|
-
const vnode = TransitionItem({ show, unmount: false, children: child })
|
|
376
|
-
|
|
377
|
-
const showProps = vnode?.props as Record<string, unknown>
|
|
378
|
-
if (showProps?.fallback && typeof showProps.fallback === 'object') {
|
|
379
|
-
const fallbackNode = showProps.fallback as VNode
|
|
380
|
-
const fallbackProps = fallbackNode.props as Record<string, unknown>
|
|
381
|
-
expect(fallbackProps?.ref).toBeDefined()
|
|
382
|
-
expect(typeof fallbackProps?.ref).toBe('function')
|
|
383
|
-
}
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
it('unmount=false merges existing child style with display:none', () => {
|
|
387
|
-
const show = () => false
|
|
388
|
-
const child = h('div', { style: { color: 'red', opacity: 1 } }, 'Hello') as VNode
|
|
389
|
-
const vnode = TransitionItem({ show, unmount: false, children: child })
|
|
390
|
-
|
|
391
|
-
const showProps = vnode?.props as Record<string, unknown>
|
|
392
|
-
if (showProps?.fallback && typeof showProps.fallback === 'object') {
|
|
393
|
-
const fallbackNode = showProps.fallback as VNode
|
|
394
|
-
const fallbackProps = fallbackNode.props as Record<string, unknown>
|
|
395
|
-
const style = fallbackProps?.style as Record<string, unknown> | undefined
|
|
396
|
-
expect(style?.color).toBe('red')
|
|
397
|
-
expect(style?.opacity).toBe(1)
|
|
398
|
-
expect(style?.display).toBe('none')
|
|
399
|
-
}
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
it('appear=true applies enter classes on initial mount when show is true', () => {
|
|
403
|
-
const show = signal(true)
|
|
404
|
-
const { el } = setupTransitionItem({
|
|
405
|
-
show: () => show(),
|
|
406
|
-
appear: true,
|
|
407
|
-
enter: 'ti-enter',
|
|
408
|
-
enterFrom: 'ti-enter-from',
|
|
409
|
-
enterTo: 'ti-enter-to',
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
// After appear, entering classes should be applied
|
|
413
|
-
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
414
|
-
expect(el.classList.contains('ti-enter-from')).toBe(true)
|
|
415
|
-
|
|
416
|
-
flushRaf()
|
|
417
|
-
flushRaf()
|
|
418
|
-
|
|
419
|
-
expect(el.classList.contains('ti-enter-from')).toBe(false)
|
|
420
|
-
expect(el.classList.contains('ti-enter-to')).toBe(true)
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
it('appear=true completes full enter lifecycle', () => {
|
|
424
|
-
const show = signal(true)
|
|
425
|
-
const onEnter = vi.fn()
|
|
426
|
-
const onAfterEnter = vi.fn()
|
|
427
|
-
|
|
428
|
-
const { el } = setupTransitionItem({
|
|
429
|
-
show: () => show(),
|
|
430
|
-
appear: true,
|
|
431
|
-
onEnter,
|
|
432
|
-
onAfterEnter,
|
|
433
|
-
enter: 'ti-enter',
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
437
|
-
|
|
438
|
-
flushRaf()
|
|
439
|
-
flushRaf()
|
|
440
|
-
fireTransitionEnd(el)
|
|
441
|
-
|
|
442
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
443
|
-
// After entered stage, enter class should be cleaned up
|
|
444
|
-
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
445
|
-
expect(el.style.transition).toBe('')
|
|
446
|
-
})
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
describe('TransitionItem — reduced motion', () => {
|
|
450
|
-
beforeEach(() => {
|
|
451
|
-
vi.useFakeTimers()
|
|
452
|
-
rafCallbacks = []
|
|
453
|
-
_reducedMotion = true
|
|
454
|
-
|
|
455
|
-
vi.stubGlobal(
|
|
456
|
-
'requestAnimationFrame',
|
|
457
|
-
vi.fn((cb: () => void) => {
|
|
458
|
-
rafCallbacks.push(cb)
|
|
459
|
-
return rafCallbacks.length
|
|
460
|
-
}),
|
|
461
|
-
)
|
|
462
|
-
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
afterEach(() => {
|
|
466
|
-
vi.useRealTimers()
|
|
467
|
-
vi.stubGlobal('requestAnimationFrame', originalRaf)
|
|
468
|
-
vi.stubGlobal('cancelAnimationFrame', originalCaf)
|
|
469
|
-
_reducedMotion = false
|
|
470
|
-
})
|
|
471
|
-
|
|
472
|
-
it('reduced motion: entering fires onEnter and onAfterEnter immediately', () => {
|
|
473
|
-
const show = signal(false)
|
|
474
|
-
const onEnter = vi.fn()
|
|
475
|
-
const onAfterEnter = vi.fn()
|
|
476
|
-
|
|
477
|
-
setupTransitionItem({ show: () => show(), onEnter, onAfterEnter })
|
|
478
|
-
|
|
479
|
-
show.set(true)
|
|
480
|
-
|
|
481
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
482
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
483
|
-
})
|
|
484
|
-
|
|
485
|
-
it('reduced motion: leaving fires onLeave and onAfterLeave immediately', () => {
|
|
486
|
-
const show = signal(true)
|
|
487
|
-
const onLeave = vi.fn()
|
|
488
|
-
const onAfterLeave = vi.fn()
|
|
489
|
-
|
|
490
|
-
setupTransitionItem({ show: () => show(), onLeave, onAfterLeave })
|
|
491
|
-
|
|
492
|
-
show.set(false)
|
|
493
|
-
|
|
494
|
-
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
495
|
-
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
496
|
-
})
|
|
497
|
-
|
|
498
|
-
it('reduced motion: does not apply CSS classes or rAF', () => {
|
|
499
|
-
const show = signal(false)
|
|
500
|
-
const { el } = setupTransitionItem({
|
|
501
|
-
show: () => show(),
|
|
502
|
-
enter: 'ti-enter',
|
|
503
|
-
enterFrom: 'ti-enter-from',
|
|
504
|
-
enterTo: 'ti-enter-to',
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
show.set(true)
|
|
508
|
-
|
|
509
|
-
// No classes should be applied — reduced motion skips CSS transitions
|
|
510
|
-
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
511
|
-
expect(el.classList.contains('ti-enter-from')).toBe(false)
|
|
512
|
-
expect(rafCallbacks.length).toBe(0)
|
|
513
|
-
})
|
|
514
|
-
})
|