-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDropdown.test.tsx
More file actions
254 lines (214 loc) · 8.71 KB
/
Copy pathDropdown.test.tsx
File metadata and controls
254 lines (214 loc) · 8.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import { act, render } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import Dropdown from './Dropdown'
import styles from './Dropdown.module.css'
describe('Dropdown Component', () => {
it('renders dropdown with its children', () => {
const { container: { children: [ div ] }, getByText } = render(
<Dropdown><div>Child 1</div><div>Child 2</div></Dropdown>
)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
getByText('Child 1')
getByText('Child 2')
expect(div?.classList).toContain(styles.dropdownLeft)
})
it('toggles dropdown content on button click', async () => {
const { container: { children: [ div ] }, getByRole } = render(
<Dropdown label='go'>
<div>Child 1</div>
<div>Child 2</div>
</Dropdown>
)
const dropdownButton = getByRole('button')
// open menu with click
const user = userEvent.setup()
await user.click(dropdownButton)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
// click again to close
await user.click(dropdownButton)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})
it('closes dropdown when clicking outside', async () => {
const { container: { children: [ div ] }, getByRole } = render(
<Dropdown>
<div>Child 1</div>
<div>Child 2</div>
</Dropdown>
)
const dropdownButton = getByRole('button')
const user = userEvent.setup()
await user.click(dropdownButton) // open dropdown
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
// Simulate a click outside
await user.click(document.body)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})
it('closes dropdown when clicking inside', async () => {
const { container: { children: [ div ] }, getByRole, getByText } = render(
<Dropdown>
<div>Child 1</div>
<div>Child 2</div>
</Dropdown>
)
const dropdownButton = getByRole('button')
const user = userEvent.setup()
await user.click(dropdownButton) // open dropdown
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
const dropdownContent = getByText('Child 1').parentElement
if (!dropdownContent) throw new Error('Dropdown content not found')
await user.click(dropdownContent)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})
it('closes dropdown on escape key press', async () => {
const { container: { children: [ div ] }, getByRole } = render(
<Dropdown>
<div>Child 1</div>
<div>Child 2</div>
</Dropdown>
)
const dropdownButton = getByRole('button')
const user = userEvent.setup()
await user.click(dropdownButton) // open dropdown
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
// Press escape key
await user.keyboard('{Escape}')
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})
it('adds dropdownLeft class when align is left', () => {
const { container: { children: [ div ] } } = render(
<Dropdown align='left'><div>Child 1</div><div>Child 2</div></Dropdown>
)
expect(div?.classList).toContain(styles.dropdownLeft)
})
it('cleans up event listeners on unmount', () => {
const { unmount } = render(<Dropdown><div>Dropdown Content</div></Dropdown>)
// Mock function to replace the actual document event listener
const mockRemoveEventListener = vi.spyOn(document, 'removeEventListener')
// Unmount the component
unmount()
// Check if the event listener was removed
expect(mockRemoveEventListener).toHaveBeenCalledWith('click', expect.any(Function))
expect(mockRemoveEventListener).toHaveBeenCalledWith('keydown', expect.any(Function))
expect(mockRemoveEventListener).toHaveBeenCalledWith('mousedown', expect.any(Function))
})
// Keyboard navigation tests
it('opens dropdown and focuses first item on ArrowDown when closed', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
<button role="menuitem">Item 2</button>
</Dropdown>
)
const menuItems = getAllByRole('menuitem')
const dropdownButton = getByRole('button')
// initially closed
expect(dropdownButton.getAttribute('aria-expanded')).toBe('false')
// focus the button
act(() => {
dropdownButton.focus()
})
// down arrow to open menu
const user = userEvent.setup()
await user.keyboard('{ArrowDown}')
expect(dropdownButton.getAttribute('aria-expanded')).toBe('true')
// first menu item should be focused
expect(document.activeElement).toBe(menuItems[0])
})
it('focuses the next item on ArrowDown and wraps to first item if at the end', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
<button role="menuitem">Item 2</button>
</Dropdown>
)
const menuItems = getAllByRole('menuitem') as [HTMLElement, HTMLElement]
const dropdownButton = getByRole('button')
// open menu, first item has focus
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])
// second item should be focused
await user.keyboard('{ArrowDown}')
expect(document.activeElement).toBe(menuItems[1])
// wrap back to first item
await user.keyboard('{ArrowDown}')
expect(document.activeElement).toBe(menuItems[0])
})
it('focuses the previous item on ArrowUp and wraps to the last item if at the top', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
<button role="menuitem">Item 2</button>
</Dropdown>
)
const menuItems = getAllByRole('menuitem') as [HTMLElement, HTMLElement]
const dropdownButton = getByRole('button')
// open menu, first item has focus
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])
// ArrowUp -> should wrap to last item
await user.keyboard('{ArrowUp}')
expect(document.activeElement).toBe(menuItems[1])
})
it('focuses first item on Home key press', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
<button role="menuitem">Item 2</button>
<button role="menuitem">Item 3</button>
</Dropdown>
)
const menuItems = getAllByRole('menuitem') as [HTMLElement, HTMLElement, HTMLElement]
const dropdownButton = getByRole('button')
// open menu, first item has focus
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])
// move to the second item
await user.keyboard('{ArrowDown}')
expect(document.activeElement).toBe(menuItems[1])
// Home key should focus first item
await user.keyboard('{Home}')
expect(document.activeElement).toBe(menuItems[0])
})
it('focuses last item on End key press', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
<button role="menuitem">Item 2</button>
<button role="menuitem">Item 3</button>
</Dropdown>
)
const menuItems = getAllByRole('menuitem') as [HTMLElement, HTMLElement, HTMLElement]
const dropdownButton = getByRole('button')
// open menu, first item has focus
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])
// End key should focus the last item
await user.keyboard('{End}')
expect(document.activeElement).toBe(menuItems[2])
})
it('closes the menu and puts focus back on the button on Escape', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
<button role="menuitem">Item 2</button>
</Dropdown>
)
const menuItems = getAllByRole('menuitem') as [HTMLElement]
const dropdownButton = getByRole('button')
// open menu, first item has focus
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])
expect(dropdownButton.getAttribute('aria-expanded')).toBe('true')
// escape closes menu
await user.keyboard('{Escape}')
expect(dropdownButton.getAttribute('aria-expanded')).toBe('false')
// focus returns to the button
expect(document.activeElement).toBe(dropdownButton)
})
})