Skip to content

Commit 65711fe

Browse files
committed
test: 🔧 add test
1 parent 2b47e9a commit 65711fe

File tree

7 files changed

+303
-12
lines changed

7 files changed

+303
-12
lines changed

packages/hooks/src/useDebounceFn/__tests__/index.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,54 @@ describe('useDebounceFn', () => {
3535
await sleep(300)
3636
expect(count).toBe(7)
3737
})
38+
39+
it('should support leading option', async () => {
40+
let value = 0
41+
const fn = (v: number) => { value += v }
42+
const { run } = useDebounceFn(fn, { wait: 100, leading: true, trailing: false })
43+
run(1)
44+
expect(value).toBe(1)
45+
run(1)
46+
run(1)
47+
expect(value).toBe(1)
48+
await sleep(150)
49+
run(2)
50+
expect(value).toBe(3)
51+
})
52+
53+
it('should support trailing option', async () => {
54+
let value = 0
55+
const fn = (v: number) => { value += v }
56+
const { run } = useDebounceFn(fn, { wait: 100, leading: false, trailing: true })
57+
run(1)
58+
run(2)
59+
run(3)
60+
expect(value).toBe(0)
61+
await sleep(150)
62+
expect(value).toBe(3)
63+
})
64+
65+
it('should support maxWait option', async () => {
66+
let value = 0
67+
const fn = (v: number) => { value += v }
68+
const { run } = useDebounceFn(fn, { wait: 100, maxWait: 200 })
69+
run(1)
70+
setTimeout(() => run(2), 50)
71+
setTimeout(() => run(3), 120)
72+
await sleep(250)
73+
expect(value).toBe(3)
74+
})
75+
76+
it('should update options dynamically', async () => {
77+
let value = 0
78+
const fn = (v: number) => { value += v }
79+
const { run, updateOptions } = useDebounceFn(fn, { wait: 200 })
80+
run(1)
81+
await sleep(100)
82+
updateOptions({ wait: 50 })
83+
await sleep(0)
84+
run(2)
85+
await sleep(60)
86+
expect(value).toBe(2)
87+
})
3888
})

