@@ -6,13 +6,15 @@ import type { ComponentProps } from 'svelte';
6
6
import { SearchableSelect as Subject , InputStates } from '$lib' ;
7
7
8
8
const onChange = vi . fn ( ) ;
9
+ const onMultiChange = vi . fn ( ) ;
9
10
const onFocus = vi . fn ( ) ;
10
11
const onBlur = vi . fn ( ) ;
11
12
12
13
const renderSubject = ( props : Partial < ComponentProps < Subject > > = { } ) => {
13
14
return render ( Subject , {
14
15
options : [ 'hello from' , 'the other side' ] ,
15
16
onChange,
17
+ onMultiChange,
16
18
onFocus,
17
19
onBlur,
18
20
...props ,
@@ -33,8 +35,8 @@ const getResults = (): {
33
35
return { search, button, list, options } ;
34
36
} ;
35
37
36
- describe ( 'combobox list ' , ( ) => {
37
- it ( 'controls a listbox' , ( ) => {
38
+ describe ( 'SearchableSelect ' , ( ) => {
39
+ it ( 'is a combobox that controls a listbox' , ( ) => {
38
40
renderSubject ( ) ;
39
41
40
42
const { search, button, list } = getResults ( ) ;
@@ -43,6 +45,7 @@ describe('combobox list', () => {
43
45
expect ( button ) . toHaveAttribute ( 'aria-controls' , list . id ) ;
44
46
expect ( search ) . toHaveAttribute ( 'aria-controls' , list . id ) ;
45
47
expect ( search ) . toHaveAttribute ( 'aria-autocomplete' , 'list' ) ;
48
+ expect ( search ) . not . toHaveAttribute ( 'aria-multiselectable' ) ;
46
49
} ) ;
47
50
48
51
it ( 'has a placeholder' , ( ) => {
@@ -421,6 +424,31 @@ describe('combobox list', () => {
421
424
expect ( options [ 0 ] ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
422
425
} ) ;
423
426
427
+ it ( 'selects visually focused option with space' , async ( ) => {
428
+ const user = userEvent . setup ( ) ;
429
+ renderSubject ( ) ;
430
+
431
+ const { search } = getResults ( ) ;
432
+ await user . click ( search ) ;
433
+ await user . keyboard ( '{ArrowDown} ' ) ;
434
+
435
+ expect ( onChange ) . toHaveBeenCalledWith ( 'hello from' ) ;
436
+ expect ( search ) . toHaveValue ( 'hello from' ) ;
437
+ expect ( search ) . toHaveAttribute ( 'aria-expanded' , 'false' ) ;
438
+ } ) ;
439
+
440
+ it ( 'types with space when visual focus on search' , async ( ) => {
441
+ const user = userEvent . setup ( ) ;
442
+ renderSubject ( ) ;
443
+
444
+ const { search } = getResults ( ) ;
445
+ await user . click ( search ) ;
446
+ await user . keyboard ( ' ' ) ;
447
+
448
+ expect ( onChange ) . not . toHaveBeenCalled ( ) ;
449
+ expect ( search ) . toHaveValue ( ' ' ) ;
450
+ } ) ;
451
+
424
452
it ( 'sets cursor with home and end' , async ( ) => {
425
453
const user = userEvent . setup ( ) ;
426
454
renderSubject ( ) ;
@@ -471,4 +499,78 @@ describe('combobox list', () => {
471
499
472
500
expect ( options [ 0 ] ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
473
501
} ) ;
502
+
503
+ describe ( 'multiple mode' , ( ) => {
504
+ it ( 'can select multiple options without closing' , async ( ) => {
505
+ const user = userEvent . setup ( ) ;
506
+ renderSubject ( { multiple : true } ) ;
507
+
508
+ const { search, options } = getResults ( ) ;
509
+
510
+ expect ( search ) . toHaveAttribute ( 'aria-multiselectable' , 'true' ) ;
511
+
512
+ await user . click ( search ) ;
513
+
514
+ // TODO(mc, 2024-02-03): replace .click with userEvent
515
+ // https://github.com/testing-library/user-event/issues/1119
516
+ await act ( ( ) => options [ 0 ] ?. click ( ) ) ;
517
+ expect ( onMultiChange ) . toHaveBeenCalledWith ( [ 'hello from' ] ) ;
518
+ expect ( search ) . toHaveFocus ( ) ;
519
+ expect ( search ) . toHaveValue ( '' ) ;
520
+ expect ( search ) . toHaveAttribute ( 'aria-expanded' , 'true' ) ;
521
+ expect ( options [ 0 ] ) . toHaveAttribute ( 'aria-checked' , 'true' ) ;
522
+ expect ( options [ 1 ] ) . toHaveAttribute ( 'aria-checked' , 'false' ) ;
523
+
524
+ await act ( ( ) => options [ 1 ] ?. click ( ) ) ;
525
+ expect ( onMultiChange ) . toHaveBeenCalledWith ( [
526
+ 'hello from' ,
527
+ 'the other side' ,
528
+ ] ) ;
529
+ expect ( search ) . toHaveFocus ( ) ;
530
+ expect ( search ) . toHaveValue ( '' ) ;
531
+ expect ( search ) . toHaveAttribute ( 'aria-expanded' , 'true' ) ;
532
+ expect ( options [ 0 ] ) . toHaveAttribute ( 'aria-checked' , 'true' ) ;
533
+ expect ( options [ 1 ] ) . toHaveAttribute ( 'aria-checked' , 'true' ) ;
534
+ } ) ;
535
+
536
+ it ( 'can select unselect with the mouse' , async ( ) => {
537
+ const user = userEvent . setup ( ) ;
538
+ renderSubject ( { multiple : true } ) ;
539
+
540
+ const { search, options } = getResults ( ) ;
541
+
542
+ await user . click ( search ) ;
543
+
544
+ // TODO(mc, 2024-02-03): replace .click with userEvent
545
+ // https://github.com/testing-library/user-event/issues/1119
546
+ await act ( ( ) => options [ 0 ] ?. click ( ) ) ;
547
+ expect ( onMultiChange ) . toHaveBeenCalledWith ( [ 'hello from' ] ) ;
548
+ await act ( ( ) => options [ 0 ] ?. click ( ) ) ;
549
+ expect ( onMultiChange ) . toHaveBeenCalledWith ( [ ] ) ;
550
+ } ) ;
551
+
552
+ it ( 'resets search input on select' , async ( ) => {
553
+ const user = userEvent . setup ( ) ;
554
+ renderSubject ( { multiple : true } ) ;
555
+
556
+ const { search } = getResults ( ) ;
557
+ await user . click ( search ) ;
558
+
559
+ await user . type ( search , 'hello{Enter}' ) ;
560
+
561
+ expect ( onMultiChange ) . toHaveBeenCalledWith ( [ 'hello from' ] ) ;
562
+ expect ( search ) . toHaveValue ( '' ) ;
563
+ } ) ;
564
+
565
+ it ( 'closes menu on blur' , async ( ) => {
566
+ const user = userEvent . setup ( ) ;
567
+ renderSubject ( { multiple : true } ) ;
568
+
569
+ const { search } = getResults ( ) ;
570
+ await user . click ( search ) ;
571
+ await user . keyboard ( '{Tab}' ) ;
572
+
573
+ expect ( search ) . toHaveAttribute ( 'aria-expanded' , 'false' ) ;
574
+ } ) ;
575
+ } ) ;
474
576
} ) ;
0 commit comments