@@ -21,6 +21,7 @@ import scaleMedium from '@adobe/spectrum-css-temp/vars/spectrum-medium-unique.cs
21
21
import themeLight from '@adobe/spectrum-css-temp/vars/spectrum-light-unique.css' ;
22
22
import { triggerPress } from '@react-spectrum/test-utils' ;
23
23
import { typeText } from '@react-spectrum/test-utils' ;
24
+ import { useAsyncList } from '@react-stately/data' ;
24
25
import userEvent from '@testing-library/user-event' ;
25
26
26
27
let theme = {
@@ -78,6 +79,47 @@ function renderSectionComboBox(props = {}) {
78
79
) ;
79
80
}
80
81
82
+ let initialFilterItems = [
83
+ { name : 'Aardvark' , id : '1' } ,
84
+ { name : 'Kangaroo' , id : '2' } ,
85
+ { name : 'Snake' , id : '3' }
86
+ ] ;
87
+
88
+ let secondCallFilterItems = [
89
+ { name : 'Aardvark' , id : '1' }
90
+ ] ;
91
+
92
+ function getFilterItems ( ) {
93
+ return Promise . resolve ( {
94
+ items : initialFilterItems
95
+ } ) ;
96
+ }
97
+
98
+ function mockSecondCall ( ) {
99
+ return Promise . resolve ( {
100
+ items : secondCallFilterItems
101
+ } ) ;
102
+ }
103
+
104
+ let load ;
105
+ let AsyncComboBox = ( ) => {
106
+ let list = useAsyncList ( {
107
+ load : load
108
+ } ) ;
109
+
110
+ return (
111
+ < ComboBox
112
+ items = { list . items }
113
+ label = "Combobox"
114
+ inputValue = { list . filterText }
115
+ onInputChange = { list . setFilterText }
116
+ loadingState = { list . loadingState }
117
+ onLoadMore = { list . loadMore } >
118
+ { ( item ) => < Item > { item . name } </ Item > }
119
+ </ ComboBox >
120
+ ) ;
121
+ } ;
122
+
81
123
function testComboBoxOpen ( combobox , button , listbox , focusedItemIndex ) {
82
124
let buttonId = button . id ;
83
125
let comboboxLabelledBy = combobox . getAttribute ( 'aria-labelledby' ) ;
@@ -130,6 +172,14 @@ describe('ComboBox', function () {
130
172
jest . useFakeTimers ( ) ;
131
173
} ) ;
132
174
175
+ beforeEach ( ( ) => {
176
+ load = jest
177
+ . fn ( )
178
+ . mockImplementationOnce ( getFilterItems )
179
+ . mockImplementationOnce ( mockSecondCall )
180
+ . mockImplementationOnce ( mockSecondCall ) ;
181
+ } ) ;
182
+
133
183
afterEach ( ( ) => {
134
184
jest . clearAllMocks ( ) ;
135
185
act ( ( ) => jest . runAllTimers ( ) ) ;
@@ -2584,6 +2634,137 @@ describe('ComboBox', function () {
2584
2634
expect ( combobox ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
2585
2635
} ) ;
2586
2636
2637
+ describe ( 'loadingState' , function ( ) {
2638
+ it . each `
2639
+ LoadingState | ValidationState
2640
+ ${ 'loading' } | ${ null }
2641
+ ${ 'filtering' } | ${ null }
2642
+ ${ 'loading' } | ${ 'invalid' }
2643
+ ${ 'filtering' } | ${ 'invalid' }
2644
+ ` ( 'should render the loading swirl in the input field when loadingState="$LoadingState" and validationState="$ValidationState"' , ( { LoadingState, ValidationState} ) => {
2645
+ let { getByRole} = renderComboBox ( { loadingState : LoadingState , validationState : ValidationState } ) ;
2646
+ let button = getByRole ( 'button' ) ;
2647
+ let progressSpinner = getByRole ( 'progressbar' ) ;
2648
+
2649
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
2650
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading...' ) ;
2651
+
2652
+ let combobox = getByRole ( 'combobox' ) ;
2653
+ if ( ValidationState ) {
2654
+ expect ( combobox ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
2655
+ }
2656
+
2657
+ // validation icon should no be present
2658
+ expect ( ( ) => within ( combobox ) . getByRole ( 'img' , { hidden : true } ) ) . toThrow ( ) ;
2659
+
2660
+ act ( ( ) => {
2661
+ triggerPress ( button ) ;
2662
+ jest . runAllTimers ( ) ;
2663
+ } ) ;
2664
+
2665
+ let listbox = getByRole ( 'listbox' ) ;
2666
+ expect ( listbox ) . toBeVisible ( ) ;
2667
+ expect ( ( ) => within ( listbox ) . getByRole ( 'progressbar' ) ) . toThrow ( ) ;
2668
+ } ) ;
2669
+
2670
+ it ( 'should render the loading swirl in the listbox when loadingState="loadingMore"' , function ( ) {
2671
+ let { getByRole} = renderComboBox ( { loadingState : 'loadingMore' } ) ;
2672
+ let button = getByRole ( 'button' ) ;
2673
+
2674
+ expect ( ( ) => getByRole ( 'progressbar' ) ) . toThrow ( ) ;
2675
+
2676
+ act ( ( ) => {
2677
+ triggerPress ( button ) ;
2678
+ jest . runAllTimers ( ) ;
2679
+ } ) ;
2680
+
2681
+ let listbox = getByRole ( 'listbox' ) ;
2682
+ expect ( listbox ) . toBeVisible ( ) ;
2683
+
2684
+ let progressSpinner = within ( listbox ) . getByRole ( 'progressbar' ) ;
2685
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
2686
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading more…' ) ;
2687
+ } ) ;
2688
+ } ) ;
2689
+
2690
+ describe ( 'async loading' , function ( ) {
2691
+ it ( 'async combobox works with useAsyncList' , async ( ) => {
2692
+ let { getByRole} = render (
2693
+ < Provider theme = { theme } >
2694
+ < AsyncComboBox />
2695
+ </ Provider >
2696
+ ) ;
2697
+
2698
+ let combobox = getByRole ( 'combobox' ) ;
2699
+ let button = getByRole ( 'button' ) ;
2700
+ let progressSpinner = getByRole ( 'progressbar' ) ;
2701
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
2702
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading...' ) ;
2703
+
2704
+ await waitFor ( ( ) => expect ( load ) . toHaveBeenCalledTimes ( 1 ) ) ;
2705
+ expect ( load ) . toHaveBeenLastCalledWith (
2706
+ expect . objectContaining ( {
2707
+ 'filterText' : ''
2708
+ } )
2709
+ ) ;
2710
+
2711
+ act ( ( ) => {
2712
+ triggerPress ( button ) ;
2713
+ jest . runAllTimers ( ) ;
2714
+ } ) ;
2715
+
2716
+ expect ( ( ) => getByRole ( 'progressbar' ) ) . toThrow ( ) ;
2717
+
2718
+ let listbox = getByRole ( 'listbox' ) ;
2719
+ expect ( listbox ) . toBeTruthy ( ) ;
2720
+ let items = within ( listbox ) . getAllByRole ( 'option' ) ;
2721
+ expect ( items ) . toHaveLength ( 3 ) ;
2722
+ expect ( items [ 0 ] ) . toHaveTextContent ( 'Aardvark' ) ;
2723
+ expect ( items [ 1 ] ) . toHaveTextContent ( 'Kangaroo' ) ;
2724
+ expect ( items [ 2 ] ) . toHaveTextContent ( 'Snake' ) ;
2725
+
2726
+ act ( ( ) => {
2727
+ fireEvent . change ( combobox , { target : { value : 'aard' } } ) ;
2728
+ jest . runAllTimers ( ) ;
2729
+ } ) ;
2730
+
2731
+ progressSpinner = getByRole ( 'progressbar' ) ;
2732
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
2733
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading...' ) ;
2734
+
2735
+ await waitFor ( ( ) => expect ( load ) . toHaveBeenCalledTimes ( 2 ) ) ;
2736
+ expect ( load ) . toHaveBeenLastCalledWith (
2737
+ expect . objectContaining ( {
2738
+ 'filterText' : 'aard'
2739
+ } )
2740
+ ) ;
2741
+ expect ( ( ) => getByRole ( 'progressbar' ) ) . toThrow ( ) ;
2742
+
2743
+ items = within ( listbox ) . getAllByRole ( 'option' ) ;
2744
+ expect ( items ) . toHaveLength ( 1 ) ;
2745
+ expect ( items [ 0 ] ) . toHaveTextContent ( 'Aardvark' ) ;
2746
+
2747
+ act ( ( ) => {
2748
+ triggerPress ( items [ 0 ] ) ;
2749
+ jest . runAllTimers ( ) ;
2750
+ } ) ;
2751
+
2752
+ progressSpinner = getByRole ( 'progressbar' ) ;
2753
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
2754
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading...' ) ;
2755
+
2756
+ await waitFor ( ( ) => expect ( load ) . toHaveBeenCalledTimes ( 3 ) ) ;
2757
+ expect ( load ) . toHaveBeenLastCalledWith (
2758
+ expect . objectContaining ( {
2759
+ 'filterText' : 'Aardvark'
2760
+ } )
2761
+ ) ;
2762
+
2763
+ expect ( ( ) => getByRole ( 'progressbar' ) ) . toThrow ( ) ;
2764
+ expect ( combobox . value ) . toBe ( 'Aardvark' ) ;
2765
+ } ) ;
2766
+ } ) ;
2767
+
2587
2768
describe ( 'mobile combobox' , function ( ) {
2588
2769
beforeEach ( ( ) => {
2589
2770
jest . spyOn ( window . screen , 'width' , 'get' ) . mockImplementation ( ( ) => 600 ) ;
@@ -3272,6 +3453,177 @@ describe('ComboBox', function () {
3272
3453
expect ( document . activeElement ) . toBe ( getByRole ( 'button' ) ) ;
3273
3454
} ) ;
3274
3455
} ) ;
3456
+
3457
+ describe ( 'isLoading' , function ( ) {
3458
+ it . each `
3459
+ LoadingState | ValidationState
3460
+ ${ 'loading' } | ${ null }
3461
+ ${ 'filtering' } | ${ null }
3462
+ ${ 'loading' } | ${ 'invalid' }
3463
+ ${ 'filtering' } | ${ 'invalid' }
3464
+ ` ( 'should render the loading swirl in the tray input field when loadingState="$LoadingState" and validationState="$ValidationState"' , ( { LoadingState, ValidationState} ) => {
3465
+ let { getByRole, getByTestId} = renderComboBox ( { loadingState : LoadingState , validationState : ValidationState , defaultInputValue : 'O' } ) ;
3466
+ let button = getByRole ( 'button' ) ;
3467
+ let progressSpinner = getByRole ( 'progressbar' ) ;
3468
+
3469
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
3470
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading...' ) ;
3471
+
3472
+ act ( ( ) => {
3473
+ triggerPress ( button ) ;
3474
+ jest . runAllTimers ( ) ;
3475
+ } ) ;
3476
+
3477
+ let tray = getByTestId ( 'tray' ) ;
3478
+ expect ( tray ) . toBeVisible ( ) ;
3479
+
3480
+ let trayProgressSpinner = within ( tray ) . getByRole ( 'progressbar' ) ;
3481
+ expect ( trayProgressSpinner ) . toBeTruthy ( ) ;
3482
+
3483
+ if ( LoadingState === 'loading' ) {
3484
+ expect ( trayProgressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading more…' ) ;
3485
+ } else {
3486
+ expect ( trayProgressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading...' ) ;
3487
+ }
3488
+
3489
+
3490
+ let clearButton = within ( tray ) . getByLabelText ( 'Clear' ) ;
3491
+ expect ( clearButton ) . toBeTruthy ( ) ;
3492
+
3493
+ let listbox = getByRole ( 'listbox' ) ;
3494
+
3495
+ if ( LoadingState === 'loading' ) {
3496
+ expect ( within ( listbox ) . getByRole ( 'progressbar' ) ) . toBeTruthy ( ) ;
3497
+ } else {
3498
+ expect ( ( ) => within ( listbox ) . getByRole ( 'progressbar' ) ) . toThrow ( ) ;
3499
+ }
3500
+
3501
+ if ( ValidationState ) {
3502
+ let trayInput = within ( tray ) . getByRole ( 'searchbox' ) ;
3503
+ expect ( trayInput ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
3504
+ }
3505
+
3506
+ if ( ValidationState && LoadingState === 'loading' ) {
3507
+ // validation icon should be present along with the clear button
3508
+ expect ( within ( tray ) . getAllByRole ( 'img' , { hidden : true } ) ) . toHaveLength ( 2 ) ;
3509
+ } else {
3510
+ // validation icon should not be present, only img is the clear button
3511
+ expect ( within ( tray ) . getAllByRole ( 'img' , { hidden : true } ) ) . toHaveLength ( 1 ) ;
3512
+ }
3513
+ } ) ;
3514
+
3515
+ it ( 'should render the loading swirl in the listbox when loadingState="loadingMore"' , function ( ) {
3516
+ let { getByRole, getByTestId} = renderComboBox ( { loadingState : 'loadingMore' , validationState : 'invalid' } ) ;
3517
+ let button = getByRole ( 'button' ) ;
3518
+
3519
+ expect ( ( ) => getByRole ( 'progressbar' ) ) . toThrow ( ) ;
3520
+
3521
+ act ( ( ) => {
3522
+ triggerPress ( button ) ;
3523
+ jest . runAllTimers ( ) ;
3524
+ } ) ;
3525
+
3526
+ let tray = getByTestId ( 'tray' ) ;
3527
+ expect ( tray ) . toBeVisible ( ) ;
3528
+
3529
+ let allProgressSpinners = within ( tray ) . getAllByRole ( 'progressbar' ) ;
3530
+ expect ( allProgressSpinners . length ) . toBe ( 1 ) ;
3531
+
3532
+ let validationIcon = within ( tray ) . getByRole ( 'img' , { hidden : true } ) ;
3533
+ expect ( validationIcon ) . toBeTruthy ( ) ;
3534
+
3535
+ let trayInput = within ( tray ) . getByRole ( 'searchbox' ) ;
3536
+ expect ( trayInput ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
3537
+
3538
+ let listbox = getByRole ( 'listbox' ) ;
3539
+ let progressSpinner = within ( listbox ) . getByRole ( 'progressbar' ) ;
3540
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
3541
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading more…' ) ;
3542
+ } ) ;
3543
+ } ) ;
3544
+
3545
+ describe ( 'mobile async loading' , function ( ) {
3546
+ it ( 'async combobox works with useAsyncList' , async ( ) => {
3547
+ let { getByRole, getByTestId, getByText} = render (
3548
+ < Provider theme = { theme } >
3549
+ < AsyncComboBox />
3550
+ </ Provider >
3551
+ ) ;
3552
+
3553
+ let button = getByRole ( 'button' ) ;
3554
+ let progressSpinner = getByRole ( 'progressbar' ) ;
3555
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
3556
+
3557
+ await waitFor ( ( ) => expect ( load ) . toHaveBeenCalledTimes ( 1 ) ) ;
3558
+ expect ( load ) . toHaveBeenLastCalledWith (
3559
+ expect . objectContaining ( {
3560
+ 'filterText' : ''
3561
+ } )
3562
+ ) ;
3563
+
3564
+ act ( ( ) => {
3565
+ triggerPress ( button ) ;
3566
+ jest . runAllTimers ( ) ;
3567
+ } ) ;
3568
+
3569
+ expect ( ( ) => getByRole ( 'progressbar' ) ) . toThrow ( ) ;
3570
+
3571
+ let tray = getByTestId ( 'tray' ) ;
3572
+ expect ( tray ) . toBeVisible ( ) ;
3573
+ expect ( ( ) => within ( tray ) . getByRole ( 'progressbar' ) ) . toThrow ( ) ;
3574
+
3575
+ let listbox = getByRole ( 'listbox' ) ;
3576
+ expect ( ( ) => within ( listbox ) . getByRole ( 'progressbar' ) ) . toThrow ;
3577
+ let items = within ( listbox ) . getAllByRole ( 'option' ) ;
3578
+ expect ( items ) . toHaveLength ( 3 ) ;
3579
+ expect ( items [ 0 ] ) . toHaveTextContent ( 'Aardvark' ) ;
3580
+ expect ( items [ 1 ] ) . toHaveTextContent ( 'Kangaroo' ) ;
3581
+ expect ( items [ 2 ] ) . toHaveTextContent ( 'Snake' ) ;
3582
+
3583
+ let trayInput = within ( tray ) . getByRole ( 'searchbox' ) ;
3584
+ act ( ( ) => {
3585
+ trayInput . focus ( ) ;
3586
+ fireEvent . change ( trayInput , { target : { value : 'aard' } } ) ;
3587
+ jest . runAllTimers ( ) ;
3588
+ } ) ;
3589
+
3590
+ let trayInputProgress = within ( tray ) . getByRole ( 'progressbar' ) ;
3591
+ expect ( trayInputProgress ) . toBeTruthy ( ) ;
3592
+ expect ( ( ) => within ( listbox ) . getByRole ( 'progressbar' ) ) . toThrow ;
3593
+
3594
+ await waitFor ( ( ) => expect ( load ) . toHaveBeenCalledTimes ( 2 ) ) ;
3595
+ expect ( load ) . toHaveBeenLastCalledWith (
3596
+ expect . objectContaining ( {
3597
+ 'filterText' : 'aard'
3598
+ } )
3599
+ ) ;
3600
+ expect ( ( ) => within ( tray ) . getByRole ( 'progressbar' ) ) . toThrow ( ) ;
3601
+
3602
+ items = within ( listbox ) . getAllByRole ( 'option' ) ;
3603
+ expect ( items ) . toHaveLength ( 1 ) ;
3604
+ expect ( items [ 0 ] ) . toHaveTextContent ( 'Aardvark' ) ;
3605
+
3606
+ act ( ( ) => {
3607
+ triggerPress ( items [ 0 ] ) ;
3608
+ jest . runAllTimers ( ) ;
3609
+ } ) ;
3610
+
3611
+ progressSpinner = getByRole ( 'progressbar' ) ;
3612
+ expect ( progressSpinner ) . toBeTruthy ( ) ;
3613
+ expect ( progressSpinner ) . toHaveAttribute ( 'aria-label' , 'Loading...' ) ;
3614
+
3615
+ await waitFor ( ( ) => expect ( load ) . toHaveBeenCalledTimes ( 3 ) ) ;
3616
+ expect ( load ) . toHaveBeenLastCalledWith (
3617
+ expect . objectContaining ( {
3618
+ 'filterText' : 'Aardvark'
3619
+ } )
3620
+ ) ;
3621
+
3622
+ expect ( ( ) => getByRole ( 'progressbar' ) ) . toThrow ( ) ;
3623
+ expect ( ( ) => getByTestId ( 'tray' ) ) . toThrow ( ) ;
3624
+ expect ( button ) . toHaveAttribute ( 'aria-labelledby' , `${ getByText ( 'Combobox' ) . id } ${ getByText ( 'Aardvark' ) . id } ` ) ;
3625
+ } ) ;
3626
+ } ) ;
3275
3627
} ) ;
3276
3628
3277
3629
describe ( 'accessibility' , function ( ) {
0 commit comments