Skip to content

Commit 4c6142b

Browse files
thejacksheltonwmertens
authored andcommitted
test(combobox): tests for disabled behavior, and larger test suite
1 parent 4b4ae1b commit 4c6142b

File tree

1 file changed

+282
-100
lines changed

1 file changed

+282
-100
lines changed

packages/kit-headless/src/components/combobox/combobox.spec.tsx

Lines changed: 282 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,24 @@ describe('Critical Functionality', () => {
173173

174174
cy.findByRole('listbox').should('not.exist');
175175
});
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+
});
176194
});
177195

178196
describe('Default Label', () => {
@@ -319,7 +337,7 @@ describe('Keyboard Navigation', () => {
319337
cy.get('li').filter(':visible').first().should('have.attr', 'aria-selected', 'true');
320338
});
321339

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
323341
WHEN the down arrow key is pressed,
324342
THEN the 1st filtered option in the listbox should be selected.`, () => {
325343
cy.mount(<StringCombobox />);
@@ -348,102 +366,266 @@ describe('Keyboard Navigation', () => {
348366
});
349367
});
350368

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

Comments
 (0)