@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,565 +0,0 @@
|
|
|
1
|
-
import type { VNode, VNodeChild } 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 { kinetic } from '../index'
|
|
12
|
-
import { fade, slideUp } from '../presets'
|
|
13
|
-
|
|
14
|
-
// Mock rAF for deterministic testing
|
|
15
|
-
let rafCallbacks: (() => void)[] = []
|
|
16
|
-
const originalRaf = globalThis.requestAnimationFrame
|
|
17
|
-
const originalCaf = globalThis.cancelAnimationFrame
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
vi.useFakeTimers()
|
|
21
|
-
rafCallbacks = []
|
|
22
|
-
|
|
23
|
-
vi.stubGlobal(
|
|
24
|
-
'requestAnimationFrame',
|
|
25
|
-
vi.fn((cb: () => void) => {
|
|
26
|
-
rafCallbacks.push(cb)
|
|
27
|
-
return rafCallbacks.length
|
|
28
|
-
}),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
vi.useRealTimers()
|
|
36
|
-
vi.stubGlobal('requestAnimationFrame', originalRaf)
|
|
37
|
-
vi.stubGlobal('cancelAnimationFrame', originalCaf)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const flushRaf = () => {
|
|
41
|
-
const cbs = [...rafCallbacks]
|
|
42
|
-
rafCallbacks = []
|
|
43
|
-
for (const cb of cbs) cb()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const fireTransitionEnd = (el: HTMLElement) => {
|
|
47
|
-
const event = new Event('transitionend', { bubbles: true })
|
|
48
|
-
Object.defineProperty(event, 'target', { value: el })
|
|
49
|
-
el.dispatchEvent(event)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Wire up a mock element to the ref found in the VNode tree.
|
|
54
|
-
* The kinetic transition mode uses h(tag, { ref: mergedRef, ... })
|
|
55
|
-
* which means the ref is directly on the VNode props.
|
|
56
|
-
*/
|
|
57
|
-
const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
58
|
-
if (!vnode) return
|
|
59
|
-
|
|
60
|
-
const visitNode = (node: VNode) => {
|
|
61
|
-
const props = node.props as Record<string, unknown>
|
|
62
|
-
if (typeof props?.ref === 'function') {
|
|
63
|
-
;(props.ref as (element: HTMLElement | null) => void)(el)
|
|
64
|
-
} else if (props?.ref && typeof props.ref === 'object') {
|
|
65
|
-
;(props.ref as { current: HTMLElement | null }).current = el
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Visit vnode.children (for intrinsic elements)
|
|
69
|
-
if (node.children) {
|
|
70
|
-
const children = Array.isArray(node.children) ? node.children : [node.children]
|
|
71
|
-
for (const child of children) {
|
|
72
|
-
if (child && typeof child === 'object' && 'type' in (child as object)) {
|
|
73
|
-
visitNode(child as VNode)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Visit props.children (for component VNodes like Show)
|
|
79
|
-
if (props?.children) {
|
|
80
|
-
const pChildren = Array.isArray(props.children) ? props.children : [props.children]
|
|
81
|
-
for (const child of pChildren) {
|
|
82
|
-
if (child && typeof child === 'object' && 'type' in (child as object)) {
|
|
83
|
-
visitNode(child as VNode)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Visit fallback on Show
|
|
89
|
-
if (
|
|
90
|
-
props?.fallback &&
|
|
91
|
-
typeof props.fallback === 'object' &&
|
|
92
|
-
'type' in (props.fallback as object)
|
|
93
|
-
) {
|
|
94
|
-
visitNode(props.fallback as VNode)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
visitNode(vnode)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Resolve the outermost component-type VNode by calling the function.
|
|
103
|
-
* kinetic() returns VNodes where type is a component function (e.g. TransitionRenderer).
|
|
104
|
-
* We call it once to execute the component and set up watches/refs,
|
|
105
|
-
* but do NOT recurse into framework components like Show.
|
|
106
|
-
*/
|
|
107
|
-
const resolveComponent = (vnode: VNodeChild): VNode | null => {
|
|
108
|
-
if (!vnode || typeof vnode !== 'object' || !('type' in vnode)) return null
|
|
109
|
-
if (typeof vnode.type === 'function') {
|
|
110
|
-
const props = vnode.props as Record<string, unknown>
|
|
111
|
-
const children = vnode.children
|
|
112
|
-
// Call the component function with props (children merged in)
|
|
113
|
-
const result = (vnode.type as (p: Record<string, unknown>) => VNodeChild)({
|
|
114
|
-
...props,
|
|
115
|
-
...(children != null ? { children } : {}),
|
|
116
|
-
})
|
|
117
|
-
if (!result || typeof result !== 'object' || !('type' in result)) return null
|
|
118
|
-
return result
|
|
119
|
-
}
|
|
120
|
-
return vnode
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ─── Transition Mode (default) ────────────────────────────
|
|
124
|
-
|
|
125
|
-
describe('kinetic() — transition mode', () => {
|
|
126
|
-
it('returns a VNode when show=true', () => {
|
|
127
|
-
const FadeDiv = kinetic('div').preset(fade)
|
|
128
|
-
const show = signal(true)
|
|
129
|
-
const vnode = FadeDiv({ show, children: 'Hello' })
|
|
130
|
-
expect(vnode).not.toBeNull()
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('fires onEnter callback when entering', () => {
|
|
134
|
-
const onEnter = vi.fn()
|
|
135
|
-
const show = signal(false)
|
|
136
|
-
const Slide = kinetic('div')
|
|
137
|
-
.enter({ opacity: 0, transform: 'translateY(16px)' })
|
|
138
|
-
.enterTo({ opacity: 1, transform: 'translateY(0)' })
|
|
139
|
-
.enterTransition('all 300ms ease')
|
|
140
|
-
|
|
141
|
-
const el = document.createElement('div')
|
|
142
|
-
const vnode = resolveComponent(Slide({ show, onEnter, children: 'Hello' }))
|
|
143
|
-
wireRef(vnode, el)
|
|
144
|
-
|
|
145
|
-
show.set(true)
|
|
146
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it('applies enterStyle on entering', () => {
|
|
150
|
-
const show = signal(false)
|
|
151
|
-
const Slide = kinetic('div')
|
|
152
|
-
.enter({ opacity: 0, transform: 'translateY(16px)' })
|
|
153
|
-
.enterTo({ opacity: 1, transform: 'translateY(0)' })
|
|
154
|
-
.enterTransition('all 300ms ease')
|
|
155
|
-
|
|
156
|
-
const el = document.createElement('div')
|
|
157
|
-
const vnode = resolveComponent(Slide({ show, children: 'Hello' }))
|
|
158
|
-
wireRef(vnode, el)
|
|
159
|
-
|
|
160
|
-
show.set(true)
|
|
161
|
-
|
|
162
|
-
expect(el.style.opacity).toBe('0')
|
|
163
|
-
expect(el.style.transition).toBe('all 300ms ease')
|
|
164
|
-
|
|
165
|
-
flushRaf()
|
|
166
|
-
flushRaf()
|
|
167
|
-
|
|
168
|
-
expect(el.style.opacity).toBe('1')
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('applies class-based transitions via .enterClass()', () => {
|
|
172
|
-
const show = signal(false)
|
|
173
|
-
const ClassFade = kinetic('div')
|
|
174
|
-
.enterClass({ active: 't-enter', from: 't-from', to: 't-to' })
|
|
175
|
-
.leaveClass({ active: 't-leave', from: 't-lfrom', to: 't-lto' })
|
|
176
|
-
|
|
177
|
-
const el = document.createElement('div')
|
|
178
|
-
const vnode = resolveComponent(ClassFade({ show, children: 'Hello' }))
|
|
179
|
-
wireRef(vnode, el)
|
|
180
|
-
|
|
181
|
-
show.set(true)
|
|
182
|
-
|
|
183
|
-
expect(el.classList.contains('t-enter')).toBe(true)
|
|
184
|
-
expect(el.classList.contains('t-from')).toBe(true)
|
|
185
|
-
|
|
186
|
-
flushRaf()
|
|
187
|
-
flushRaf()
|
|
188
|
-
|
|
189
|
-
expect(el.classList.contains('t-from')).toBe(false)
|
|
190
|
-
expect(el.classList.contains('t-to')).toBe(true)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('fires lifecycle callbacks at correct times', () => {
|
|
194
|
-
const onEnter = vi.fn()
|
|
195
|
-
const onAfterEnter = vi.fn()
|
|
196
|
-
const onLeave = vi.fn()
|
|
197
|
-
const onAfterLeave = vi.fn()
|
|
198
|
-
const show = signal(false)
|
|
199
|
-
|
|
200
|
-
const FadeDiv = kinetic('div').preset(fade)
|
|
201
|
-
const el = document.createElement('div')
|
|
202
|
-
const vnode = resolveComponent(
|
|
203
|
-
FadeDiv({ show, onEnter, onAfterEnter, onLeave, onAfterLeave, children: 'Hello' }),
|
|
204
|
-
)
|
|
205
|
-
wireRef(vnode, el)
|
|
206
|
-
|
|
207
|
-
// Enter
|
|
208
|
-
show.set(true)
|
|
209
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
210
|
-
expect(onAfterEnter).not.toHaveBeenCalled()
|
|
211
|
-
|
|
212
|
-
flushRaf()
|
|
213
|
-
flushRaf()
|
|
214
|
-
fireTransitionEnd(el)
|
|
215
|
-
|
|
216
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
217
|
-
|
|
218
|
-
// Leave
|
|
219
|
-
show.set(false)
|
|
220
|
-
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
221
|
-
|
|
222
|
-
flushRaf()
|
|
223
|
-
flushRaf()
|
|
224
|
-
fireTransitionEnd(el)
|
|
225
|
-
|
|
226
|
-
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
it('timeout fallback completes transition', () => {
|
|
230
|
-
const onAfterEnter = vi.fn()
|
|
231
|
-
const show = signal(false)
|
|
232
|
-
const FadeDiv = kinetic('div').preset(fade).config({ timeout: 1000 })
|
|
233
|
-
|
|
234
|
-
const el = document.createElement('div')
|
|
235
|
-
const vnode = resolveComponent(FadeDiv({ show, onAfterEnter, children: 'Hello' }))
|
|
236
|
-
wireRef(vnode, el)
|
|
237
|
-
|
|
238
|
-
show.set(true)
|
|
239
|
-
|
|
240
|
-
flushRaf()
|
|
241
|
-
flushRaf()
|
|
242
|
-
|
|
243
|
-
vi.advanceTimersByTime(1000)
|
|
244
|
-
|
|
245
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
it('appear=true animates on initial mount', () => {
|
|
249
|
-
const onEnter = vi.fn()
|
|
250
|
-
const show = signal(true)
|
|
251
|
-
const FadeDiv = kinetic('div').preset(fade).config({ appear: true }).on({ onEnter })
|
|
252
|
-
|
|
253
|
-
const el = document.createElement('div')
|
|
254
|
-
const vnode = resolveComponent(FadeDiv({ show, children: 'Hello' }))
|
|
255
|
-
wireRef(vnode, el)
|
|
256
|
-
|
|
257
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
// ─── Chain Immutability ────────────────────────────────────
|
|
262
|
-
|
|
263
|
-
describe('kinetic() — chaining', () => {
|
|
264
|
-
it('chain is immutable (each method returns new component)', () => {
|
|
265
|
-
const Base = kinetic('div')
|
|
266
|
-
const WithFade = Base.preset(fade)
|
|
267
|
-
const WithSlide = Base.preset(slideUp)
|
|
268
|
-
|
|
269
|
-
// WithFade and WithSlide are different components
|
|
270
|
-
expect(WithFade).not.toBe(WithSlide)
|
|
271
|
-
expect(WithFade).not.toBe(Base)
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
it('.preset() merges preset properties into config', () => {
|
|
275
|
-
const show = signal(false)
|
|
276
|
-
const FadeDiv = kinetic('div').preset(fade)
|
|
277
|
-
|
|
278
|
-
const el = document.createElement('div')
|
|
279
|
-
const vnode = resolveComponent(FadeDiv({ show, children: 'Hello' }))
|
|
280
|
-
wireRef(vnode, el)
|
|
281
|
-
|
|
282
|
-
show.set(true)
|
|
283
|
-
|
|
284
|
-
expect(el.style.opacity).toBe('0')
|
|
285
|
-
expect(el.style.transition).toBe('opacity 300ms ease-out')
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
it('.on() callbacks from chain are used when runtime callbacks not provided', () => {
|
|
289
|
-
const onEnter = vi.fn()
|
|
290
|
-
const show = signal(false)
|
|
291
|
-
const FadeDiv = kinetic('div').preset(fade).on({ onEnter })
|
|
292
|
-
|
|
293
|
-
const el = document.createElement('div')
|
|
294
|
-
const vnode = resolveComponent(FadeDiv({ show, children: 'Hello' }))
|
|
295
|
-
wireRef(vnode, el)
|
|
296
|
-
|
|
297
|
-
show.set(true)
|
|
298
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
it('runtime props override chain config', () => {
|
|
302
|
-
const chainOnEnter = vi.fn()
|
|
303
|
-
const runtimeOnEnter = vi.fn()
|
|
304
|
-
const show = signal(false)
|
|
305
|
-
const FadeDiv = kinetic('div').preset(fade).on({ onEnter: chainOnEnter })
|
|
306
|
-
|
|
307
|
-
const el = document.createElement('div')
|
|
308
|
-
const vnode = resolveComponent(FadeDiv({ show, onEnter: runtimeOnEnter, children: 'Hello' }))
|
|
309
|
-
wireRef(vnode, el)
|
|
310
|
-
|
|
311
|
-
show.set(true)
|
|
312
|
-
|
|
313
|
-
expect(runtimeOnEnter).toHaveBeenCalledTimes(1)
|
|
314
|
-
expect(chainOnEnter).not.toHaveBeenCalled()
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
it('displayName is set correctly', () => {
|
|
318
|
-
const FadeDiv = kinetic('div').preset(fade)
|
|
319
|
-
expect(FadeDiv.displayName).toBe('kinetic(div)')
|
|
320
|
-
})
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
// ─── Collapse Mode ─────────────────────────────────────────
|
|
324
|
-
|
|
325
|
-
describe('kinetic() — collapse mode', () => {
|
|
326
|
-
const mockScrollHeight = (value: number) => {
|
|
327
|
-
Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
|
|
328
|
-
configurable: true,
|
|
329
|
-
get() {
|
|
330
|
-
return value
|
|
331
|
-
},
|
|
332
|
-
})
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
beforeEach(() => {
|
|
336
|
-
mockScrollHeight(200)
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
it('fires onEnter on entering and onAfterEnter after transitionend', () => {
|
|
340
|
-
const onEnter = vi.fn()
|
|
341
|
-
const onAfterEnter = vi.fn()
|
|
342
|
-
const show = signal(false)
|
|
343
|
-
const Accordion = kinetic('div').collapse()
|
|
344
|
-
|
|
345
|
-
const wrapperEl = document.createElement('div')
|
|
346
|
-
const contentEl = document.createElement('div')
|
|
347
|
-
Object.defineProperty(wrapperEl, 'offsetHeight', {
|
|
348
|
-
configurable: true,
|
|
349
|
-
get: () => 0,
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
const vnode = resolveComponent(
|
|
353
|
-
Accordion({
|
|
354
|
-
show,
|
|
355
|
-
onEnter,
|
|
356
|
-
onAfterEnter,
|
|
357
|
-
// Real h() instead of a mock literal — same VNode shape as
|
|
358
|
-
// production, so the test exercises whatever flattening /
|
|
359
|
-
// normalisation h() applies (instead of asserting the
|
|
360
|
-
// hand-built shape always matches).
|
|
361
|
-
children: h('p', null, 'Content'),
|
|
362
|
-
}),
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
// Wire up refs - CollapseRenderer uses h(config.tag, { ref: wrapperRef, ... })
|
|
366
|
-
// and inner <div ref={contentRef}>
|
|
367
|
-
wireRef(vnode, wrapperEl)
|
|
368
|
-
|
|
369
|
-
// Find contentRef in the VNode tree
|
|
370
|
-
const findContentRef = (node: VNode | null) => {
|
|
371
|
-
if (!node) return
|
|
372
|
-
if (node.children) {
|
|
373
|
-
const children = Array.isArray(node.children) ? node.children : [node.children]
|
|
374
|
-
for (const child of children) {
|
|
375
|
-
if (child && typeof child === 'object' && 'type' in (child as object)) {
|
|
376
|
-
const cNode = child as VNode
|
|
377
|
-
const props = cNode.props as Record<string, unknown>
|
|
378
|
-
if (props?.ref && typeof props.ref === 'object' && cNode.type === 'div') {
|
|
379
|
-
;(props.ref as { current: HTMLElement | null }).current = contentEl
|
|
380
|
-
}
|
|
381
|
-
findContentRef(cNode)
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
// Check Show props
|
|
386
|
-
const props = node.props as Record<string, unknown>
|
|
387
|
-
if (props?.children) {
|
|
388
|
-
const pc = Array.isArray(props.children) ? props.children : [props.children]
|
|
389
|
-
for (const p of pc) {
|
|
390
|
-
if (p && typeof p === 'object' && 'type' in (p as object)) {
|
|
391
|
-
const pNode = p as VNode
|
|
392
|
-
const pProps = pNode.props as Record<string, unknown>
|
|
393
|
-
if (pProps?.ref && typeof pProps.ref === 'object') {
|
|
394
|
-
;(pProps.ref as { current: HTMLElement | null }).current = contentEl
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
findContentRef(vnode)
|
|
401
|
-
|
|
402
|
-
show.set(true)
|
|
403
|
-
|
|
404
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
405
|
-
|
|
406
|
-
fireTransitionEnd(wrapperEl)
|
|
407
|
-
|
|
408
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
409
|
-
})
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
// ─── Transition mode — leave with styles ─────────────────────
|
|
413
|
-
|
|
414
|
-
describe('kinetic() — transition leave styles', () => {
|
|
415
|
-
it('applies leaveStyle and leaveTransition on leaving', () => {
|
|
416
|
-
const show = signal(true)
|
|
417
|
-
const Slide = kinetic('div')
|
|
418
|
-
.enter({ opacity: 0 })
|
|
419
|
-
.enterTo({ opacity: 1 })
|
|
420
|
-
.enterTransition('opacity 300ms ease')
|
|
421
|
-
.leave({ opacity: 1 })
|
|
422
|
-
.leaveTo({ opacity: 0 })
|
|
423
|
-
.leaveTransition('opacity 200ms ease-in')
|
|
424
|
-
|
|
425
|
-
const el = document.createElement('div')
|
|
426
|
-
const vnode = resolveComponent(Slide({ show, children: 'Hello' }))
|
|
427
|
-
wireRef(vnode, el)
|
|
428
|
-
|
|
429
|
-
show.set(false)
|
|
430
|
-
|
|
431
|
-
expect(el.style.opacity).toBe('1')
|
|
432
|
-
expect(el.style.transition).toBe('opacity 200ms ease-in')
|
|
433
|
-
|
|
434
|
-
flushRaf()
|
|
435
|
-
flushRaf()
|
|
436
|
-
|
|
437
|
-
expect(el.style.opacity).toBe('0')
|
|
438
|
-
})
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
// ─── Config defaults ──────────────────────────────────────
|
|
442
|
-
|
|
443
|
-
describe('kinetic() — config defaults and overrides', () => {
|
|
444
|
-
it('appear from config is used when runtime appear not provided', () => {
|
|
445
|
-
const onEnter = vi.fn()
|
|
446
|
-
const show = signal(true)
|
|
447
|
-
const FadeDiv = kinetic('div').preset(fade).config({ appear: true })
|
|
448
|
-
|
|
449
|
-
const el = document.createElement('div')
|
|
450
|
-
const vnode = resolveComponent(FadeDiv({ show, onEnter, children: 'Hello' }))
|
|
451
|
-
wireRef(vnode, el)
|
|
452
|
-
|
|
453
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
it('runtime appear overrides config appear', () => {
|
|
457
|
-
const onEnter = vi.fn()
|
|
458
|
-
const show = signal(true)
|
|
459
|
-
const FadeDiv = kinetic('div').preset(fade).config({ appear: true })
|
|
460
|
-
|
|
461
|
-
const el = document.createElement('div')
|
|
462
|
-
const vnode = resolveComponent(FadeDiv({ show, appear: false, onEnter, children: 'Hello' }))
|
|
463
|
-
wireRef(vnode, el)
|
|
464
|
-
|
|
465
|
-
expect(onEnter).not.toHaveBeenCalled()
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
it('timeout from config is used as fallback', () => {
|
|
469
|
-
const onAfterEnter = vi.fn()
|
|
470
|
-
const show = signal(false)
|
|
471
|
-
const FadeDiv = kinetic('div').preset(fade).config({ timeout: 200 })
|
|
472
|
-
|
|
473
|
-
const el = document.createElement('div')
|
|
474
|
-
const vnode = resolveComponent(FadeDiv({ show, onAfterEnter, children: 'Hello' }))
|
|
475
|
-
wireRef(vnode, el)
|
|
476
|
-
|
|
477
|
-
show.set(true)
|
|
478
|
-
|
|
479
|
-
flushRaf()
|
|
480
|
-
flushRaf()
|
|
481
|
-
|
|
482
|
-
// No transitionend fired — timeout from config (200ms) should fire
|
|
483
|
-
vi.advanceTimersByTime(200)
|
|
484
|
-
|
|
485
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
486
|
-
})
|
|
487
|
-
|
|
488
|
-
it('runtime timeout overrides config timeout', () => {
|
|
489
|
-
const onAfterEnter = vi.fn()
|
|
490
|
-
const show = signal(false)
|
|
491
|
-
const FadeDiv = kinetic('div').preset(fade).config({ timeout: 200 })
|
|
492
|
-
|
|
493
|
-
const el = document.createElement('div')
|
|
494
|
-
const vnode = resolveComponent(FadeDiv({ show, timeout: 500, onAfterEnter, children: 'Hello' }))
|
|
495
|
-
wireRef(vnode, el)
|
|
496
|
-
|
|
497
|
-
show.set(true)
|
|
498
|
-
|
|
499
|
-
flushRaf()
|
|
500
|
-
flushRaf()
|
|
501
|
-
|
|
502
|
-
// 200ms from config should NOT trigger since runtime is 500ms
|
|
503
|
-
vi.advanceTimersByTime(200)
|
|
504
|
-
expect(onAfterEnter).not.toHaveBeenCalled()
|
|
505
|
-
|
|
506
|
-
vi.advanceTimersByTime(300)
|
|
507
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
508
|
-
})
|
|
509
|
-
})
|
|
510
|
-
|
|
511
|
-
// ─── DisplayName ───────────────────────────────────────────
|
|
512
|
-
|
|
513
|
-
describe('kinetic() — displayName', () => {
|
|
514
|
-
it('uses tag string for displayName', () => {
|
|
515
|
-
const FadeDiv = kinetic('div').preset(fade)
|
|
516
|
-
expect(FadeDiv.displayName).toBe('kinetic(div)')
|
|
517
|
-
})
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
// ─── Reduced Motion ───────────────────────────────────────
|
|
521
|
-
|
|
522
|
-
describe('kinetic() — transition reduced motion', () => {
|
|
523
|
-
beforeEach(() => {
|
|
524
|
-
_reducedMotion = true
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
afterEach(() => {
|
|
528
|
-
_reducedMotion = false
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
it('reduced motion: entering fires onEnter and onAfterEnter immediately without rAF', () => {
|
|
532
|
-
const show = signal(false)
|
|
533
|
-
const onEnter = vi.fn()
|
|
534
|
-
const onAfterEnter = vi.fn()
|
|
535
|
-
const FadeDiv = kinetic('div').preset(fade)
|
|
536
|
-
|
|
537
|
-
const el = document.createElement('div')
|
|
538
|
-
const vnode = resolveComponent(FadeDiv({ show, onEnter, onAfterEnter, children: 'Hello' }))
|
|
539
|
-
wireRef(vnode, el)
|
|
540
|
-
|
|
541
|
-
show.set(true)
|
|
542
|
-
|
|
543
|
-
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
544
|
-
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
545
|
-
// No rAF should have been used
|
|
546
|
-
expect(rafCallbacks.length).toBe(0)
|
|
547
|
-
})
|
|
548
|
-
|
|
549
|
-
it('reduced motion: leaving fires onLeave and onAfterLeave immediately without rAF', () => {
|
|
550
|
-
const show = signal(true)
|
|
551
|
-
const onLeave = vi.fn()
|
|
552
|
-
const onAfterLeave = vi.fn()
|
|
553
|
-
const FadeDiv = kinetic('div').preset(fade)
|
|
554
|
-
|
|
555
|
-
const el = document.createElement('div')
|
|
556
|
-
const vnode = resolveComponent(FadeDiv({ show, onLeave, onAfterLeave, children: 'Hello' }))
|
|
557
|
-
wireRef(vnode, el)
|
|
558
|
-
|
|
559
|
-
show.set(false)
|
|
560
|
-
|
|
561
|
-
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
562
|
-
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
563
|
-
expect(rafCallbacks.length).toBe(0)
|
|
564
|
-
})
|
|
565
|
-
})
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { fade, presets, scaleIn, slideDown, slideLeft, slideRight, slideUp } from '../presets'
|
|
2
|
-
|
|
3
|
-
describe('presets', () => {
|
|
4
|
-
const allPresets = {
|
|
5
|
-
fade,
|
|
6
|
-
scaleIn,
|
|
7
|
-
slideUp,
|
|
8
|
-
slideDown,
|
|
9
|
-
slideLeft,
|
|
10
|
-
slideRight,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
it.each(Object.entries(allPresets))('%s has all required style properties', (_, preset) => {
|
|
14
|
-
expect(preset.enterStyle).toBeDefined()
|
|
15
|
-
expect(preset.enterToStyle).toBeDefined()
|
|
16
|
-
expect(preset.enterTransition).toBeDefined()
|
|
17
|
-
expect(preset.leaveStyle).toBeDefined()
|
|
18
|
-
expect(preset.leaveToStyle).toBeDefined()
|
|
19
|
-
expect(preset.leaveTransition).toBeDefined()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it.each(Object.entries(allPresets))('%s has non-empty transition strings', (_, preset) => {
|
|
23
|
-
expect(typeof preset.enterTransition).toBe('string')
|
|
24
|
-
expect((preset.enterTransition as string).length).toBeGreaterThan(0)
|
|
25
|
-
expect(typeof preset.leaveTransition).toBe('string')
|
|
26
|
-
expect((preset.leaveTransition as string).length).toBeGreaterThan(0)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('presets object contains all expected presets', () => {
|
|
30
|
-
expect(Object.keys(presets)).toEqual([
|
|
31
|
-
'fade',
|
|
32
|
-
'scaleIn',
|
|
33
|
-
'slideUp',
|
|
34
|
-
'slideDown',
|
|
35
|
-
'slideLeft',
|
|
36
|
-
'slideRight',
|
|
37
|
-
])
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('presets are plain objects (no side effects)', () => {
|
|
41
|
-
for (const preset of Object.values(presets)) {
|
|
42
|
-
expect(typeof preset).toBe('object')
|
|
43
|
-
expect(preset).not.toBeInstanceOf(Array)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
})
|