@leafygreen-ui/combobox 12.0.2 → 12.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +14 -14
- package/src/Combobox/Combobox.chips.spec.tsx +50 -0
- package/src/Combobox/Combobox.keyboard.spec.tsx +589 -0
- package/src/Combobox/Combobox.mouse.spec.tsx +424 -0
- package/src/Combobox/Combobox.spec.tsx +1 -991
- package/src/Combobox.stories.tsx +1 -1
- package/src/ComboboxChip/ComboboxChip.stories.tsx +1 -1
- package/src/ComboboxGroup/ComboboxGroup.stories.tsx +1 -1
- package/src/ComboboxGroup/ComboboxGroup.styles.ts +1 -1
- package/src/ComboboxMenu/ComboboxMenu.stories.tsx +1 -1
- package/src/ComboboxOption/ComboboxOption.stories.tsx +1 -1
- package/src/ComboboxOption/ComboboxOption.styles.ts +1 -1
- package/src/types/index.ts +3 -3
- package/stories.js +2 -2
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/* eslint-disable jest/no-standalone-expect */
|
|
2
2
|
/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectSelection"] }] */
|
|
3
|
-
import
|
|
3
|
+
import { createRef } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
act,
|
|
6
|
-
fireEvent,
|
|
7
6
|
queryByText,
|
|
8
|
-
render,
|
|
9
7
|
waitFor,
|
|
10
8
|
waitForElementToBeRemoved,
|
|
11
9
|
} from '@testing-library/react';
|
|
@@ -14,15 +12,12 @@ import { axe } from 'jest-axe';
|
|
|
14
12
|
import flatten from 'lodash/flatten';
|
|
15
13
|
import isUndefined from 'lodash/isUndefined';
|
|
16
14
|
|
|
17
|
-
import Button from '@leafygreen-ui/button';
|
|
18
|
-
import { keyMap } from '@leafygreen-ui/lib';
|
|
19
15
|
import { RenderMode } from '@leafygreen-ui/popover';
|
|
20
16
|
import { eventContainingTargetValue } from '@leafygreen-ui/testing-lib';
|
|
21
17
|
|
|
22
18
|
import { OptionObject } from '../ComboboxOption/ComboboxOption.types';
|
|
23
19
|
import {
|
|
24
20
|
defaultOptions,
|
|
25
|
-
getComboboxJSX,
|
|
26
21
|
groupedOptions,
|
|
27
22
|
NestedObject,
|
|
28
23
|
renderCombobox,
|
|
@@ -584,942 +579,6 @@ describe('packages/combobox', () => {
|
|
|
584
579
|
});
|
|
585
580
|
});
|
|
586
581
|
|
|
587
|
-
/**
|
|
588
|
-
* Mouse interaction
|
|
589
|
-
*/
|
|
590
|
-
describe('Mouse interaction', () => {
|
|
591
|
-
test('Menu is not initially opened', () => {
|
|
592
|
-
const { getMenuElements } = renderCombobox(select);
|
|
593
|
-
const { menuContainerEl } = getMenuElements();
|
|
594
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
test('Clicking the combobox sets focus to the input', () => {
|
|
598
|
-
const { comboboxEl, inputEl } = renderCombobox(select);
|
|
599
|
-
userEvent.click(comboboxEl);
|
|
600
|
-
expect(inputEl).toHaveFocus();
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
test('Menu appears when box is clicked', () => {
|
|
604
|
-
const { comboboxEl, getMenuElements } = renderCombobox(select);
|
|
605
|
-
userEvent.click(comboboxEl);
|
|
606
|
-
const { menuContainerEl } = getMenuElements();
|
|
607
|
-
expect(menuContainerEl).not.toBeNull();
|
|
608
|
-
expect(menuContainerEl).toBeInTheDocument();
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
test('Clicking an option sets selection', () => {
|
|
612
|
-
const { openMenu, queryChipsByName, inputEl } = renderCombobox(select);
|
|
613
|
-
const { optionElements } = openMenu();
|
|
614
|
-
expect(optionElements).not.toBeUndefined();
|
|
615
|
-
const option3 = (optionElements as HTMLCollectionOf<HTMLLIElement>)[2];
|
|
616
|
-
act(() => {
|
|
617
|
-
userEvent.click(option3);
|
|
618
|
-
});
|
|
619
|
-
if (select === 'multiple') {
|
|
620
|
-
expect(queryChipsByName('Carrot')).toBeInTheDocument();
|
|
621
|
-
} else {
|
|
622
|
-
expect(inputEl).toHaveValue('Carrot');
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
test('Clicking an option fires onChange', () => {
|
|
627
|
-
const onChange = jest.fn();
|
|
628
|
-
const { openMenu } = renderCombobox(select, {
|
|
629
|
-
onChange,
|
|
630
|
-
});
|
|
631
|
-
const { optionElements } = openMenu();
|
|
632
|
-
expect(optionElements).not.toBeUndefined();
|
|
633
|
-
const option3 = (optionElements as HTMLCollectionOf<HTMLLIElement>)[2];
|
|
634
|
-
act(() => {
|
|
635
|
-
userEvent.click(option3);
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
if (select === 'multiple') {
|
|
639
|
-
expect(onChange).toHaveBeenCalledWith(
|
|
640
|
-
expect.arrayContaining(['carrot']),
|
|
641
|
-
expect.objectContaining({
|
|
642
|
-
diffType: 'insert',
|
|
643
|
-
value: 'carrot',
|
|
644
|
-
}),
|
|
645
|
-
);
|
|
646
|
-
} else {
|
|
647
|
-
expect(onChange).toHaveBeenCalledWith('carrot');
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
testSingleSelect('Clicking selected option closes menu', async () => {
|
|
652
|
-
const { openMenu } = renderCombobox(select, {
|
|
653
|
-
initialValue: 'apple',
|
|
654
|
-
});
|
|
655
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
656
|
-
expect(optionElements).not.toBeUndefined();
|
|
657
|
-
userEvent.click((optionElements as HTMLCollectionOf<HTMLLIElement>)[0]);
|
|
658
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
659
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
testMultiSelect(
|
|
663
|
-
'Clicking selected option toggles selection & does NOT close menu',
|
|
664
|
-
async () => {
|
|
665
|
-
const { openMenu, queryChipsByName } = renderCombobox(select, {
|
|
666
|
-
initialValue: ['apple'],
|
|
667
|
-
});
|
|
668
|
-
const selectedChip = queryChipsByName('Apple');
|
|
669
|
-
expect(selectedChip).toBeInTheDocument();
|
|
670
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
671
|
-
expect(optionElements).not.toBeUndefined();
|
|
672
|
-
|
|
673
|
-
userEvent.click(
|
|
674
|
-
(optionElements as HTMLCollectionOf<HTMLLIElement>)[0],
|
|
675
|
-
);
|
|
676
|
-
|
|
677
|
-
await waitFor(() => {
|
|
678
|
-
expect(selectedChip).not.toBeInTheDocument();
|
|
679
|
-
expect(menuContainerEl).toBeInTheDocument();
|
|
680
|
-
});
|
|
681
|
-
},
|
|
682
|
-
);
|
|
683
|
-
|
|
684
|
-
testSingleSelect('Clicking any option closes menu', async () => {
|
|
685
|
-
const { openMenu } = renderCombobox(select);
|
|
686
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
687
|
-
expect(optionElements).not.toBeUndefined();
|
|
688
|
-
userEvent.click((optionElements as HTMLCollectionOf<HTMLLIElement>)[1]);
|
|
689
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
690
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
testMultiSelect(
|
|
694
|
-
'Clicking any option toggles selection & does NOT close menu',
|
|
695
|
-
async () => {
|
|
696
|
-
const { openMenu, queryChipsByName } = renderCombobox(select);
|
|
697
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
698
|
-
expect(optionElements).not.toBeUndefined();
|
|
699
|
-
|
|
700
|
-
userEvent.click(
|
|
701
|
-
(optionElements as HTMLCollectionOf<HTMLLIElement>)[0],
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
await waitFor(() => {
|
|
705
|
-
const selectedChip = queryChipsByName('Apple');
|
|
706
|
-
expect(selectedChip).toBeInTheDocument();
|
|
707
|
-
expect(menuContainerEl).toBeInTheDocument();
|
|
708
|
-
});
|
|
709
|
-
},
|
|
710
|
-
);
|
|
711
|
-
|
|
712
|
-
testSingleSelect(
|
|
713
|
-
'Input returned to previous valid selection when menu closes',
|
|
714
|
-
() => {
|
|
715
|
-
const initialValue = 'apple';
|
|
716
|
-
const { inputEl } = renderCombobox(select, {
|
|
717
|
-
initialValue,
|
|
718
|
-
});
|
|
719
|
-
userEvent.type(inputEl, '{backspace}{backspace}{esc}');
|
|
720
|
-
expect(inputEl).toHaveValue('Apple');
|
|
721
|
-
},
|
|
722
|
-
);
|
|
723
|
-
|
|
724
|
-
testSingleSelect(
|
|
725
|
-
'Clicking after making a selection should re-open the menu',
|
|
726
|
-
async () => {
|
|
727
|
-
const { comboboxEl, inputEl, openMenu, getMenuElements } =
|
|
728
|
-
renderCombobox(select);
|
|
729
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
730
|
-
const firstOption = optionElements![0];
|
|
731
|
-
userEvent.click(firstOption);
|
|
732
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
733
|
-
userEvent.click(comboboxEl);
|
|
734
|
-
waitFor(() => {
|
|
735
|
-
const { menuContainerEl: newMenuContainerEl } = getMenuElements();
|
|
736
|
-
expect(newMenuContainerEl).not.toBeNull();
|
|
737
|
-
expect(newMenuContainerEl).toBeInTheDocument();
|
|
738
|
-
expect(inputEl).toHaveFocus();
|
|
739
|
-
});
|
|
740
|
-
},
|
|
741
|
-
);
|
|
742
|
-
|
|
743
|
-
test('Opening the menu when there is a selection should show all options', () => {
|
|
744
|
-
// See also: 'Pressing Down Arrow when there is a selection shows all menu options'
|
|
745
|
-
const initialValue = select === 'multiple' ? ['apple'] : 'apple';
|
|
746
|
-
const { comboboxEl, getMenuElements } = renderCombobox(select, {
|
|
747
|
-
initialValue,
|
|
748
|
-
});
|
|
749
|
-
userEvent.click(comboboxEl);
|
|
750
|
-
const { optionElements } = getMenuElements();
|
|
751
|
-
expect(optionElements).toHaveLength(defaultOptions.length);
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
test('First item is highlighted when re-opened after selection is made', async () => {
|
|
755
|
-
const initialValue = 'banana'; // Select an option that is not the first one
|
|
756
|
-
const { comboboxEl, getMenuElements } = renderCombobox('single', {
|
|
757
|
-
initialValue,
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
// Open the combobox
|
|
761
|
-
userEvent.click(comboboxEl);
|
|
762
|
-
let { optionElements } = getMenuElements();
|
|
763
|
-
expect(optionElements).toHaveLength(defaultOptions.length);
|
|
764
|
-
|
|
765
|
-
// Verify that the first item is highlighted
|
|
766
|
-
expect(
|
|
767
|
-
(optionElements as HTMLCollectionOf<HTMLLIElement>)[0],
|
|
768
|
-
).toHaveAttribute('aria-selected', 'true');
|
|
769
|
-
|
|
770
|
-
// Click the same option again to close the menu
|
|
771
|
-
userEvent.click(
|
|
772
|
-
(optionElements as HTMLCollectionOf<HTMLLIElement>)[1], // Click the second option
|
|
773
|
-
);
|
|
774
|
-
|
|
775
|
-
// Open the combobox again
|
|
776
|
-
userEvent.click(comboboxEl);
|
|
777
|
-
optionElements = getMenuElements().optionElements;
|
|
778
|
-
|
|
779
|
-
// Verify that the first item is highlighted again
|
|
780
|
-
expect(
|
|
781
|
-
(optionElements as HTMLCollectionOf<HTMLLIElement>)[0],
|
|
782
|
-
).toHaveAttribute('aria-selected', 'true');
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
describe('Clickaway', () => {
|
|
786
|
-
test('Menu closes on click-away', async () => {
|
|
787
|
-
const { containerEl, openMenu } = renderCombobox(select);
|
|
788
|
-
const { menuContainerEl } = openMenu();
|
|
789
|
-
userEvent.click(containerEl.parentElement!);
|
|
790
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
791
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
792
|
-
expect(containerEl).toContainFocus();
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
test("Other click handlers don't fire on click-away", async () => {
|
|
796
|
-
const buttonClickHandler = jest.fn();
|
|
797
|
-
const comboboxJSX = getComboboxJSX({
|
|
798
|
-
multiselect: select === 'multiple',
|
|
799
|
-
});
|
|
800
|
-
const renderResult = render(
|
|
801
|
-
<>
|
|
802
|
-
{comboboxJSX}
|
|
803
|
-
<Button onClick={buttonClickHandler}></Button>
|
|
804
|
-
</>,
|
|
805
|
-
);
|
|
806
|
-
|
|
807
|
-
const comboboxEl = renderResult.getByRole('combobox');
|
|
808
|
-
const buttonEl = renderResult.getByRole('button');
|
|
809
|
-
userEvent.click(comboboxEl); // Open menu
|
|
810
|
-
const menuContainerEl = renderResult.queryByRole('listbox');
|
|
811
|
-
userEvent.click(buttonEl); // Click button to close menu
|
|
812
|
-
await waitForElementToBeRemoved(menuContainerEl); // wait for menu to close
|
|
813
|
-
expect(buttonClickHandler).not.toHaveBeenCalled();
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
testSingleSelect(
|
|
817
|
-
'Clicking away should keep text if input is a valid value',
|
|
818
|
-
async () => {
|
|
819
|
-
const { inputEl, openMenu } = renderCombobox(select);
|
|
820
|
-
const { menuContainerEl } = openMenu();
|
|
821
|
-
userEvent.type(inputEl, 'Apple');
|
|
822
|
-
userEvent.click(document.body);
|
|
823
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
824
|
-
expect(inputEl).toHaveValue('Apple');
|
|
825
|
-
},
|
|
826
|
-
);
|
|
827
|
-
|
|
828
|
-
testSingleSelect(
|
|
829
|
-
'Clicking away should NOT keep text if input is not a valid value',
|
|
830
|
-
async () => {
|
|
831
|
-
const { inputEl, openMenu } = renderCombobox(select);
|
|
832
|
-
const { menuContainerEl } = openMenu();
|
|
833
|
-
userEvent.type(inputEl, 'abc');
|
|
834
|
-
userEvent.click(document.body);
|
|
835
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
836
|
-
expect(inputEl).toHaveValue('');
|
|
837
|
-
},
|
|
838
|
-
);
|
|
839
|
-
|
|
840
|
-
testMultiSelect('Clicking away should keep text as typed', async () => {
|
|
841
|
-
const { inputEl, openMenu } = renderCombobox(select);
|
|
842
|
-
const { menuContainerEl } = openMenu();
|
|
843
|
-
userEvent.type(inputEl, 'abc');
|
|
844
|
-
userEvent.click(document.body);
|
|
845
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
846
|
-
expect(inputEl).toHaveValue('abc');
|
|
847
|
-
});
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
describe('Click clear button', () => {
|
|
851
|
-
test('Clicking clear all button clears selection', async () => {
|
|
852
|
-
const initialValue =
|
|
853
|
-
select === 'single' ? 'apple' : ['apple', 'banana', 'carrot'];
|
|
854
|
-
const { inputEl, clearButtonEl, queryAllChips } = renderCombobox(
|
|
855
|
-
select,
|
|
856
|
-
{
|
|
857
|
-
initialValue,
|
|
858
|
-
},
|
|
859
|
-
);
|
|
860
|
-
expect(clearButtonEl).not.toBeNull();
|
|
861
|
-
act(() => {
|
|
862
|
-
userEvent.click(clearButtonEl!);
|
|
863
|
-
});
|
|
864
|
-
if (select === 'multiple') {
|
|
865
|
-
expect(queryAllChips()).toHaveLength(0);
|
|
866
|
-
} else {
|
|
867
|
-
await waitFor(() => expect(inputEl).toHaveValue(''));
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
describe('Clicking chips', () => {
|
|
873
|
-
testMultiSelect('Clicking chip X button removes option', async () => {
|
|
874
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
875
|
-
const { queryChipsByName, queryAllChips } = renderCombobox(select, {
|
|
876
|
-
initialValue,
|
|
877
|
-
});
|
|
878
|
-
const appleChip = queryChipsByName('Apple');
|
|
879
|
-
expect(appleChip).not.toBeNull();
|
|
880
|
-
const appleChipButton = appleChip!.querySelector('button')!;
|
|
881
|
-
userEvent.click(appleChipButton);
|
|
882
|
-
await waitFor(() => {
|
|
883
|
-
expect(appleChip).not.toBeInTheDocument();
|
|
884
|
-
const allChips = queryChipsByName(['Banana', 'Carrot']);
|
|
885
|
-
allChips?.forEach((chip: HTMLElement) =>
|
|
886
|
-
expect(chip).toBeInTheDocument(),
|
|
887
|
-
);
|
|
888
|
-
expect(queryAllChips()).toHaveLength(2);
|
|
889
|
-
});
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
testMultiSelect(
|
|
893
|
-
'Clicking chip X button fires onChange with diff',
|
|
894
|
-
async () => {
|
|
895
|
-
const onChange = jest.fn();
|
|
896
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
897
|
-
const { queryChipsByName } = renderCombobox(select, {
|
|
898
|
-
onChange,
|
|
899
|
-
initialValue,
|
|
900
|
-
});
|
|
901
|
-
const appleChip = queryChipsByName('Apple');
|
|
902
|
-
expect(appleChip).not.toBeNull();
|
|
903
|
-
const appleChipButton = appleChip!.querySelector('button')!;
|
|
904
|
-
userEvent.click(appleChipButton);
|
|
905
|
-
await waitFor(() => {
|
|
906
|
-
expect(appleChip).not.toBeInTheDocument();
|
|
907
|
-
expect(onChange).toHaveBeenCalledWith(
|
|
908
|
-
expect.arrayContaining(['banana', 'carrot']),
|
|
909
|
-
expect.objectContaining({
|
|
910
|
-
diffType: 'delete',
|
|
911
|
-
value: 'apple',
|
|
912
|
-
}),
|
|
913
|
-
);
|
|
914
|
-
});
|
|
915
|
-
},
|
|
916
|
-
);
|
|
917
|
-
|
|
918
|
-
testMultiSelect('Clicking chip text focuses the chip', () => {
|
|
919
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
920
|
-
const { queryChipsByName, queryAllChips } = renderCombobox(select, {
|
|
921
|
-
initialValue,
|
|
922
|
-
});
|
|
923
|
-
const appleChip = queryChipsByName('Apple');
|
|
924
|
-
userEvent.click(appleChip!);
|
|
925
|
-
expect(appleChip!).toContainFocus();
|
|
926
|
-
expect(queryAllChips()).toHaveLength(3);
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
testMultiSelect(
|
|
930
|
-
'Clicking chip X button does nothing when disabled',
|
|
931
|
-
async () => {
|
|
932
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
933
|
-
const { queryChipsByName, queryAllChips } = renderCombobox(select, {
|
|
934
|
-
initialValue,
|
|
935
|
-
disabled: true,
|
|
936
|
-
});
|
|
937
|
-
const carrotChip = queryChipsByName('Carrot');
|
|
938
|
-
const carrotChipButton = carrotChip!.querySelector('button');
|
|
939
|
-
expect(() => userEvent.click(carrotChipButton!)).toThrow();
|
|
940
|
-
await waitFor(() => {
|
|
941
|
-
expect(queryAllChips()).toHaveLength(3);
|
|
942
|
-
});
|
|
943
|
-
},
|
|
944
|
-
);
|
|
945
|
-
|
|
946
|
-
testMultiSelect(
|
|
947
|
-
'Removing a chip sets focus to the next chip',
|
|
948
|
-
async () => {
|
|
949
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
950
|
-
const { queryChipsByName } = renderCombobox(select, {
|
|
951
|
-
initialValue,
|
|
952
|
-
});
|
|
953
|
-
const appleChip = queryChipsByName('Apple');
|
|
954
|
-
const bananaChip = queryChipsByName('Banana');
|
|
955
|
-
const appleChipButton = appleChip!.querySelector('button');
|
|
956
|
-
const bananaChipButton = bananaChip!.querySelector('button');
|
|
957
|
-
userEvent.click(appleChipButton!);
|
|
958
|
-
await waitFor(() => {
|
|
959
|
-
expect(appleChip).not.toBeInTheDocument();
|
|
960
|
-
expect(bananaChipButton!).toHaveFocus();
|
|
961
|
-
});
|
|
962
|
-
},
|
|
963
|
-
);
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
test.todo(
|
|
967
|
-
'Clicking in the middle of the input text should set the cursor there',
|
|
968
|
-
);
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* Keyboard navigation
|
|
973
|
-
*/
|
|
974
|
-
describe('Keyboard interaction', () => {
|
|
975
|
-
test('First option is highlighted on menu open', () => {
|
|
976
|
-
const { openMenu } = renderCombobox(select);
|
|
977
|
-
const { optionElements } = openMenu();
|
|
978
|
-
expect(optionElements).not.toBeUndefined();
|
|
979
|
-
expect(
|
|
980
|
-
(optionElements as HTMLCollectionOf<HTMLLIElement>)[0],
|
|
981
|
-
).toHaveAttribute('aria-selected', 'true');
|
|
982
|
-
});
|
|
983
|
-
|
|
984
|
-
describe('Enter key', () => {
|
|
985
|
-
test('opens menu when input is focused', () => {
|
|
986
|
-
const { getMenuElements, inputEl } = renderCombobox(select);
|
|
987
|
-
userEvent.tab();
|
|
988
|
-
userEvent.type(inputEl!, '{enter}');
|
|
989
|
-
const { menuContainerEl } = getMenuElements();
|
|
990
|
-
expect(menuContainerEl).not.toBeNull();
|
|
991
|
-
expect(menuContainerEl).toBeInTheDocument();
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
test('does not make a selection when clicking enter on a closed menu', () => {
|
|
995
|
-
const { getMenuElements, inputEl } = renderCombobox(select);
|
|
996
|
-
userEvent.tab();
|
|
997
|
-
userEvent.keyboard('{enter}');
|
|
998
|
-
expect(inputEl).toHaveValue('');
|
|
999
|
-
const { menuContainerEl } = getMenuElements();
|
|
1000
|
-
expect(menuContainerEl).not.toBeNull();
|
|
1001
|
-
expect(menuContainerEl).toBeInTheDocument();
|
|
1002
|
-
expect(inputEl).toHaveValue('');
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
test('selects highlighted option', () => {
|
|
1006
|
-
const { inputEl, openMenu, queryChipsByName } =
|
|
1007
|
-
renderCombobox(select);
|
|
1008
|
-
openMenu();
|
|
1009
|
-
userEvent.type(inputEl!, '{arrowdown}{enter}');
|
|
1010
|
-
if (select === 'multiple') {
|
|
1011
|
-
expect(queryChipsByName('Banana')).toBeInTheDocument();
|
|
1012
|
-
} else {
|
|
1013
|
-
expect(inputEl).toHaveValue('Banana');
|
|
1014
|
-
}
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
test('fires onChange handler with payload', () => {
|
|
1018
|
-
const onChange = jest.fn();
|
|
1019
|
-
const { inputEl, openMenu } = renderCombobox(select, { onChange });
|
|
1020
|
-
openMenu();
|
|
1021
|
-
userEvent.type(inputEl!, '{arrowdown}{enter}');
|
|
1022
|
-
|
|
1023
|
-
if (select === 'multiple') {
|
|
1024
|
-
expect(onChange).toHaveBeenCalledWith(
|
|
1025
|
-
['banana'],
|
|
1026
|
-
expect.objectContaining({
|
|
1027
|
-
diffType: 'insert',
|
|
1028
|
-
value: 'banana',
|
|
1029
|
-
}),
|
|
1030
|
-
);
|
|
1031
|
-
} else {
|
|
1032
|
-
expect(onChange).toHaveBeenCalledWith('banana');
|
|
1033
|
-
}
|
|
1034
|
-
});
|
|
1035
|
-
|
|
1036
|
-
test('does not fire onClear handler', () => {
|
|
1037
|
-
const onClear = jest.fn();
|
|
1038
|
-
const { inputEl, openMenu } = renderCombobox(select, { onClear });
|
|
1039
|
-
openMenu();
|
|
1040
|
-
userEvent.type(inputEl!, '{arrowdown}{enter}');
|
|
1041
|
-
expect(onClear).not.toHaveBeenCalled();
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
|
-
testSingleSelect('Re-opens menu after making a selection', async () => {
|
|
1045
|
-
const { inputEl, openMenu, getMenuElements } =
|
|
1046
|
-
renderCombobox('single');
|
|
1047
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
1048
|
-
const firstOption = optionElements![0];
|
|
1049
|
-
userEvent.click(firstOption);
|
|
1050
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1051
|
-
userEvent.type(inputEl, '{emter}');
|
|
1052
|
-
await waitFor(() => {
|
|
1053
|
-
const { menuContainerEl: newMenuContainerEl } = getMenuElements();
|
|
1054
|
-
expect(newMenuContainerEl).not.toBeNull();
|
|
1055
|
-
expect(newMenuContainerEl).toBeInTheDocument();
|
|
1056
|
-
});
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
testMultiSelect('Removes Chip when one is focused', () => {
|
|
1060
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1061
|
-
const { comboboxEl, queryAllChips, queryChipsByName } =
|
|
1062
|
-
renderCombobox(select, {
|
|
1063
|
-
initialValue,
|
|
1064
|
-
});
|
|
1065
|
-
userEvent.type(comboboxEl, '{arrowleft}');
|
|
1066
|
-
const chip = queryChipsByName('Carrot');
|
|
1067
|
-
// Calling `userEvent.type` doesn't fire the necessary `keyDown` event
|
|
1068
|
-
fireEvent.keyDown(chip!, { key: keyMap.Enter });
|
|
1069
|
-
expect(queryAllChips()).toHaveLength(2);
|
|
1070
|
-
});
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
|
-
describe('Space key', () => {
|
|
1074
|
-
test('Types a space character', () => {
|
|
1075
|
-
const { inputEl, openMenu, queryAllChips } = renderCombobox(select);
|
|
1076
|
-
openMenu();
|
|
1077
|
-
userEvent.type(inputEl, 'a{space}fruit');
|
|
1078
|
-
expect(inputEl).toHaveValue('a fruit');
|
|
1079
|
-
if (select === 'multiple') {
|
|
1080
|
-
expect(queryAllChips()).toHaveLength(0);
|
|
1081
|
-
}
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
testMultiSelect('Removes Chip when one is focused', () => {
|
|
1085
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1086
|
-
const { comboboxEl, queryAllChips, queryChipsByName } =
|
|
1087
|
-
renderCombobox(select, {
|
|
1088
|
-
initialValue,
|
|
1089
|
-
});
|
|
1090
|
-
userEvent.type(comboboxEl, '{arrowleft}');
|
|
1091
|
-
const chip = queryChipsByName('Carrot');
|
|
1092
|
-
// Calling `userEvent.type` doesn't fire the necessary `keyDown` event
|
|
1093
|
-
fireEvent.keyDown(chip!, { key: keyMap.Space });
|
|
1094
|
-
waitFor(() => expect(queryAllChips()).toHaveLength(2));
|
|
1095
|
-
});
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
describe('Escape key', () => {
|
|
1099
|
-
test('Closes menu', async () => {
|
|
1100
|
-
const { inputEl, openMenu } = renderCombobox(select);
|
|
1101
|
-
const { menuContainerEl } = openMenu();
|
|
1102
|
-
userEvent.type(inputEl, '{esc}');
|
|
1103
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1104
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
1105
|
-
});
|
|
1106
|
-
test('Returns focus to the combobox', async () => {
|
|
1107
|
-
const { inputEl, openMenu } = renderCombobox(select);
|
|
1108
|
-
const { menuContainerEl } = openMenu();
|
|
1109
|
-
userEvent.type(inputEl, '{esc}');
|
|
1110
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1111
|
-
expect(inputEl).toContainFocus();
|
|
1112
|
-
});
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
describe('Tab key', () => {
|
|
1116
|
-
test('Focuses combobox but does not open menu', () => {
|
|
1117
|
-
const { getMenuElements, inputEl } = renderCombobox(select);
|
|
1118
|
-
userEvent.tab();
|
|
1119
|
-
expect(inputEl).toHaveFocus();
|
|
1120
|
-
const { menuContainerEl } = getMenuElements();
|
|
1121
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
1122
|
-
});
|
|
1123
|
-
|
|
1124
|
-
test('Closes menu when no selection is made', async () => {
|
|
1125
|
-
const { openMenu } = renderCombobox(select);
|
|
1126
|
-
const { menuContainerEl } = openMenu();
|
|
1127
|
-
userEvent.tab();
|
|
1128
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1129
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
test('Focuses clear button when it exists', async () => {
|
|
1133
|
-
const initialValue = select === 'multiple' ? ['apple'] : 'apple';
|
|
1134
|
-
const { clearButtonEl, openMenu } = renderCombobox(select, {
|
|
1135
|
-
initialValue,
|
|
1136
|
-
});
|
|
1137
|
-
openMenu();
|
|
1138
|
-
userEvent.tab();
|
|
1139
|
-
expect(clearButtonEl).toHaveFocus();
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
testMultiSelect('Focuses next Chip when a Chip is selected', () => {
|
|
1143
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1144
|
-
const { queryAllChips } = renderCombobox(select, { initialValue });
|
|
1145
|
-
const [firstChip, secondChip] = queryAllChips();
|
|
1146
|
-
userEvent.click(firstChip);
|
|
1147
|
-
userEvent.tab();
|
|
1148
|
-
expect(secondChip).toContainFocus();
|
|
1149
|
-
});
|
|
1150
|
-
|
|
1151
|
-
testMultiSelect('Focuses input when the last Chip is selected', () => {
|
|
1152
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1153
|
-
const { inputEl, queryChipsByIndex } = renderCombobox(select, {
|
|
1154
|
-
initialValue,
|
|
1155
|
-
});
|
|
1156
|
-
const lastChip = queryChipsByIndex('last');
|
|
1157
|
-
userEvent.click(lastChip!);
|
|
1158
|
-
userEvent.tab();
|
|
1159
|
-
expect(inputEl).toHaveFocus();
|
|
1160
|
-
});
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
describe('Backspace key', () => {
|
|
1164
|
-
test('Deletes text when cursor is NOT at beginning of selection', async () => {
|
|
1165
|
-
const { inputEl } = renderCombobox(select);
|
|
1166
|
-
await userEvent.type(inputEl, 'app{backspace}');
|
|
1167
|
-
expect(inputEl).toHaveFocus();
|
|
1168
|
-
expect(inputEl).toHaveValue('ap');
|
|
1169
|
-
});
|
|
1170
|
-
|
|
1171
|
-
testSingleSelect(
|
|
1172
|
-
'Deletes text after making a single selection',
|
|
1173
|
-
async () => {
|
|
1174
|
-
const { inputEl, openMenu } = renderCombobox('single');
|
|
1175
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
1176
|
-
const firstOption = optionElements![0];
|
|
1177
|
-
userEvent.click(firstOption);
|
|
1178
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1179
|
-
userEvent.type(inputEl, '{backspace}');
|
|
1180
|
-
expect(inputEl).toHaveFocus();
|
|
1181
|
-
expect(inputEl).toHaveValue('Appl');
|
|
1182
|
-
},
|
|
1183
|
-
);
|
|
1184
|
-
|
|
1185
|
-
testSingleSelect('Re-opens menu after making a selection', async () => {
|
|
1186
|
-
const { inputEl, openMenu, getMenuElements } =
|
|
1187
|
-
renderCombobox('single');
|
|
1188
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
1189
|
-
const firstOption = optionElements![0];
|
|
1190
|
-
userEvent.click(firstOption);
|
|
1191
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1192
|
-
userEvent.type(inputEl, '{backspace}');
|
|
1193
|
-
await waitFor(() => {
|
|
1194
|
-
const { menuContainerEl: newMenuContainerEl } = getMenuElements();
|
|
1195
|
-
expect(newMenuContainerEl).not.toBeNull();
|
|
1196
|
-
expect(newMenuContainerEl).toBeInTheDocument();
|
|
1197
|
-
});
|
|
1198
|
-
});
|
|
1199
|
-
|
|
1200
|
-
testMultiSelect(
|
|
1201
|
-
'Focuses last chip when cursor is at beginning of selection',
|
|
1202
|
-
() => {
|
|
1203
|
-
const initialValue = ['apple'];
|
|
1204
|
-
const { inputEl, queryAllChips } = renderCombobox(select, {
|
|
1205
|
-
initialValue,
|
|
1206
|
-
});
|
|
1207
|
-
userEvent.type(inputEl, '{backspace}');
|
|
1208
|
-
expect(queryAllChips()).toHaveLength(1);
|
|
1209
|
-
expect(queryAllChips()[0]).toContainFocus();
|
|
1210
|
-
},
|
|
1211
|
-
);
|
|
1212
|
-
|
|
1213
|
-
testMultiSelect('Focuses last Chip after making a selection', () => {
|
|
1214
|
-
const { inputEl, openMenu, queryAllChips } = renderCombobox(select);
|
|
1215
|
-
const { optionElements } = openMenu();
|
|
1216
|
-
const firstOption = optionElements![0];
|
|
1217
|
-
userEvent.click(firstOption);
|
|
1218
|
-
userEvent.type(inputEl, '{backspace}');
|
|
1219
|
-
expect(queryAllChips()).toHaveLength(1);
|
|
1220
|
-
expect(queryAllChips()[0]).toContainFocus();
|
|
1221
|
-
});
|
|
1222
|
-
|
|
1223
|
-
testMultiSelect('Removes Chip when one is focused', async () => {
|
|
1224
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1225
|
-
const { comboboxEl, queryAllChips, queryChipsByIndex } =
|
|
1226
|
-
renderCombobox(select, {
|
|
1227
|
-
initialValue,
|
|
1228
|
-
});
|
|
1229
|
-
userEvent.type(comboboxEl, '{arrowleft}');
|
|
1230
|
-
const lastChip = queryChipsByIndex(2);
|
|
1231
|
-
// Calling `userEvent.type` doesn't fire the necessary `keyDown` event
|
|
1232
|
-
fireEvent.keyDown(lastChip!, { key: keyMap.Backspace });
|
|
1233
|
-
expect(queryAllChips()).toHaveLength(2);
|
|
1234
|
-
});
|
|
1235
|
-
|
|
1236
|
-
testMultiSelect('Focuses input when last chip is removed', () => {
|
|
1237
|
-
const initialValue = ['apple', 'banana'];
|
|
1238
|
-
const { comboboxEl, inputEl, queryChipsByIndex } = renderCombobox(
|
|
1239
|
-
select,
|
|
1240
|
-
{ initialValue },
|
|
1241
|
-
);
|
|
1242
|
-
userEvent.type(comboboxEl, '{arrowleft}');
|
|
1243
|
-
const lastChip = queryChipsByIndex(1);
|
|
1244
|
-
fireEvent.keyDown(lastChip!, { key: keyMap.Backspace });
|
|
1245
|
-
expect(inputEl).toHaveFocus();
|
|
1246
|
-
});
|
|
1247
|
-
|
|
1248
|
-
testMultiSelect(
|
|
1249
|
-
'Focuses next chip when an inner chip is removed',
|
|
1250
|
-
() => {
|
|
1251
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1252
|
-
const { comboboxEl, queryChipsByIndex } = renderCombobox(select, {
|
|
1253
|
-
initialValue,
|
|
1254
|
-
});
|
|
1255
|
-
userEvent.type(comboboxEl, '{arrowleft}');
|
|
1256
|
-
const appleChip = queryChipsByIndex(0);
|
|
1257
|
-
const bananaChip = queryChipsByIndex(1);
|
|
1258
|
-
fireEvent.keyDown(appleChip!, { key: keyMap.Backspace });
|
|
1259
|
-
expect(bananaChip).toContainFocus();
|
|
1260
|
-
},
|
|
1261
|
-
);
|
|
1262
|
-
});
|
|
1263
|
-
|
|
1264
|
-
describe('Up & Down arrow keys', () => {
|
|
1265
|
-
test('Down arrow moves highlight down', async () => {
|
|
1266
|
-
const { inputEl, openMenu, findByRole } = renderCombobox(select);
|
|
1267
|
-
openMenu();
|
|
1268
|
-
userEvent.type(inputEl, '{arrowdown}');
|
|
1269
|
-
const highlight = await findByRole('option', {
|
|
1270
|
-
selected: true,
|
|
1271
|
-
});
|
|
1272
|
-
expect(highlight).toHaveTextContent('Banana');
|
|
1273
|
-
});
|
|
1274
|
-
|
|
1275
|
-
test('Up arrow moves highlight up', async () => {
|
|
1276
|
-
const { inputEl, openMenu, findByRole } = renderCombobox(select);
|
|
1277
|
-
openMenu();
|
|
1278
|
-
userEvent.type(inputEl, '{arrowdown}{arrowdown}{arrowup}');
|
|
1279
|
-
const highlight = await findByRole('option', {
|
|
1280
|
-
selected: true,
|
|
1281
|
-
});
|
|
1282
|
-
expect(highlight).toHaveTextContent('Banana');
|
|
1283
|
-
});
|
|
1284
|
-
|
|
1285
|
-
test.todo('Down arrow cycles highlight to top');
|
|
1286
|
-
|
|
1287
|
-
test.todo('Up arrow cycles highlight to bottom');
|
|
1288
|
-
|
|
1289
|
-
test('Down arrow key opens menu when its closed', async () => {
|
|
1290
|
-
const { inputEl, openMenu, findByRole } = renderCombobox(select);
|
|
1291
|
-
const { menuContainerEl } = openMenu();
|
|
1292
|
-
expect(inputEl).toHaveFocus();
|
|
1293
|
-
userEvent.type(inputEl, '{esc}');
|
|
1294
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1295
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
1296
|
-
userEvent.type(inputEl, '{arrowdown}');
|
|
1297
|
-
const reOpenedMenu = await findByRole('listbox');
|
|
1298
|
-
expect(reOpenedMenu).toBeInTheDocument();
|
|
1299
|
-
});
|
|
1300
|
-
|
|
1301
|
-
test('Pressing Down Arrow when there is a selection shows all menu options', () => {
|
|
1302
|
-
// See also: 'Opening the menu when there is a selection should show all options'
|
|
1303
|
-
const initialValue = select === 'multiple' ? ['apple'] : 'apple';
|
|
1304
|
-
const { inputEl, getMenuElements } = renderCombobox(select, {
|
|
1305
|
-
initialValue,
|
|
1306
|
-
});
|
|
1307
|
-
// First pressing escape to ensure the menu is closed
|
|
1308
|
-
userEvent.type(inputEl, '{esc}{arrowdown}');
|
|
1309
|
-
const { optionElements } = getMenuElements();
|
|
1310
|
-
expect(optionElements).toHaveLength(defaultOptions.length);
|
|
1311
|
-
expect(optionElements![0]).toHaveAttribute('aria-selected', 'true');
|
|
1312
|
-
});
|
|
1313
|
-
|
|
1314
|
-
test('Pressing Up Arrow when there is a selection shows all menu options', () => {
|
|
1315
|
-
// See also: 'Opening the menu when there is a selection should show all options'
|
|
1316
|
-
const initialValue = select === 'multiple' ? ['apple'] : 'apple';
|
|
1317
|
-
const { inputEl, getMenuElements } = renderCombobox(select, {
|
|
1318
|
-
initialValue,
|
|
1319
|
-
});
|
|
1320
|
-
// First pressing escape to ensure the menu is closed
|
|
1321
|
-
userEvent.type(inputEl, '{esc}{arrowup}');
|
|
1322
|
-
const { optionElements } = getMenuElements();
|
|
1323
|
-
expect(optionElements).toHaveLength(defaultOptions.length);
|
|
1324
|
-
expect(optionElements![0]).toHaveAttribute('aria-selected', 'true');
|
|
1325
|
-
});
|
|
1326
|
-
});
|
|
1327
|
-
|
|
1328
|
-
describe('Left arrow key', () => {
|
|
1329
|
-
testMultiSelect(
|
|
1330
|
-
'When cursor is at the beginning of input, Left arrow focuses last chip',
|
|
1331
|
-
() => {
|
|
1332
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1333
|
-
const { queryChipsByIndex, inputEl } = renderCombobox(select, {
|
|
1334
|
-
initialValue,
|
|
1335
|
-
});
|
|
1336
|
-
userEvent.type(inputEl, '{arrowleft}');
|
|
1337
|
-
const lastChip = queryChipsByIndex('last');
|
|
1338
|
-
expect(lastChip).toContainFocus();
|
|
1339
|
-
},
|
|
1340
|
-
);
|
|
1341
|
-
testSingleSelect(
|
|
1342
|
-
'When cursor is at the beginning of input, Left arrow does nothing',
|
|
1343
|
-
async () => {
|
|
1344
|
-
const { inputEl } = renderCombobox(select);
|
|
1345
|
-
userEvent.type(inputEl, '{arrowleft}');
|
|
1346
|
-
await waitFor(() => expect(inputEl).toHaveFocus());
|
|
1347
|
-
},
|
|
1348
|
-
);
|
|
1349
|
-
test('If cursor is NOT at the beginning of input, Left arrow key moves cursor', async () => {
|
|
1350
|
-
const { inputEl } = renderCombobox(select);
|
|
1351
|
-
userEvent.type(inputEl, 'abc{arrowleft}');
|
|
1352
|
-
await waitFor(() => expect(inputEl).toHaveFocus());
|
|
1353
|
-
});
|
|
1354
|
-
|
|
1355
|
-
test('When focus is on clear button, Left arrow moves focus to input', async () => {
|
|
1356
|
-
const initialValue = select === 'multiple' ? ['apple'] : 'apple';
|
|
1357
|
-
const { inputEl } = renderCombobox(select, {
|
|
1358
|
-
initialValue,
|
|
1359
|
-
});
|
|
1360
|
-
userEvent.type(inputEl!, '{arrowright}{arrowleft}');
|
|
1361
|
-
expect(inputEl!).toHaveFocus();
|
|
1362
|
-
expect(inputEl!.selectionEnd).toEqual(select === 'multiple' ? 0 : 5);
|
|
1363
|
-
});
|
|
1364
|
-
|
|
1365
|
-
testMultiSelect(
|
|
1366
|
-
'When focus is on a chip, Left arrow focuses prev chip',
|
|
1367
|
-
() => {
|
|
1368
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1369
|
-
const { queryChipsByIndex, inputEl } = renderCombobox(select, {
|
|
1370
|
-
initialValue,
|
|
1371
|
-
});
|
|
1372
|
-
userEvent.type(inputEl, '{arrowleft}{arrowleft}');
|
|
1373
|
-
const secondChip = queryChipsByIndex(1);
|
|
1374
|
-
expect(secondChip).toContainFocus();
|
|
1375
|
-
},
|
|
1376
|
-
);
|
|
1377
|
-
testMultiSelect(
|
|
1378
|
-
'When focus is on the first chip, Left arrrow does nothing',
|
|
1379
|
-
() => {
|
|
1380
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1381
|
-
const { queryAllChips, inputEl } = renderCombobox(select, {
|
|
1382
|
-
initialValue,
|
|
1383
|
-
});
|
|
1384
|
-
const [firstChip] = queryAllChips();
|
|
1385
|
-
userEvent.type(
|
|
1386
|
-
inputEl,
|
|
1387
|
-
'{arrowleft}{arrowleft}{arrowleft}{arrowleft}',
|
|
1388
|
-
);
|
|
1389
|
-
expect(firstChip).toContainFocus();
|
|
1390
|
-
},
|
|
1391
|
-
);
|
|
1392
|
-
});
|
|
1393
|
-
|
|
1394
|
-
describe('Right arrow key', () => {
|
|
1395
|
-
test('Does nothing when focus is on clear button', () => {
|
|
1396
|
-
const initialValue =
|
|
1397
|
-
select === 'multiple' ? ['apple', 'banana', 'carrot'] : 'apple';
|
|
1398
|
-
const { inputEl, clearButtonEl } = renderCombobox(select, {
|
|
1399
|
-
initialValue,
|
|
1400
|
-
});
|
|
1401
|
-
userEvent.type(inputEl, '{arrowright}{arrowright}');
|
|
1402
|
-
expect(clearButtonEl).toHaveFocus();
|
|
1403
|
-
});
|
|
1404
|
-
|
|
1405
|
-
test('Focuses clear button when cursor is at the end of input', () => {
|
|
1406
|
-
const initialValue =
|
|
1407
|
-
select === 'multiple' ? ['apple', 'banana', 'carrot'] : 'apple';
|
|
1408
|
-
const { inputEl, clearButtonEl } = renderCombobox(select, {
|
|
1409
|
-
initialValue,
|
|
1410
|
-
});
|
|
1411
|
-
userEvent.type(inputEl, '{arrowright}');
|
|
1412
|
-
expect(clearButtonEl).toHaveFocus();
|
|
1413
|
-
});
|
|
1414
|
-
|
|
1415
|
-
test('Moves cursor when cursor is NOT at the end of input', () => {
|
|
1416
|
-
const initialValue =
|
|
1417
|
-
select === 'multiple' ? ['apple', 'banana', 'carrot'] : 'apple';
|
|
1418
|
-
const { inputEl } = renderCombobox(select, {
|
|
1419
|
-
initialValue,
|
|
1420
|
-
});
|
|
1421
|
-
userEvent.type(inputEl, 'abc{arrowleft}{arrowright}');
|
|
1422
|
-
expect(inputEl).toHaveFocus();
|
|
1423
|
-
});
|
|
1424
|
-
|
|
1425
|
-
// FIXME: act warning
|
|
1426
|
-
testMultiSelect('Focuses input when focus is on last chip', () => {
|
|
1427
|
-
const initialValue = ['apple', 'banana'];
|
|
1428
|
-
const { inputEl } = renderCombobox(select, {
|
|
1429
|
-
initialValue,
|
|
1430
|
-
});
|
|
1431
|
-
userEvent.type(
|
|
1432
|
-
inputEl!,
|
|
1433
|
-
'abc{arrowleft}{arrowleft}{arrowleft}{arrowleft}{arrowright}',
|
|
1434
|
-
);
|
|
1435
|
-
expect(inputEl!).toHaveFocus();
|
|
1436
|
-
// This behavior passes in the browser, but not in jest
|
|
1437
|
-
// expect(inputEl!.selectionStart).toEqual(0);
|
|
1438
|
-
});
|
|
1439
|
-
|
|
1440
|
-
// FIXME: act warning
|
|
1441
|
-
testMultiSelect('Focuses input when focus is on only chip', () => {
|
|
1442
|
-
const initialValue = ['apple'];
|
|
1443
|
-
const { inputEl } = renderCombobox(select, {
|
|
1444
|
-
initialValue,
|
|
1445
|
-
});
|
|
1446
|
-
userEvent.type(
|
|
1447
|
-
inputEl!,
|
|
1448
|
-
'abc{arrowleft}{arrowleft}{arrowleft}{arrowleft}{arrowright}',
|
|
1449
|
-
);
|
|
1450
|
-
expect(inputEl!).toHaveFocus();
|
|
1451
|
-
// expect(inputEl!.selectionStart).toEqual(0);
|
|
1452
|
-
});
|
|
1453
|
-
|
|
1454
|
-
// FIXME: act warning
|
|
1455
|
-
testMultiSelect(
|
|
1456
|
-
'Focuses next chip when focus is on an inner chip',
|
|
1457
|
-
() => {
|
|
1458
|
-
const initialValue = ['apple', 'banana', 'carrot'];
|
|
1459
|
-
const { inputEl, queryChipsByIndex } = renderCombobox(select, {
|
|
1460
|
-
initialValue,
|
|
1461
|
-
});
|
|
1462
|
-
userEvent.type(inputEl!, '{arrowleft}{arrowleft}{arrowright}');
|
|
1463
|
-
const lastChip = queryChipsByIndex('last');
|
|
1464
|
-
expect(lastChip!).toContainFocus();
|
|
1465
|
-
},
|
|
1466
|
-
);
|
|
1467
|
-
});
|
|
1468
|
-
|
|
1469
|
-
describe('Any other key', () => {
|
|
1470
|
-
test('Updates the value of the input', () => {
|
|
1471
|
-
const { inputEl } = renderCombobox(select);
|
|
1472
|
-
userEvent.type(inputEl, 'z');
|
|
1473
|
-
expect(inputEl).toHaveValue('z');
|
|
1474
|
-
});
|
|
1475
|
-
|
|
1476
|
-
test('Updates the input when options are highlighted', () => {
|
|
1477
|
-
const { inputEl, openMenu } = renderCombobox(select);
|
|
1478
|
-
openMenu();
|
|
1479
|
-
userEvent.type(inputEl, '{arrowdown}z');
|
|
1480
|
-
expect(inputEl).toHaveValue('z');
|
|
1481
|
-
});
|
|
1482
|
-
|
|
1483
|
-
test("Opens the menu if it's closed", async () => {
|
|
1484
|
-
const { inputEl, openMenu, getMenuElements } = renderCombobox(select);
|
|
1485
|
-
const { menuContainerEl } = openMenu();
|
|
1486
|
-
userEvent.type(inputEl, '{esc}');
|
|
1487
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1488
|
-
expect(menuContainerEl).not.toBeInTheDocument();
|
|
1489
|
-
userEvent.type(inputEl, 'a');
|
|
1490
|
-
await waitFor(() => {
|
|
1491
|
-
const { menuContainerEl: newMenuContainerEl } = getMenuElements();
|
|
1492
|
-
expect(newMenuContainerEl).toBeInTheDocument();
|
|
1493
|
-
});
|
|
1494
|
-
});
|
|
1495
|
-
|
|
1496
|
-
testSingleSelect(
|
|
1497
|
-
'Opens the menu after making a selection',
|
|
1498
|
-
async () => {
|
|
1499
|
-
const { inputEl, openMenu, getMenuElements } =
|
|
1500
|
-
renderCombobox(select);
|
|
1501
|
-
const { optionElements, menuContainerEl } = openMenu();
|
|
1502
|
-
const firstOption = optionElements![0];
|
|
1503
|
-
userEvent.click(firstOption);
|
|
1504
|
-
await waitForElementToBeRemoved(menuContainerEl);
|
|
1505
|
-
userEvent.type(inputEl, 'a');
|
|
1506
|
-
await waitFor(() => {
|
|
1507
|
-
const { menuContainerEl: newMenuContainerEl } = getMenuElements();
|
|
1508
|
-
expect(newMenuContainerEl).toBeInTheDocument();
|
|
1509
|
-
});
|
|
1510
|
-
},
|
|
1511
|
-
);
|
|
1512
|
-
|
|
1513
|
-
test('Filters the menu options', () => {
|
|
1514
|
-
// Using default options
|
|
1515
|
-
const { inputEl, getMenuElements } = renderCombobox(select);
|
|
1516
|
-
userEvent.type(inputEl, 'c');
|
|
1517
|
-
const { optionElements } = getMenuElements();
|
|
1518
|
-
expect(optionElements).toHaveLength(1); // carrot
|
|
1519
|
-
});
|
|
1520
|
-
});
|
|
1521
|
-
});
|
|
1522
|
-
|
|
1523
582
|
describe('Programmatic interaction', () => {
|
|
1524
583
|
test('Menu does not open when input is focused programmatically', () => {
|
|
1525
584
|
const { inputEl, getMenuElements } = renderCombobox(select);
|
|
@@ -1763,53 +822,4 @@ describe('packages/combobox', () => {
|
|
|
1763
822
|
});
|
|
1764
823
|
});
|
|
1765
824
|
});
|
|
1766
|
-
|
|
1767
|
-
describe('Chips', () => {
|
|
1768
|
-
const ellipsis = '…';
|
|
1769
|
-
const options = [
|
|
1770
|
-
'loremipsumdolor',
|
|
1771
|
-
'sitametconsectetur',
|
|
1772
|
-
'anotherlongoption',
|
|
1773
|
-
];
|
|
1774
|
-
|
|
1775
|
-
test('Chips truncate at the beginning', () => {
|
|
1776
|
-
const { queryAllChips } = renderCombobox('multiple', {
|
|
1777
|
-
options,
|
|
1778
|
-
initialValue: ['loremipsumdolor'],
|
|
1779
|
-
chipTruncationLocation: 'start',
|
|
1780
|
-
});
|
|
1781
|
-
const firstChipEl = queryAllChips()[0];
|
|
1782
|
-
expect(firstChipEl).toHaveTextContent(ellipsis + 'psumdolor');
|
|
1783
|
-
});
|
|
1784
|
-
|
|
1785
|
-
test('Chips truncate in the middle', () => {
|
|
1786
|
-
const { queryAllChips } = renderCombobox('multiple', {
|
|
1787
|
-
options,
|
|
1788
|
-
initialValue: ['loremipsumdolor'],
|
|
1789
|
-
chipTruncationLocation: 'middle',
|
|
1790
|
-
});
|
|
1791
|
-
const [firstChipEl] = queryAllChips();
|
|
1792
|
-
expect(firstChipEl).toHaveTextContent('lore' + ellipsis + 'dolor');
|
|
1793
|
-
});
|
|
1794
|
-
test('Chips truncate at the end', () => {
|
|
1795
|
-
const { queryAllChips } = renderCombobox('multiple', {
|
|
1796
|
-
options,
|
|
1797
|
-
initialValue: ['loremipsumdolor'],
|
|
1798
|
-
chipTruncationLocation: 'end',
|
|
1799
|
-
});
|
|
1800
|
-
const [firstChipEl] = queryAllChips();
|
|
1801
|
-
expect(firstChipEl).toHaveTextContent('loremipsu' + ellipsis);
|
|
1802
|
-
});
|
|
1803
|
-
|
|
1804
|
-
test('Chips truncate to the provided length', () => {
|
|
1805
|
-
const { queryAllChips } = renderCombobox('multiple', {
|
|
1806
|
-
options,
|
|
1807
|
-
initialValue: ['loremipsumdolor'],
|
|
1808
|
-
chipTruncationLocation: 'start',
|
|
1809
|
-
chipCharacterLimit: 8,
|
|
1810
|
-
});
|
|
1811
|
-
const [firstChipEl] = queryAllChips();
|
|
1812
|
-
expect(firstChipEl).toHaveTextContent(ellipsis + 'dolor');
|
|
1813
|
-
});
|
|
1814
|
-
});
|
|
1815
825
|
});
|