@@ -46,13 +46,15 @@ describe('Listbox Pattern', () => {
4646
4747 function getOptions ( listbox : TestListbox , values : string [ ] ) : TestOption [ ] {
4848 return values . map ( ( value , index ) => {
49+ const element = document . createElement ( 'div' ) ;
50+ element . role = 'option' ;
4951 return new OptionPattern ( {
5052 value : signal ( value ) ,
5153 id : signal ( `option-${ index } ` ) ,
5254 disabled : signal ( false ) ,
5355 searchTerm : signal ( value ) ,
5456 listbox : signal ( listbox ) ,
55- element : signal ( { focus : ( ) => { } } as HTMLElement ) ,
57+ element : signal ( element ) ,
5658 } ) ;
5759 } ) ;
5860 }
@@ -439,4 +441,158 @@ describe('Listbox Pattern', () => {
439441 } ) ;
440442 } ) ;
441443 } ) ;
444+
445+ describe ( 'Pointer Events' , ( ) => {
446+ function click ( options : WritableSignal < TestOption [ ] > , index : number , mods ?: ModifierKeys ) {
447+ return {
448+ target : options ( ) [ index ] . element ( ) ,
449+ shiftKey : mods ?. shift ,
450+ ctrlKey : mods ?. control ,
451+ } as unknown as PointerEvent ;
452+ }
453+
454+ describe ( 'follows focus & single select' , ( ) => {
455+ it ( 'should select a single option on click' , ( ) => {
456+ const { listbox, options} = getDefaultPatterns ( {
457+ multi : signal ( false ) ,
458+ selectionMode : signal ( 'follow' ) ,
459+ } ) ;
460+ listbox . onPointerdown ( click ( options , 0 ) ) ;
461+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' ] ) ;
462+ } ) ;
463+ } ) ;
464+
465+ describe ( 'explicit focus & single select' , ( ) => {
466+ it ( 'should select an unselected option on click' , ( ) => {
467+ const { listbox, options} = getDefaultPatterns ( {
468+ multi : signal ( false ) ,
469+ selectionMode : signal ( 'explicit' ) ,
470+ } ) ;
471+ listbox . onPointerdown ( click ( options , 0 ) ) ;
472+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' ] ) ;
473+ } ) ;
474+
475+ it ( 'should deselect a selected option on click' , ( ) => {
476+ const { listbox, options} = getDefaultPatterns ( {
477+ multi : signal ( false ) ,
478+ value : signal ( [ 'Apple' ] ) ,
479+ selectionMode : signal ( 'explicit' ) ,
480+ } ) ;
481+ listbox . onPointerdown ( click ( options , 0 ) ) ;
482+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
483+ } ) ;
484+ } ) ;
485+
486+ describe ( 'explicit focus & multi select' , ( ) => {
487+ it ( 'should select an unselected option on click' , ( ) => {
488+ const { listbox, options} = getDefaultPatterns ( {
489+ multi : signal ( true ) ,
490+ selectionMode : signal ( 'explicit' ) ,
491+ } ) ;
492+ listbox . onPointerdown ( click ( options , 0 ) ) ;
493+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' ] ) ;
494+ } ) ;
495+
496+ it ( 'should deselect a selected option on click' , ( ) => {
497+ const { listbox, options} = getDefaultPatterns ( {
498+ multi : signal ( true ) ,
499+ value : signal ( [ 'Apple' ] ) ,
500+ selectionMode : signal ( 'explicit' ) ,
501+ } ) ;
502+ listbox . onPointerdown ( click ( options , 0 ) ) ;
503+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
504+ } ) ;
505+
506+ it ( 'should select options from anchor on shift + click' , ( ) => {
507+ const { listbox, options} = getDefaultPatterns ( {
508+ multi : signal ( true ) ,
509+ selectionMode : signal ( 'explicit' ) ,
510+ } ) ;
511+ listbox . onPointerdown ( click ( options , 2 ) ) ;
512+ listbox . onPointerdown ( click ( options , 5 , { shift : true } ) ) ;
513+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Banana' , 'Blackberry' , 'Blueberry' , 'Cantaloupe' ] ) ;
514+ } ) ;
515+
516+ it ( 'should deselect options from anchor on shift + click' , ( ) => {
517+ const { listbox, options} = getDefaultPatterns ( {
518+ multi : signal ( true ) ,
519+ selectionMode : signal ( 'explicit' ) ,
520+ } ) ;
521+ listbox . onPointerdown ( click ( options , 2 ) ) ;
522+ listbox . onPointerdown ( click ( options , 5 ) ) ;
523+ listbox . onPointerdown ( click ( options , 2 , { shift : true } ) ) ;
524+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
525+ } ) ;
526+ } ) ;
527+
528+ describe ( 'follows focus & multi select' , ( ) => {
529+ it ( 'should select a single option on click' , ( ) => {
530+ const { listbox, options} = getDefaultPatterns ( {
531+ multi : signal ( true ) ,
532+ selectionMode : signal ( 'follow' ) ,
533+ } ) ;
534+ listbox . onPointerdown ( click ( options , 0 ) ) ;
535+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' ] ) ;
536+ listbox . onPointerdown ( click ( options , 1 ) ) ;
537+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apricot' ] ) ;
538+ listbox . onPointerdown ( click ( options , 2 ) ) ;
539+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Banana' ] ) ;
540+ } ) ;
541+
542+ it ( 'should select an unselected option on ctrl + click' , ( ) => {
543+ const { listbox, options} = getDefaultPatterns ( {
544+ multi : signal ( true ) ,
545+ selectionMode : signal ( 'follow' ) ,
546+ } ) ;
547+ listbox . onPointerdown ( click ( options , 0 ) ) ;
548+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' ] ) ;
549+ listbox . onPointerdown ( click ( options , 1 , { control : true } ) ) ;
550+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' , 'Apricot' ] ) ;
551+ listbox . onPointerdown ( click ( options , 2 , { control : true } ) ) ;
552+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' , 'Apricot' , 'Banana' ] ) ;
553+ } ) ;
554+
555+ it ( 'should deselect a selected option on ctrl + click' , ( ) => {
556+ const { listbox, options} = getDefaultPatterns ( {
557+ multi : signal ( true ) ,
558+ selectionMode : signal ( 'follow' ) ,
559+ } ) ;
560+ listbox . onPointerdown ( click ( options , 0 ) ) ;
561+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Apple' ] ) ;
562+ listbox . onPointerdown ( click ( options , 0 , { control : true } ) ) ;
563+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
564+ } ) ;
565+
566+ it ( 'should select options from anchor on shift + click' , ( ) => {
567+ const { listbox, options} = getDefaultPatterns ( {
568+ multi : signal ( true ) ,
569+ selectionMode : signal ( 'follow' ) ,
570+ } ) ;
571+ listbox . onPointerdown ( click ( options , 2 ) ) ;
572+ listbox . onPointerdown ( click ( options , 5 , { shift : true } ) ) ;
573+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ 'Banana' , 'Blackberry' , 'Blueberry' , 'Cantaloupe' ] ) ;
574+ } ) ;
575+
576+ it ( 'should deselect options from anchor on shift + click' , ( ) => {
577+ const { listbox, options} = getDefaultPatterns ( {
578+ multi : signal ( true ) ,
579+ selectionMode : signal ( 'follow' ) ,
580+ } ) ;
581+ listbox . onPointerdown ( click ( options , 2 ) ) ;
582+ listbox . onPointerdown ( click ( options , 5 , { control : true } ) ) ;
583+ listbox . onPointerdown ( click ( options , 2 , { shift : true } ) ) ;
584+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
585+ } ) ;
586+ } ) ;
587+
588+ it ( 'should only navigate when readonly' , ( ) => {
589+ const { listbox, options} = getDefaultPatterns ( { readonly : signal ( true ) } ) ;
590+ listbox . onPointerdown ( click ( options , 0 ) ) ;
591+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
592+ listbox . onPointerdown ( click ( options , 1 ) ) ;
593+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
594+ listbox . onPointerdown ( click ( options , 2 ) ) ;
595+ expect ( listbox . inputs . value ( ) ) . toEqual ( [ ] ) ;
596+ } ) ;
597+ } ) ;
442598} ) ;
0 commit comments