@@ -173,6 +173,24 @@ describe('Critical Functionality', () => {
173
173
174
174
cy . findByRole ( 'listbox' ) . should ( 'not.exist' ) ;
175
175
} ) ;
176
+
177
+ it ( `GIVEN a Combobox component with an open listbox
178
+ WHEN the user hits the downarrow with the input focused
179
+ THEN input should remain focused` , ( ) => {
180
+ cy . mount ( < StringCombobox /> ) ;
181
+
182
+ cy . get ( 'input' ) . type ( `{downarrow}` ) . should ( 'have.focus' ) ;
183
+ } ) ;
184
+
185
+ it ( `GIVEN a Combobox component with a trigger
186
+ WHEN the user clicks on the trigger
187
+ THEN the input should remain focused` , ( ) => {
188
+ cy . mount ( < StringCombobox /> ) ;
189
+
190
+ cy . get ( 'button' ) . click ( ) ;
191
+
192
+ cy . get ( 'input' ) . should ( 'have.focus' ) ;
193
+ } ) ;
176
194
} ) ;
177
195
178
196
describe ( 'Default Label' , ( ) => {
@@ -319,7 +337,7 @@ describe('Keyboard Navigation', () => {
319
337
cy . get ( 'li' ) . filter ( ':visible' ) . first ( ) . should ( 'have.attr' , 'aria-selected' , 'true' ) ;
320
338
} ) ;
321
339
322
- it ( `GIVEN a Combobox component with an open listbox and multiple filtered options
340
+ it ( `GIVEN a Combobox component with an open listbox and multiple filtered options
323
341
WHEN the down arrow key is pressed,
324
342
THEN the 1st filtered option in the listbox should be selected.` , ( ) => {
325
343
cy . mount ( < StringCombobox /> ) ;
@@ -348,102 +366,266 @@ describe('Keyboard Navigation', () => {
348
366
} ) ;
349
367
} ) ;
350
368
351
- // const DisabledCombobox = component$(() => {
352
- // type Trainer = {
353
- // testValue: string;
354
- // testLabel: string;
355
- // disabled: boolean;
356
- // };
357
-
358
- // const objectExample: Array<Trainer> = [
359
- // { testValue: 'alice', testLabel: 'Alice', disabled: true },
360
- // { testValue: 'joana', testLabel: 'Joana', disabled: true },
361
- // { testValue: 'malcolm', testLabel: 'Malcolm', disabled: false },
362
- // { testValue: 'zack', testLabel: 'Zack', disabled: true },
363
- // { testValue: 'brian', testLabel: 'Brian', disabled: false },
364
- // { testValue: 'ryan', testLabel: 'Ryan', disabled: false },
365
- // { testValue: 'joe', testLabel: 'Joe', disabled: false },
366
- // { testValue: 'randy', testLabel: 'Randy', disabled: false },
367
- // { testValue: 'david', testLabel: 'David', disabled: true },
368
- // { testValue: 'joseph', testLabel: 'Joseph', disabled: false }
369
- // ];
370
-
371
- // const objectExampleSig = useSignal(objectExample);
372
-
373
- // const onInputChange$ = $((value: string) => {
374
- // objectExampleSig.value = objectExample.filter((option) => {
375
- // return option.testLabel.toLowerCase().includes(value.toLowerCase());
376
- // });
377
- // });
378
-
379
- // return (
380
- // <>
381
- // <QwikUIProvider>
382
- // <Combobox
383
- // options={objectExampleSig}
384
- // onInputChange$={onInputChange$}
385
- // optionLabelKey="testLabel"
386
- // optionValue="testValue"
387
- // optionDisabledKey="disabled"
388
- // optionComponent$={$((option: Trainer, index: number) => (
389
- // <ComboboxOption
390
- // style={{ color: option.disabled ? 'gray' : '' }}
391
- // class="option"
392
- // index={index}
393
- // option={option}
394
- // >
395
- // {option.testLabel}
396
- // </ComboboxOption>
397
- // ))}
398
- // >
399
- // <ComboboxLabel>Fruits</ComboboxLabel>
400
- // <ComboboxControl style={{ display: 'flex' }}>
401
- // <ComboboxInput />
402
- // <ComboboxTrigger data-testid="trigger">
403
- // <svg
404
- // xmlns="http://www.w3.org/2000/svg"
405
- // viewBox="0 0 24 24"
406
- // width="20px"
407
- // style="stroke: black"
408
- // stroke-width="2"
409
- // stroke-linecap="round"
410
- // stroke-linejoin="round"
411
- // >
412
- // <polyline points="6 9 12 15 18 9"></polyline>
413
- // </svg>
414
- // </ComboboxTrigger>
415
- // </ComboboxControl>
416
- // <ComboboxPortal>
417
- // <ComboboxListbox style={{ width: 'fit-content' }} />
418
- // </ComboboxPortal>
419
- // </Combobox>
420
- // </QwikUIProvider>
421
- // </>
422
- // );
423
- // });
424
-
425
- // describe('Disabled', () => {
426
- // it(`GIVEN a Combobox component with an open listbox and a disabled option,
427
- // WHEN the user clicks on the disabled option,
428
- // THEN the disabled option should not be selected.`, () => {
429
- // cy.mount(<DisabledCombobox />);
430
-
431
- // cy.get('[data-testid="trigger"]').click();
432
-
433
- // cy.findByRole('option', { name: `Alice` }).click();
434
-
435
- // cy.get('input').should('have.value', '');
436
- // });
437
-
438
- // it(`GIVEN a Combobox component with an open listbox and a disabled option,
439
- // WHEN the user clicks on the disabled option,
440
- // THEN the listbox should not close`, () => {
441
- // cy.mount(<DisabledCombobox />);
442
-
443
- // cy.get('button').click();
444
-
445
- // cy.findByRole('option', { name: `I'm disabled!` }).click();
446
-
447
- // cy.get('listbox').should('exist');
448
- // });
449
- // });
369
+ const DisabledCombobox = component$ ( ( ) => {
370
+ type Trainer = {
371
+ testValue : string ;
372
+ testLabel : string ;
373
+ disabled : boolean ;
374
+ } ;
375
+
376
+ const objectExample : Array < Trainer > = [
377
+ { testValue : 'alice' , testLabel : 'Alice' , disabled : true } ,
378
+ { testValue : 'joana' , testLabel : 'Joana' , disabled : true } ,
379
+ { testValue : 'malcolm' , testLabel : 'Malcolm' , disabled : false } ,
380
+ { testValue : 'zack' , testLabel : 'Zack' , disabled : true } ,
381
+ { testValue : 'brian' , testLabel : 'Brian' , disabled : false } ,
382
+ { testValue : 'ryan' , testLabel : 'Ryan' , disabled : false } ,
383
+ { testValue : 'joe' , testLabel : 'Joe' , disabled : false } ,
384
+ { testValue : 'randy' , testLabel : 'Randy' , disabled : false } ,
385
+ { testValue : 'david' , testLabel : 'David' , disabled : true } ,
386
+ { testValue : 'joseph' , testLabel : 'Joseph' , disabled : true } ,
387
+ { testValue : 'mark' , testLabel : 'Mark' , disabled : false } ,
388
+ { testValue : 'sidney' , testLabel : 'Sidney' , disabled : true }
389
+ ] ;
390
+
391
+ const objectExampleSig = useSignal ( objectExample ) ;
392
+
393
+ const onInputChange$ = $ ( ( value : string ) => {
394
+ objectExampleSig . value = objectExample . filter ( ( option ) => {
395
+ return option . testLabel . toLowerCase ( ) . includes ( value . toLowerCase ( ) ) ;
396
+ } ) ;
397
+ } ) ;
398
+
399
+ return (
400
+ < >
401
+ < QwikUIProvider >
402
+ < Combobox
403
+ options = { objectExampleSig }
404
+ onInputChange$ = { onInputChange$ }
405
+ optionLabelKey = "testLabel"
406
+ optionValue = "testValue"
407
+ optionDisabledKey = "disabled"
408
+ optionComponent$ = { $ ( ( option : Trainer , index : number ) => (
409
+ < ComboboxOption
410
+ style = { { color : option . disabled ? 'gray' : '' } }
411
+ class = "option"
412
+ index = { index }
413
+ option = { option }
414
+ >
415
+ { option . testLabel }
416
+ </ ComboboxOption >
417
+ ) ) }
418
+ >
419
+ < ComboboxLabel > Fruits</ ComboboxLabel >
420
+ < ComboboxControl style = { { display : 'flex' } } >
421
+ < ComboboxInput />
422
+ < ComboboxTrigger data-testid = "trigger" >
423
+ < svg
424
+ xmlns = "http://www.w3.org/2000/svg"
425
+ viewBox = "0 0 24 24"
426
+ width = "20px"
427
+ style = "stroke: black"
428
+ stroke-width = "2"
429
+ stroke-linecap = "round"
430
+ stroke-linejoin = "round"
431
+ >
432
+ < polyline points = "6 9 12 15 18 9" > </ polyline >
433
+ </ svg >
434
+ </ ComboboxTrigger >
435
+ </ ComboboxControl >
436
+ < ComboboxPortal >
437
+ < ComboboxListbox style = { { width : 'fit-content' } } />
438
+ </ ComboboxPortal >
439
+ </ Combobox >
440
+ </ QwikUIProvider >
441
+ </ >
442
+ ) ;
443
+ } ) ;
444
+
445
+ describe ( 'Disabled & Object Combobox' , ( ) => {
446
+ it ( `GIVEN a Combobox component with an open listbox and a disabled option,
447
+ WHEN the user clicks on a disabled option,
448
+ THEN the disabled option should not be selected.` , ( ) => {
449
+ cy . mount ( < DisabledCombobox /> ) ;
450
+
451
+ cy . findByTestId ( 'trigger' ) . click ( ) ;
452
+
453
+ cy . findByRole ( 'option' , { name : `David` } ) . click ( ) ;
454
+
455
+ cy . get ( 'input' ) . should ( 'have.value' , '' ) ;
456
+ } ) ;
457
+
458
+ it ( `GIVEN a Combobox component with an open listbox and a disabled option,
459
+ WHEN the user clicks on a disabled option,
460
+ THEN the listbox should remain open` , ( ) => {
461
+ cy . mount ( < DisabledCombobox /> ) ;
462
+
463
+ cy . findByTestId ( 'trigger' ) . click ( ) ;
464
+
465
+ cy . findByRole ( 'option' , { name : `David` } ) . click ( ) ;
466
+
467
+ cy . findByRole ( 'listbox' ) . should ( 'be.visible' ) ;
468
+ } ) ;
469
+
470
+ it ( `GIVEN a Combobox component with an open listbox and a disabled option,
471
+ WHEN the user clicks on a disabled option,
472
+ THEN the input should remain focused` , ( ) => {
473
+ cy . mount ( < DisabledCombobox /> ) ;
474
+
475
+ cy . findByTestId ( 'trigger' ) . click ( ) ;
476
+
477
+ cy . findByRole ( 'option' , { name : `David` } ) . click ( ) ;
478
+
479
+ cy . get ( 'input' ) . should ( 'have.focus' ) ;
480
+ } ) ;
481
+
482
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
483
+ WHEN the user hits the downarrow with the input focused
484
+ THEN the first enabled option should be selected` , ( ) => {
485
+ cy . mount ( < DisabledCombobox /> ) ;
486
+
487
+ cy . get ( 'input' ) . type ( `{downarrow}` ) ;
488
+
489
+ cy . findByRole ( 'option' , { name : `Malcolm` } ) . should (
490
+ 'have.attr' ,
491
+ 'aria-selected' ,
492
+ 'true'
493
+ ) ;
494
+ } ) ;
495
+
496
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
497
+ WHEN the user hits the up arrow on the first enabled option
498
+ THEN the last enabled option should be selected` , ( ) => {
499
+ cy . mount ( < DisabledCombobox /> ) ;
500
+
501
+ cy . get ( 'input' ) . type ( `{downarrow}{uparrow}` ) ;
502
+
503
+ cy . findByRole ( 'option' , { name : `Mark` } ) . should (
504
+ 'have.attr' ,
505
+ 'aria-selected' ,
506
+ 'true'
507
+ ) ;
508
+ } ) ;
509
+
510
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
511
+ WHEN the user is on an option and pressed the Home key
512
+ THEN the first enabled option should be selected` , ( ) => {
513
+ cy . mount ( < DisabledCombobox /> ) ;
514
+
515
+ cy . get ( 'input' ) . type ( `{downarrow}{downarrow}{downarrow}` ) ;
516
+
517
+ cy . get ( 'input' ) . type ( `{home}` ) ;
518
+
519
+ cy . findByRole ( 'option' , { name : `Malcolm` } ) . should (
520
+ 'have.attr' ,
521
+ 'aria-selected' ,
522
+ 'true'
523
+ ) ;
524
+ } ) ;
525
+
526
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
527
+ WHEN the user is on an option and pressed the End key
528
+ THEN the first last enabled option should be selected` , ( ) => {
529
+ cy . mount ( < DisabledCombobox /> ) ;
530
+
531
+ cy . get ( 'input' ) . type ( `{downarrow}{downarrow}` ) ;
532
+
533
+ cy . get ( 'input' ) . type ( `{end}` ) ;
534
+
535
+ cy . findByRole ( 'option' , { name : `Mark` } ) . should (
536
+ 'have.attr' ,
537
+ 'aria-selected' ,
538
+ 'true'
539
+ ) ;
540
+ } ) ;
541
+
542
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
543
+ WHEN the user is on an option and pressed the down arrow key
544
+ THEN the next enabled index should be selected skipping a disabled option` , ( ) => {
545
+ cy . mount ( < DisabledCombobox /> ) ;
546
+
547
+ // selects Malcolm
548
+ cy . get ( 'input' ) . type ( `{downarrow}` ) ;
549
+
550
+ cy . get ( 'input' ) . type ( `{downarrow}` ) ;
551
+
552
+ cy . findByRole ( 'option' , { name : `Brian` } ) . should (
553
+ 'have.attr' ,
554
+ 'aria-selected' ,
555
+ 'true'
556
+ ) ;
557
+ } ) ;
558
+
559
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
560
+ WHEN the user is on an option and pressed the up arrow key
561
+ THEN the previous enabled index should be selected skipping a disabled option` , ( ) => {
562
+ cy . mount ( < DisabledCombobox /> ) ;
563
+
564
+ // selects Malcolm
565
+ cy . get ( 'input' ) . type ( `{downarrow}` ) ;
566
+
567
+ cy . get ( 'input' ) . type ( `{downarrow}` ) ;
568
+
569
+ cy . get ( 'input' ) . type ( `{uparrow}` ) ;
570
+
571
+ cy . findByRole ( 'option' , { name : `Malcolm` } ) . should (
572
+ 'have.attr' ,
573
+ 'aria-selected' ,
574
+ 'true'
575
+ ) ;
576
+ } ) ;
577
+
578
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
579
+ WHEN the user is on an option and pressed the down arrow key
580
+ THEN the next enabled index should be selected skipping multiple disabled options` , ( ) => {
581
+ cy . mount ( < DisabledCombobox /> ) ;
582
+
583
+ // selects Malcolm
584
+ cy . get ( 'input' ) . type ( `{downarrow}{downarrow}{downarrow}{downarrow}{downarrow}` ) ;
585
+
586
+ cy . findByRole ( 'option' , { name : `Randy` } ) . should (
587
+ 'have.attr' ,
588
+ 'aria-selected' ,
589
+ 'true'
590
+ ) ;
591
+
592
+ cy . get ( 'input' ) . type ( `{downarrow}` ) ;
593
+
594
+ cy . findByRole ( 'option' , { name : `Mark` } ) . should (
595
+ 'have.attr' ,
596
+ 'aria-selected' ,
597
+ 'true'
598
+ ) ;
599
+ } ) ;
600
+
601
+ it ( `GIVEN a Combobox component with an open listbox and disabled options,
602
+ WHEN the user is on an option and pressed the up arrow key
603
+ THEN the previous enabled index should be selected skipping multiple disabled options` , ( ) => {
604
+ cy . mount ( < DisabledCombobox /> ) ;
605
+
606
+ // selects Malcolm
607
+ cy . get ( 'input' ) . type ( `{downarrow}{downarrow}{downarrow}{downarrow}{downarrow}` ) ;
608
+
609
+ cy . findByRole ( 'option' , { name : `Randy` } ) . should (
610
+ 'have.attr' ,
611
+ 'aria-selected' ,
612
+ 'true'
613
+ ) ;
614
+
615
+ cy . get ( 'input' ) . type ( `{downarrow}` ) ;
616
+
617
+ cy . findByRole ( 'option' , { name : `Mark` } ) . should (
618
+ 'have.attr' ,
619
+ 'aria-selected' ,
620
+ 'true'
621
+ ) ;
622
+
623
+ cy . get ( 'input' ) . type ( `{uparrow}` ) ;
624
+
625
+ cy . findByRole ( 'option' , { name : `Randy` } ) . should (
626
+ 'have.attr' ,
627
+ 'aria-selected' ,
628
+ 'true'
629
+ ) ;
630
+ } ) ;
631
+ } ) ;
0 commit comments