tangled
alpha
login
or
join now
kitten.sh
/
use-interactions
0
fork
atom
Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
0
fork
atom
overview
issues
pulls
pipelines
Add test for nested or duplicated useDialogFocus
kitten.sh
4 years ago
24e868f4
141e3053
+192
1 changed file
expand all
collapse all
unified
split
src
__tests__
useDialogFocus.test.tsx
+192
src/__tests__/useDialogFocus.test.tsx
reviewed
···
164
164
cy.realPress('Escape');
165
165
cy.get('@input').should('have.focus');
166
166
});
167
167
+
168
168
+
it('supports nested dialogs', () => {
169
169
+
const InnerDialog = () => {
170
170
+
const ref = useRef<HTMLUListElement>(null);
171
171
+
useDialogFocus(ref);
172
172
+
173
173
+
return (
174
174
+
<ul ref={ref} role="dialog">
175
175
+
<li tabIndex={0}>Inner #1</li>
176
176
+
<li tabIndex={0}>Inner #2</li>
177
177
+
</ul>
178
178
+
);
179
179
+
};
180
180
+
181
181
+
const OuterDialog = () => {
182
182
+
const [visible, setVisible] = useState(false);
183
183
+
const [nested, setNested] = useState(false);
184
184
+
const ref = useRef<HTMLUListElement>(null);
185
185
+
186
186
+
useDialogFocus(ref, { disabled: !visible });
187
187
+
188
188
+
return (
189
189
+
<main>
190
190
+
<input type="text" name="text" onFocus={() => setVisible(true)} />
191
191
+
{visible && (
192
192
+
<ul ref={ref} role="dialog">
193
193
+
<li tabIndex={0}>Outer #1</li>
194
194
+
<li tabIndex={0} onFocus={() => setNested(true)}>Outer #2</li>
195
195
+
{nested && <InnerDialog />}
196
196
+
</ul>
197
197
+
)}
198
198
+
<button>after</button>
199
199
+
</main>
200
200
+
);
201
201
+
};
202
202
+
203
203
+
mount(<OuterDialog />);
204
204
+
205
205
+
cy.get('input').first().as('input').focus();
206
206
+
cy.focused().should('have.property.name', 'text');
207
207
+
208
208
+
// select first dialog
209
209
+
cy.realPress('ArrowDown');
210
210
+
cy.focused().contains('Outer #1');
211
211
+
cy.realPress('ArrowDown');
212
212
+
cy.focused().contains('Outer #2');
213
213
+
214
214
+
// select second dialog
215
215
+
cy.realPress('ArrowDown');
216
216
+
cy.focused().contains('Inner #1');
217
217
+
cy.realPress('ArrowDown');
218
218
+
cy.focused().contains('Inner #2');
219
219
+
220
220
+
// remains in inner dialog
221
221
+
cy.realPress('ArrowDown');
222
222
+
cy.focused().contains('Inner #1');
223
223
+
224
224
+
// tabs to last dialog
225
225
+
cy.realPress(['Shift', 'Tab']);
226
226
+
cy.focused().contains('Outer #2');
227
227
+
228
228
+
// arrows bring us back to the inner dialog
229
229
+
cy.realPress('ArrowUp');
230
230
+
cy.focused().contains('Inner #2');
231
231
+
232
232
+
// tab out of dialogs
233
233
+
cy.realPress('Tab');
234
234
+
cy.focused().contains('after');
235
235
+
// we can't reenter the dialogs
236
236
+
cy.realPress(['Shift', 'Tab']);
237
237
+
cy.get('@input').should('have.focus');
238
238
+
});
239
239
+
240
240
+
it('supports nested dialogs', () => {
241
241
+
const InnerDialog = () => {
242
242
+
const ref = useRef<HTMLUListElement>(null);
243
243
+
useDialogFocus(ref);
244
244
+
245
245
+
return (
246
246
+
<ul ref={ref} role="dialog">
247
247
+
<li tabIndex={0}>Inner #1</li>
248
248
+
<li tabIndex={0}>Inner #2</li>
249
249
+
</ul>
250
250
+
);
251
251
+
};
252
252
+
253
253
+
const OuterDialog = () => {
254
254
+
const [visible, setVisible] = useState(false);
255
255
+
const [nested, setNested] = useState(false);
256
256
+
const ref = useRef<HTMLUListElement>(null);
257
257
+
258
258
+
useDialogFocus(ref, { disabled: !visible });
259
259
+
260
260
+
return (
261
261
+
<main>
262
262
+
<input type="text" name="text" onFocus={() => setVisible(true)} />
263
263
+
{visible && (
264
264
+
<ul ref={ref} role="dialog">
265
265
+
<li tabIndex={0}>Outer #1</li>
266
266
+
<li tabIndex={0} onFocus={() => setNested(true)}>Outer #2</li>
267
267
+
{nested && <InnerDialog />}
268
268
+
</ul>
269
269
+
)}
270
270
+
<button>after</button>
271
271
+
</main>
272
272
+
);
273
273
+
};
274
274
+
275
275
+
mount(<OuterDialog />);
276
276
+
277
277
+
cy.get('input').first().as('input').focus();
278
278
+
cy.focused().should('have.property.name', 'text');
279
279
+
280
280
+
// select first dialog
281
281
+
cy.realPress('ArrowDown');
282
282
+
cy.focused().contains('Outer #1');
283
283
+
cy.realPress('ArrowDown');
284
284
+
cy.focused().contains('Outer #2');
285
285
+
286
286
+
// select second dialog
287
287
+
cy.realPress('ArrowDown');
288
288
+
cy.focused().contains('Inner #1');
289
289
+
cy.realPress('ArrowDown');
290
290
+
cy.focused().contains('Inner #2');
291
291
+
292
292
+
// remains in inner dialog
293
293
+
cy.realPress('ArrowDown');
294
294
+
cy.focused().contains('Inner #1');
295
295
+
296
296
+
// tabs to last dialog
297
297
+
cy.realPress(['Shift', 'Tab']);
298
298
+
cy.focused().contains('Outer #2');
299
299
+
300
300
+
// arrows bring us back to the inner dialog
301
301
+
cy.realPress('ArrowUp');
302
302
+
cy.focused().contains('Inner #2');
303
303
+
304
304
+
// tab out of dialogs
305
305
+
cy.realPress('Tab');
306
306
+
cy.focused().contains('after');
307
307
+
// we can't reenter the dialogs
308
308
+
cy.realPress(['Shift', 'Tab']);
309
309
+
cy.get('@input').should('have.focus');
310
310
+
});
311
311
+
312
312
+
it('allows dialogs in semantic order', () => {
313
313
+
const Dialog = ({ name }) => {
314
314
+
const ownerRef = useRef<HTMLInputElement>(null);
315
315
+
const ref = useRef<HTMLUListElement>(null);
316
316
+
317
317
+
useDialogFocus(ref, { ownerRef });
318
318
+
319
319
+
return (
320
320
+
<div>
321
321
+
<input type="text" className={name} ref={ownerRef} tabIndex={-1} />
322
322
+
<ul ref={ref} role="dialog">
323
323
+
<li tabIndex={0}>{name} #1</li>
324
324
+
<li tabIndex={0}>{name} #2</li>
325
325
+
</ul>
326
326
+
</div>
327
327
+
);
328
328
+
};
329
329
+
330
330
+
mount(
331
331
+
<main>
332
332
+
<Dialog name="First" />
333
333
+
<Dialog name="Second" />
334
334
+
<button>after</button>
335
335
+
</main>
336
336
+
);
337
337
+
338
338
+
cy.get('.First').first().as('first');
339
339
+
cy.get('.Second').first().as('second');
340
340
+
341
341
+
// focus first dialog
342
342
+
cy.get('@first').focus();
343
343
+
cy.get('.First').first().as('first').focus();
344
344
+
345
345
+
// tabs over both subsequent dialogs
346
346
+
cy.realPress('Tab');
347
347
+
cy.focused().contains('after');
348
348
+
349
349
+
// given a focused first input, doesn't allow the first dialog to be used
350
350
+
cy.get('@first').focus();
351
351
+
cy.realPress('ArrowDown');
352
352
+
cy.get('@first').should('have.focus');
353
353
+
354
354
+
// given a focused second input, does allow the second dialog to be used
355
355
+
cy.get('@second').focus();
356
356
+
cy.realPress('ArrowDown');
357
357
+
cy.focused().contains('Second #1');
358
358
+
});