packages/hooks/src/useDrop/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const useDrop = (target: BasicTarget, options: UseDropOptions = {}) => {
9393
let data = dom
9494
try {
9595
data = JSON.parse(dom)
96+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
9697
} catch (e) {
9798
data = dom
9899
}
Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,115 @@
11
import renderHook from 'test-utils/renderHook'
22
import useElementBounding from '..'
3-
import { ref } from 'vue'
3+
import { ref, nextTick } from 'vue'
44
import { UseElementBoundingReturnType } from '../index'
55

6+
// mock getBoundingClientRect for predictable values
7+
function mockRect(left: number, top: number, width: number, height: number) {
8+
return {
9+
width,
10+
height,
11+
top,
12+
left,
13+
bottom: top + height,
14+
right: left + width,
15+
x: left,
16+
y: top,
17+
toJSON: () => ({ left, top, width, height, bottom: top + height, right: left + width, x: left, y: top }),
18+
}
19+
}
20+
621
describe('useElementBounding', () => {
7-
it('callback element info', () => {
22+
beforeEach(() => {
23+
window.HTMLElement.prototype.getBoundingClientRect = function () {
24+
return mockRect(20, 10, 100, 50)
25+
}
26+
})
27+
28+
it('should return all zeros when element is not mounted', () => {
829
const el = ref<HTMLElement>()
9-
const [callbackOptions] = renderHook(() => useElementBounding(el))
30+
const [bounding] = renderHook(() => useElementBounding(el))
31+
expect(bounding.width.value).toBe(0)
32+
expect(bounding.height.value).toBe(0)
33+
expect(bounding.top.value).toBe(0)
34+
expect(bounding.left.value).toBe(0)
35+
expect(bounding.bottom.value).toBe(0)
36+
expect(bounding.right.value).toBe(0)
37+
})
1038

39+
it('should return correct bounding values after element is mounted', async () => {
40+
const el = ref<HTMLElement>()
41+
const [bounding] = renderHook(() => useElementBounding(el))
42+
const div = document.createElement('div')
43+
el.value = div
44+
await nextTick()
45+
expect(bounding.width.value).toBe(100)
46+
expect(bounding.height.value).toBe(50)
47+
expect(bounding.top.value).toBe(10)
48+
expect(bounding.left.value).toBe(20)
49+
expect(bounding.bottom.value).toBe(60)
50+
expect(bounding.right.value).toBe(120)
51+
})
52+
53+
it('should update values when element size or position changes', async () => {
54+
let rect = mockRect(20, 10, 100, 50)
55+
window.HTMLElement.prototype.getBoundingClientRect = function () {
56+
return rect
57+
}
58+
const el = ref<HTMLElement>()
59+
const [bounding] = renderHook(() => useElementBounding(el))
60+
const div = document.createElement('div')
61+
el.value = div
62+
await nextTick()
63+
expect(bounding.width.value).toBe(100)
64+
rect = mockRect(15, 5, 200, 80)
65+
window.dispatchEvent(new Event('resize'))
66+
await nextTick()
67+
expect(bounding.width.value).toBe(200)
68+
expect(bounding.height.value).toBe(80)
69+
expect(bounding.top.value).toBe(5)
70+
expect(bounding.left.value).toBe(15)
71+
expect(bounding.bottom.value).toBe(85)
72+
expect(bounding.right.value).toBe(215)
73+
})
74+
75+
it('should not reset values to zero if reset option is false', async () => {
76+
// 保证 mockRect 是 100
77+
window.HTMLElement.prototype.getBoundingClientRect = function () {
78+
return mockRect(20, 10, 100, 50)
79+
}
80+
const el = ref<HTMLElement>()
81+
const [bounding] = renderHook(() => useElementBounding(el, { reset: false }))
82+
const div = document.createElement('div')
83+
el.value = div
84+
await nextTick()
85+
expect(bounding.width.value).toBe(100)
86+
el.value = undefined
87+
await nextTick()
88+
expect(bounding.width.value).toBe(100)
89+
})
90+
91+
it('should not update on window resize/scroll if corresponding option is false', async () => {
92+
let rect = mockRect(20, 10, 100, 50)
93+
window.HTMLElement.prototype.getBoundingClientRect = function () {
94+
return rect
95+
}
96+
const el = ref<HTMLElement>()
97+
const [bounding] = renderHook(() => useElementBounding(el, { windowResize: false, windowScroll: false }))
98+
const div = document.createElement('div')
99+
el.value = div
100+
await nextTick()
101+
expect(bounding.width.value).toBe(100)
102+
rect = mockRect(40, 30, 300, 150)
103+
window.dispatchEvent(new Event('resize'))
104+
window.dispatchEvent(new Event('scroll'))
105+
await nextTick()
106+
// Should not update
107+
expect(bounding.width.value).toBe(100)
108+
})
109+
110+
it('should have correct return type', () => {
111+
const el = ref<HTMLElement>()
112+
const [callbackOptions] = renderHook(() => useElementBounding(el))
11113
assertType<UseElementBoundingReturnType>(callbackOptions)
12114
})
13115
})

packages/hooks/src/useElementBounding/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,8 @@ export default function useElementBounding(
103103

104104
useResizeObserver(target, update)
105105
watch(() => getTargetElement(target), update)
106-
107106
onMounted(() => {
108-
immediate && update()
107+
if (immediate) update()
109108
})
110109

111110
return {

packages/hooks/src/useEventListener/__tests__/index.spec.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('useEventListener', () => {
1717
const onClick = () => {
1818
state++
1919
}
20-
renderHook(() => useEventListener('click', onClick, { target: () => container }))
20+
useEventListener('click', onClick, { target: () => container })
2121

2222
document.body.click()
2323
expect(state).toEqual(0)
@@ -26,4 +26,82 @@ describe('useEventListener', () => {
2626
document.body.click()
2727
expect(state).toEqual(1)
2828
})
29+
30+
it('should listen to window resize event', async () => {
31+
let called = false
32+
const onResize = () => {
33+
called = true
34+
}
35+
useEventListener('resize', onResize, { target: window })
36+
window.dispatchEvent(new Event('resize'))
37+
expect(called).toBe(true)
38+
})
39+
40+
it('should listen to document keydown event', async () => {
41+
let key = ''
42+
const onKeyDown = (e: KeyboardEvent) => {
43+
key = e.key
44+
}
45+
useEventListener('keydown', onKeyDown, { target: document })
46+
const event = new KeyboardEvent('keydown', { key: 'a' })
47+
document.dispatchEvent(event)
48+
expect(key).toBe('a')
49+
})
50+
51+
it('should support once option', async () => {
52+
let count = 0
53+
let triggered = false
54+
const onClick = () => {
55+
if (!triggered) {
56+
count++
57+
triggered = true
58+
}
59+
}
60+
useEventListener('click', onClick, { target: () => container, once: true })
61+
container.click()
62+
container.click()
63+
expect(count).toBe(1)
64+
})
65+
66+
it('should support passive option', async () => {
67+
let called = false
68+
const onWheel = () => {
69+
called = true
70+
}
71+
useEventListener('wheel', onWheel, { target: () => container, passive: true })
72+
const event = new Event('wheel')
73+
container.dispatchEvent(event)
74+
expect(called).toBe(true)
75+
})
76+
77+
it('should support capture option', async () => {
78+
const phase: string[] = []
79+
const onCapture = () => phase.push('capture')
80+
const onBubble = () => phase.push('bubble')
81+
useEventListener('click', onCapture, { target: () => container, capture: true })
82+
container.addEventListener('click', onBubble)
83+
container.click()
84+
expect(phase[0]).toBe('capture')
85+
expect(phase[1]).toBe('bubble')
86+
container.removeEventListener('click', onBubble)
87+
})
88+
89+
it('should remove event listener after unmount', async () => {
90+
let count = 0
91+
const onClick = () => {
92+
count++
93+
}
94+
const [, app] = renderHook(() => useEventListener('click', onClick, { target: () => container }))
95+
container.click()
96+
expect(count).toBe(1)
97+
app.unmount()
98+
container.click()
99+
expect(count).toBe(1)
100+
})
101+
102+
it('should not throw if target is null', async () => {
103+
expect(() => {
104+
renderHook(() => useEventListener('click', () => { }, { target: null as any }))
105+
}).not.toThrow()
106+
})
29107
})

packages/hooks/src/useFetchs/__tests__/index.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ async function getUsername(params: { desc: string }): Promise<string> {
88
() => {
99
resolve(`vue-hooks-plus ${params.desc}`)
1010
},
11-
params.desc === '大牛' ? 4000 : 2000,
11+
params.desc === '3' ? 4000 : 2000,
1212
)
1313
})
1414
}
1515

1616
describe('useFetchs', () => {
1717
it('should work', async () => {
18-
const arr = ['', '小牛', '中牛', '大牛']
18+
const arr = ['0', '1', '2', '3']
1919
const [{ fetchRun, fetchs }] = renderHook(() =>
2020
useFetchs(
2121
getUsername,
@@ -40,13 +40,13 @@ describe('useFetchs', () => {
4040
expect(fetchs.value[arr[3]].loading).toBeTruthy()
4141

4242
await sleep(2000)
43-
expect(fetchs.value[arr[0]].data).toBe('vue-hooks-plus ')
44-
expect(fetchs.value[arr[1]].data).toBe('vue-hooks-plus 小牛')
45-
expect(fetchs.value[arr[2]].data).toBe('vue-hooks-plus 中牛')
43+
expect(fetchs.value[arr[0]].data).toBe('vue-hooks-plus 0')
44+
expect(fetchs.value[arr[1]].data).toBe('vue-hooks-plus 1')
45+
expect(fetchs.value[arr[2]].data).toBe('vue-hooks-plus 2')
4646
expect(fetchs.value[arr[3]].loading).toBeTruthy()
4747

4848
await sleep(2000)
4949
await sleep(200)
50-
expect(fetchs.value[arr[3]].data).toBe('vue-hooks-plus 大牛')
50+
expect(fetchs.value[arr[3]].data).toBe('vue-hooks-plus 3')
5151
})
5252
})
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import renderHook from 'test-utils/renderHook'
2+
import useFocusWithin from '..'
3+
4+
describe('useFocusWithin', () => {
5+
let container: HTMLDivElement
6+
7+
beforeEach(() => {
8+
container = document.createElement('div')
9+
document.body.appendChild(container)
10+
})
11+
12+
afterEach(() => {
13+
document.body.removeChild(container)
14+
})
15+
16+
it('should return false initially', () => {
17+
const [state] = renderHook(() => useFocusWithin(container))
18+
expect(state.value).toBe(false)
19+
})
20+
21+
it('should call onFocus and onChange(true) when focusin event is triggered', () => {
22+
const onFocus = vitest.fn()
23+
const onChange = vitest.fn()
24+
renderHook(() => useFocusWithin(container, { onFocus, onChange }))
25+
const event = new FocusEvent('focusin', { bubbles: true })
26+
container.dispatchEvent(event)
27+
expect(onFocus).toHaveBeenCalledTimes(1)
28+
expect(onChange).toHaveBeenCalledWith(true)
29+
})
30+
31+
it('should call onBlur and onChange(false) when focusout event is triggered and relatedTarget is outside', () => {
32+
const onBlur = vitest.fn()
33+
const onChange = vitest.fn()
34+
renderHook(() => useFocusWithin(container, { onBlur, onChange }))
35+
// First focusin to set state to true
36+
container.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
37+
// Then focusout with relatedTarget outside
38+
const event = new FocusEvent('focusout', { bubbles: true, relatedTarget: document.body })
39+
Object.defineProperty(event, 'currentTarget', { value: container })
40+
container.dispatchEvent(event)
41+
expect(onBlur).toHaveBeenCalledTimes(1)
42+
expect(onChange).toHaveBeenCalledWith(false)
43+
})
44+
45+
it('should not call onBlur if relatedTarget is inside container', () => {
46+
const onBlur = vitest.fn()
47+
const onChange = vitest.fn()
48+
renderHook(() => useFocusWithin(container, { onBlur, onChange }))
49+
// First focusin to set state to true
50+
container.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
51+
// Create a child element and set as relatedTarget
52+
const child = document.createElement('div')
53+
container.appendChild(child)
54+
const event = new FocusEvent('focusout', { bubbles: true, relatedTarget: child })
55+
Object.defineProperty(event, 'currentTarget', { value: container })
56+
container.dispatchEvent(event)
57+
expect(onBlur).not.toHaveBeenCalled()
58+
// onChange(false) should not be called either
59+
expect(onChange).not.toHaveBeenCalledWith(false)
60+
})
61+
})

0 commit comments

Comments
 (0)