@khanacademy/wonder-blocks-dropdown 5.3.8 → 5.4.0
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 +38 -0
- package/dist/components/action-menu.d.ts +15 -2
- package/dist/components/dropdown-core.d.ts +4 -0
- package/dist/components/dropdown-opener.d.ts +4 -0
- package/dist/components/multi-select.d.ts +9 -2
- package/dist/components/option-item.d.ts +1 -1
- package/dist/components/single-select.d.ts +9 -2
- package/dist/es/index.js +114 -61
- package/dist/hooks/use-listbox.d.ts +2 -2
- package/dist/index.js +113 -60
- package/package.json +6 -6
- package/src/components/__tests__/action-menu.test.tsx +630 -23
- package/src/components/__tests__/multi-select.test.tsx +293 -0
- package/src/components/__tests__/single-select.test.tsx +306 -0
- package/src/components/action-menu.tsx +85 -48
- package/src/components/dropdown-core.tsx +9 -2
- package/src/components/dropdown-opener.tsx +17 -1
- package/src/components/multi-select.tsx +94 -61
- package/src/components/option-item.tsx +1 -1
- package/src/components/select-opener.tsx +2 -2
- package/src/components/single-select.tsx +87 -57
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import {render, screen} from "@testing-library/react";
|
|
3
4
|
import {PointerEventsCheckLevel, userEvent} from "@testing-library/user-event";
|
|
@@ -145,29 +146,6 @@ describe("ActionMenu", () => {
|
|
|
145
146
|
expect(screen.queryByRole("menu")).not.toBeInTheDocument();
|
|
146
147
|
});
|
|
147
148
|
|
|
148
|
-
it("updates the aria-expanded value when opening", async () => {
|
|
149
|
-
// Arrange
|
|
150
|
-
render(
|
|
151
|
-
<ActionMenu
|
|
152
|
-
menuText={"Action menu!"}
|
|
153
|
-
testId="openTest"
|
|
154
|
-
onChange={onChange}
|
|
155
|
-
selectedValues={[]}
|
|
156
|
-
>
|
|
157
|
-
<ActionItem label="Action" onClick={onClick} />
|
|
158
|
-
<SeparatorItem />
|
|
159
|
-
<OptionItem label="Toggle" value="toggle" onClick={onToggle} />
|
|
160
|
-
</ActionMenu>,
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
// Act
|
|
164
|
-
const opener = await screen.findByRole("button");
|
|
165
|
-
await userEvent.click(opener);
|
|
166
|
-
|
|
167
|
-
// Assert
|
|
168
|
-
expect(opener).toHaveAttribute("aria-expanded", "true");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
149
|
it("triggers actions", async () => {
|
|
172
150
|
// Arrange
|
|
173
151
|
const onChange = jest.fn();
|
|
@@ -572,4 +550,633 @@ describe("ActionMenu", () => {
|
|
|
572
550
|
expect(opener).toHaveTextContent("Action menu!");
|
|
573
551
|
});
|
|
574
552
|
});
|
|
553
|
+
|
|
554
|
+
describe("With OptionItems", () => {
|
|
555
|
+
it("Should render option items with `role=menuitemcheckbox`", async () => {
|
|
556
|
+
// Arrange
|
|
557
|
+
render(
|
|
558
|
+
<ActionMenu
|
|
559
|
+
menuText="Action menu!"
|
|
560
|
+
testId="openTest"
|
|
561
|
+
onChange={onChange}
|
|
562
|
+
>
|
|
563
|
+
<OptionItem
|
|
564
|
+
label="Toggle A"
|
|
565
|
+
value="toggle-a"
|
|
566
|
+
testId="toggle-a"
|
|
567
|
+
/>
|
|
568
|
+
<OptionItem
|
|
569
|
+
label="Toggle B"
|
|
570
|
+
value="toggle-b"
|
|
571
|
+
testId="toggle-b"
|
|
572
|
+
/>
|
|
573
|
+
</ActionMenu>,
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
// Act
|
|
577
|
+
// open the menu
|
|
578
|
+
const opener = await screen.findByRole("button");
|
|
579
|
+
await userEvent.click(opener);
|
|
580
|
+
|
|
581
|
+
// Assert
|
|
582
|
+
expect(
|
|
583
|
+
await screen.findAllByRole("menuitemcheckbox", {hidden: true}),
|
|
584
|
+
).toHaveLength(2);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("Should render non-selected option items with `aria-checked` set to `false`", async () => {
|
|
588
|
+
// Arrange
|
|
589
|
+
render(
|
|
590
|
+
<ActionMenu
|
|
591
|
+
menuText="Action menu!"
|
|
592
|
+
testId="openTest"
|
|
593
|
+
onChange={onChange}
|
|
594
|
+
selectedValues={[]}
|
|
595
|
+
>
|
|
596
|
+
<OptionItem
|
|
597
|
+
label="Toggle A"
|
|
598
|
+
value="toggle-a"
|
|
599
|
+
testId="toggle-a"
|
|
600
|
+
/>
|
|
601
|
+
<OptionItem
|
|
602
|
+
label="Toggle B"
|
|
603
|
+
value="toggle-b"
|
|
604
|
+
testId="toggle-b"
|
|
605
|
+
/>
|
|
606
|
+
</ActionMenu>,
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
// Act
|
|
610
|
+
// open the menu
|
|
611
|
+
const opener = await screen.findByRole("button");
|
|
612
|
+
await userEvent.click(opener);
|
|
613
|
+
|
|
614
|
+
// Assert
|
|
615
|
+
const menuItemCheckboxes = await screen.findAllByRole(
|
|
616
|
+
"menuitemcheckbox",
|
|
617
|
+
{
|
|
618
|
+
hidden: true,
|
|
619
|
+
},
|
|
620
|
+
);
|
|
621
|
+
expect(menuItemCheckboxes.at(0)).toHaveAttribute(
|
|
622
|
+
"aria-checked",
|
|
623
|
+
"false",
|
|
624
|
+
);
|
|
625
|
+
expect(menuItemCheckboxes.at(1)).toHaveAttribute(
|
|
626
|
+
"aria-checked",
|
|
627
|
+
"false",
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it("Should render selected option items with `aria-checked` set to `true`", async () => {
|
|
632
|
+
// Arrange
|
|
633
|
+
render(
|
|
634
|
+
<ActionMenu
|
|
635
|
+
menuText="Action menu!"
|
|
636
|
+
testId="openTest"
|
|
637
|
+
onChange={onChange}
|
|
638
|
+
selectedValues={["toggle-a"]}
|
|
639
|
+
>
|
|
640
|
+
<OptionItem
|
|
641
|
+
label="Toggle A"
|
|
642
|
+
value="toggle-a"
|
|
643
|
+
testId="toggle-a"
|
|
644
|
+
/>
|
|
645
|
+
<OptionItem
|
|
646
|
+
label="Toggle B"
|
|
647
|
+
value="toggle-b"
|
|
648
|
+
testId="toggle-b"
|
|
649
|
+
/>
|
|
650
|
+
</ActionMenu>,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
// Act
|
|
654
|
+
// open the menu
|
|
655
|
+
const opener = await screen.findByRole("button");
|
|
656
|
+
await userEvent.click(opener);
|
|
657
|
+
|
|
658
|
+
// Assert
|
|
659
|
+
const menuItemCheckboxes = await screen.findAllByRole(
|
|
660
|
+
"menuitemcheckbox",
|
|
661
|
+
{
|
|
662
|
+
hidden: true,
|
|
663
|
+
},
|
|
664
|
+
);
|
|
665
|
+
expect(menuItemCheckboxes.at(0)).toHaveAttribute(
|
|
666
|
+
"aria-checked",
|
|
667
|
+
"true",
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it("Should not use `aria-selected` attribute on selected and non-selected options", async () => {
|
|
672
|
+
// Arrange
|
|
673
|
+
render(
|
|
674
|
+
<ActionMenu
|
|
675
|
+
menuText="Action menu!"
|
|
676
|
+
testId="openTest"
|
|
677
|
+
onChange={onChange}
|
|
678
|
+
selectedValues={["toggle-a"]}
|
|
679
|
+
>
|
|
680
|
+
<OptionItem
|
|
681
|
+
label="Toggle A"
|
|
682
|
+
value="toggle-a"
|
|
683
|
+
testId="toggle-a"
|
|
684
|
+
/>
|
|
685
|
+
<OptionItem
|
|
686
|
+
label="Toggle B"
|
|
687
|
+
value="toggle-b"
|
|
688
|
+
testId="toggle-b"
|
|
689
|
+
/>
|
|
690
|
+
</ActionMenu>,
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
// Act
|
|
694
|
+
// open the menu
|
|
695
|
+
const opener = await screen.findByRole("button");
|
|
696
|
+
await userEvent.click(opener);
|
|
697
|
+
|
|
698
|
+
// Assert
|
|
699
|
+
const menuItemCheckboxes = await screen.findAllByRole(
|
|
700
|
+
"menuitemcheckbox",
|
|
701
|
+
{
|
|
702
|
+
hidden: true,
|
|
703
|
+
},
|
|
704
|
+
);
|
|
705
|
+
expect(menuItemCheckboxes.at(0)).not.toHaveAttribute(
|
|
706
|
+
"aria-selected",
|
|
707
|
+
);
|
|
708
|
+
expect(menuItemCheckboxes.at(1)).not.toHaveAttribute(
|
|
709
|
+
"aria-selected",
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("Should render action items with `role=menuitem` and option items with `role=menuitemcheckbox`", async () => {
|
|
714
|
+
// Arrange
|
|
715
|
+
render(
|
|
716
|
+
<ActionMenu
|
|
717
|
+
menuText="Action menu!"
|
|
718
|
+
testId="openTest"
|
|
719
|
+
onChange={onChange}
|
|
720
|
+
>
|
|
721
|
+
<ActionItem label="Action" />
|
|
722
|
+
<OptionItem
|
|
723
|
+
label="Toggle A"
|
|
724
|
+
value="toggle-a"
|
|
725
|
+
testId="toggle-a"
|
|
726
|
+
/>
|
|
727
|
+
</ActionMenu>,
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
// Act
|
|
731
|
+
// open the menu
|
|
732
|
+
const opener = await screen.findByRole("button");
|
|
733
|
+
await userEvent.click(opener);
|
|
734
|
+
|
|
735
|
+
// Assert
|
|
736
|
+
expect(
|
|
737
|
+
await screen.findAllByRole("menuitem", {hidden: true}),
|
|
738
|
+
).toHaveLength(1);
|
|
739
|
+
expect(
|
|
740
|
+
await screen.findAllByRole("menuitemcheckbox", {hidden: true}),
|
|
741
|
+
).toHaveLength(1);
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
describe("With Virtualization", () => {
|
|
745
|
+
it("Should render option items with `role=menuitemcheckbox` when there are many options", async () => {
|
|
746
|
+
// Arrange
|
|
747
|
+
render(
|
|
748
|
+
<ActionMenu
|
|
749
|
+
menuText="Action menu!"
|
|
750
|
+
testId="openTest"
|
|
751
|
+
onChange={onChange}
|
|
752
|
+
>
|
|
753
|
+
{[...new Array(126)].map((_, i) => (
|
|
754
|
+
<OptionItem
|
|
755
|
+
label={`Toggle ${i}`}
|
|
756
|
+
key={i}
|
|
757
|
+
value={`toggle-${i}`}
|
|
758
|
+
/>
|
|
759
|
+
))}
|
|
760
|
+
</ActionMenu>,
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
// Act
|
|
764
|
+
// open the menu
|
|
765
|
+
const opener = await screen.findByRole("button");
|
|
766
|
+
await userEvent.click(opener);
|
|
767
|
+
|
|
768
|
+
// Assert
|
|
769
|
+
// Note there are less than the option items amount because they are
|
|
770
|
+
// virtualized
|
|
771
|
+
expect(
|
|
772
|
+
await screen.findAllByRole("menuitemcheckbox", {
|
|
773
|
+
hidden: true,
|
|
774
|
+
}),
|
|
775
|
+
).toHaveLength(14);
|
|
776
|
+
expect(
|
|
777
|
+
screen.queryAllByRole("menuitem", {
|
|
778
|
+
hidden: true,
|
|
779
|
+
}),
|
|
780
|
+
).toHaveLength(0);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it("Should render selected option items with `aria-checked=true` when there are many options", async () => {
|
|
784
|
+
// Arrange
|
|
785
|
+
render(
|
|
786
|
+
<ActionMenu
|
|
787
|
+
menuText="Action menu!"
|
|
788
|
+
testId="openTest"
|
|
789
|
+
onChange={onChange}
|
|
790
|
+
selectedValues={["toggle-0"]}
|
|
791
|
+
>
|
|
792
|
+
{[...new Array(126)].map((_, i) => (
|
|
793
|
+
<OptionItem
|
|
794
|
+
label={`Toggle ${i}`}
|
|
795
|
+
key={i}
|
|
796
|
+
value={`toggle-${i}`}
|
|
797
|
+
/>
|
|
798
|
+
))}
|
|
799
|
+
</ActionMenu>,
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
// Act
|
|
803
|
+
// open the menu
|
|
804
|
+
const opener = await screen.findByRole("button");
|
|
805
|
+
await userEvent.click(opener);
|
|
806
|
+
|
|
807
|
+
// Assert
|
|
808
|
+
const menuItemCheckboxes = await screen.findAllByRole(
|
|
809
|
+
"menuitemcheckbox",
|
|
810
|
+
{
|
|
811
|
+
hidden: true,
|
|
812
|
+
},
|
|
813
|
+
);
|
|
814
|
+
expect(menuItemCheckboxes.at(0)).toHaveAttribute(
|
|
815
|
+
"aria-checked",
|
|
816
|
+
"true",
|
|
817
|
+
);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("Should render non-selected option items with `aria-checked=false` when there are many options", async () => {
|
|
821
|
+
// Arrange
|
|
822
|
+
render(
|
|
823
|
+
<ActionMenu
|
|
824
|
+
menuText="Action menu!"
|
|
825
|
+
testId="openTest"
|
|
826
|
+
onChange={onChange}
|
|
827
|
+
selectedValues={[]}
|
|
828
|
+
>
|
|
829
|
+
{[...new Array(126)].map((_, i) => (
|
|
830
|
+
<OptionItem
|
|
831
|
+
label={`Toggle ${i}`}
|
|
832
|
+
key={i}
|
|
833
|
+
value={`toggle-${i}`}
|
|
834
|
+
/>
|
|
835
|
+
))}
|
|
836
|
+
</ActionMenu>,
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
// Act
|
|
840
|
+
// open the menu
|
|
841
|
+
const opener = await screen.findByRole("button");
|
|
842
|
+
await userEvent.click(opener);
|
|
843
|
+
|
|
844
|
+
// Assert
|
|
845
|
+
const menuItemCheckboxes = await screen.findAllByRole(
|
|
846
|
+
"menuitemcheckbox",
|
|
847
|
+
{
|
|
848
|
+
hidden: true,
|
|
849
|
+
},
|
|
850
|
+
);
|
|
851
|
+
expect(menuItemCheckboxes.at(0)).toHaveAttribute(
|
|
852
|
+
"aria-checked",
|
|
853
|
+
"false",
|
|
854
|
+
);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("Should not use `aria-selected` attribute on selected and non-selected options", async () => {
|
|
858
|
+
// Arrange
|
|
859
|
+
render(
|
|
860
|
+
<ActionMenu
|
|
861
|
+
menuText="Action menu!"
|
|
862
|
+
testId="openTest"
|
|
863
|
+
onChange={onChange}
|
|
864
|
+
selectedValues={["toggle-0"]}
|
|
865
|
+
>
|
|
866
|
+
{[...new Array(126)].map((_, i) => (
|
|
867
|
+
<OptionItem
|
|
868
|
+
label={`Toggle ${i}`}
|
|
869
|
+
key={i}
|
|
870
|
+
value={`toggle-${i}`}
|
|
871
|
+
/>
|
|
872
|
+
))}
|
|
873
|
+
</ActionMenu>,
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
// Act
|
|
877
|
+
// open the menu
|
|
878
|
+
const opener = await screen.findByRole("button");
|
|
879
|
+
await userEvent.click(opener);
|
|
880
|
+
|
|
881
|
+
// Assert
|
|
882
|
+
const menuItemCheckboxes = await screen.findAllByRole(
|
|
883
|
+
"menuitemcheckbox",
|
|
884
|
+
{
|
|
885
|
+
hidden: true,
|
|
886
|
+
},
|
|
887
|
+
);
|
|
888
|
+
expect(menuItemCheckboxes.at(0)).not.toHaveAttribute(
|
|
889
|
+
"aria-selected",
|
|
890
|
+
);
|
|
891
|
+
expect(menuItemCheckboxes.at(1)).not.toHaveAttribute(
|
|
892
|
+
"aria-selected",
|
|
893
|
+
);
|
|
894
|
+
});
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
describe("Ids", () => {
|
|
899
|
+
it("Should auto-generate an id for the opener if `id` prop is not provided", async () => {
|
|
900
|
+
// Arrange
|
|
901
|
+
render(
|
|
902
|
+
<ActionMenu menuText={"Action menu!"}>
|
|
903
|
+
<ActionItem label="Create" />
|
|
904
|
+
</ActionMenu>,
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
// Act
|
|
908
|
+
const opener = await screen.findByRole("button");
|
|
909
|
+
|
|
910
|
+
// Assert
|
|
911
|
+
// Expect autogenerated id to be in the form uid-action-menu-opener-[number]-wb-id
|
|
912
|
+
expect(opener).toHaveAttribute(
|
|
913
|
+
"id",
|
|
914
|
+
expect.stringMatching(/^uid-action-menu-opener-\d+-wb-id$/),
|
|
915
|
+
);
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
it("Should use the `id` prop if provided", async () => {
|
|
919
|
+
// Arrange
|
|
920
|
+
const id = "test-id";
|
|
921
|
+
render(
|
|
922
|
+
<ActionMenu menuText={"Action menu!"} id={id}>
|
|
923
|
+
<ActionItem label="Create" />
|
|
924
|
+
</ActionMenu>,
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
// Act
|
|
928
|
+
const opener = await screen.findByRole("button");
|
|
929
|
+
|
|
930
|
+
// Assert
|
|
931
|
+
expect(opener).toHaveAttribute("id", id);
|
|
932
|
+
});
|
|
933
|
+
it("Should auto-generate an id for the dropdown if `dropdownId` prop is not provided", async () => {
|
|
934
|
+
// Arrange
|
|
935
|
+
render(
|
|
936
|
+
<ActionMenu menuText={"Action menu!"}>
|
|
937
|
+
<ActionItem label="Create" />
|
|
938
|
+
</ActionMenu>,
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
// Act
|
|
942
|
+
// Open the dropdown
|
|
943
|
+
const opener = await screen.findByRole("button");
|
|
944
|
+
await userEvent.click(opener);
|
|
945
|
+
|
|
946
|
+
// Assert
|
|
947
|
+
expect(
|
|
948
|
+
await screen.findByRole("menu", {hidden: true}),
|
|
949
|
+
).toHaveAttribute(
|
|
950
|
+
"id",
|
|
951
|
+
expect.stringMatching(/^uid-action-menu-dropdown-\d+-wb-id$/),
|
|
952
|
+
);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
it("Should use the `dropdownId` prop if provided", async () => {
|
|
956
|
+
// Arrange
|
|
957
|
+
const dropdownId = "test-id";
|
|
958
|
+
render(
|
|
959
|
+
<ActionMenu menuText={"Action menu!"} dropdownId={dropdownId}>
|
|
960
|
+
<ActionItem label="Create" />
|
|
961
|
+
</ActionMenu>,
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// Act
|
|
965
|
+
// Open the dropdown
|
|
966
|
+
const opener = await screen.findByRole("button");
|
|
967
|
+
await userEvent.click(opener);
|
|
968
|
+
|
|
969
|
+
// Assert
|
|
970
|
+
expect(
|
|
971
|
+
await screen.findByRole("menu", {hidden: true}),
|
|
972
|
+
).toHaveAttribute("id", dropdownId);
|
|
973
|
+
});
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
describe("a11y > aria-controls", () => {
|
|
977
|
+
it("Should set the `aria-controls` attribute on the default opener to the provided dropdownId prop", async () => {
|
|
978
|
+
// Arrange
|
|
979
|
+
const dropdownId = "test-id";
|
|
980
|
+
render(
|
|
981
|
+
<ActionMenu menuText={"Action menu!"} dropdownId={dropdownId}>
|
|
982
|
+
<ActionItem label="Create" />
|
|
983
|
+
</ActionMenu>,
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
// Act
|
|
987
|
+
const opener = await screen.findByRole("button");
|
|
988
|
+
await userEvent.click(opener);
|
|
989
|
+
const dropdown = await screen.findByRole("menu", {hidden: true});
|
|
990
|
+
|
|
991
|
+
// Assert
|
|
992
|
+
expect(opener).toHaveAttribute("aria-controls", dropdown.id);
|
|
993
|
+
expect(opener).toHaveAttribute("aria-controls", dropdownId);
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it("Should set the `aria-controls` attribute on the default opener to the auto-generated dropdownId", async () => {
|
|
997
|
+
// Arrange
|
|
998
|
+
render(
|
|
999
|
+
<ActionMenu menuText={"Action menu!"}>
|
|
1000
|
+
<ActionItem label="Create" />
|
|
1001
|
+
</ActionMenu>,
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
// Act
|
|
1005
|
+
const opener = await screen.findByRole("button");
|
|
1006
|
+
await userEvent.click(opener);
|
|
1007
|
+
const dropdown = await screen.findByRole("menu", {hidden: true});
|
|
1008
|
+
|
|
1009
|
+
// Assert
|
|
1010
|
+
expect(opener).toHaveAttribute("aria-controls", dropdown.id);
|
|
1011
|
+
expect(opener).toHaveAttribute(
|
|
1012
|
+
"aria-controls",
|
|
1013
|
+
expect.stringMatching(/^uid-action-menu-dropdown-\d+-wb-id$/),
|
|
1014
|
+
);
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
it("Should set the `aria-controls` attribute on the custom opener to the provided dropdownId prop", async () => {
|
|
1018
|
+
// Arrange
|
|
1019
|
+
const dropdownId = "test-id";
|
|
1020
|
+
render(
|
|
1021
|
+
<ActionMenu
|
|
1022
|
+
menuText={"Action menu!"}
|
|
1023
|
+
dropdownId={dropdownId}
|
|
1024
|
+
opener={() => (
|
|
1025
|
+
<button aria-label="Search" onClick={jest.fn()} />
|
|
1026
|
+
)}
|
|
1027
|
+
>
|
|
1028
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1029
|
+
</ActionMenu>,
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
// Act
|
|
1033
|
+
const opener = await screen.findByLabelText("Search");
|
|
1034
|
+
await userEvent.click(opener);
|
|
1035
|
+
const dropdown = await screen.findByRole("menu", {hidden: true});
|
|
1036
|
+
|
|
1037
|
+
// Assert
|
|
1038
|
+
expect(opener).toHaveAttribute("aria-controls", dropdown.id);
|
|
1039
|
+
expect(opener).toHaveAttribute("aria-controls", dropdownId);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it("Should set the `aria-controls` attribute on the custom opener to the auto-generated dropdownId", async () => {
|
|
1043
|
+
// Arrange
|
|
1044
|
+
render(
|
|
1045
|
+
<ActionMenu
|
|
1046
|
+
menuText={"Action menu!"}
|
|
1047
|
+
opener={() => (
|
|
1048
|
+
<button aria-label="Search" onClick={jest.fn()} />
|
|
1049
|
+
)}
|
|
1050
|
+
>
|
|
1051
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1052
|
+
</ActionMenu>,
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
// Act
|
|
1056
|
+
const opener = await screen.findByLabelText("Search");
|
|
1057
|
+
await userEvent.click(opener);
|
|
1058
|
+
const dropdown = await screen.findByRole("menu", {hidden: true});
|
|
1059
|
+
|
|
1060
|
+
// Assert
|
|
1061
|
+
expect(opener).toHaveAttribute("aria-controls", dropdown.id);
|
|
1062
|
+
expect(opener).toHaveAttribute(
|
|
1063
|
+
"aria-controls",
|
|
1064
|
+
expect.stringMatching(/^uid-action-menu-dropdown-\d+-wb-id$/),
|
|
1065
|
+
);
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
describe("a11y > aria-haspopup", () => {
|
|
1070
|
+
it("should have aria-haspopup set on the opener", async () => {
|
|
1071
|
+
// Arrange
|
|
1072
|
+
render(
|
|
1073
|
+
<ActionMenu menuText={"Action menu!"} onChange={onChange}>
|
|
1074
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1075
|
+
</ActionMenu>,
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
// Act
|
|
1079
|
+
const opener = await screen.findByRole("button");
|
|
1080
|
+
|
|
1081
|
+
// Assert
|
|
1082
|
+
expect(opener).toHaveAttribute("aria-haspopup", "menu");
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
it("should have aria-haspopup set on the custom opener", async () => {
|
|
1086
|
+
// Arrange
|
|
1087
|
+
render(
|
|
1088
|
+
<ActionMenu
|
|
1089
|
+
menuText={"Action menu!"}
|
|
1090
|
+
onChange={onChange}
|
|
1091
|
+
opener={() => (
|
|
1092
|
+
<button aria-label="Search" onClick={jest.fn()} />
|
|
1093
|
+
)}
|
|
1094
|
+
>
|
|
1095
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1096
|
+
</ActionMenu>,
|
|
1097
|
+
);
|
|
1098
|
+
|
|
1099
|
+
// Act
|
|
1100
|
+
const opener = await screen.findByLabelText("Search");
|
|
1101
|
+
|
|
1102
|
+
// Assert
|
|
1103
|
+
expect(opener).toHaveAttribute("aria-haspopup", "menu");
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
describe("a11y > aria-expanded", () => {
|
|
1108
|
+
it("should have aria-expanded=false when closed", async () => {
|
|
1109
|
+
// Arrange
|
|
1110
|
+
render(
|
|
1111
|
+
<ActionMenu menuText={"Action menu!"} onChange={onChange}>
|
|
1112
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1113
|
+
</ActionMenu>,
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
// Act
|
|
1117
|
+
const opener = await screen.findByRole("button");
|
|
1118
|
+
|
|
1119
|
+
// Assert
|
|
1120
|
+
expect(opener).toHaveAttribute("aria-expanded", "false");
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
it("updates the aria-expanded value when opening", async () => {
|
|
1124
|
+
// Arrange
|
|
1125
|
+
render(
|
|
1126
|
+
<ActionMenu menuText={"Action menu!"} onChange={onChange}>
|
|
1127
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1128
|
+
</ActionMenu>,
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
// Act
|
|
1132
|
+
const opener = await screen.findByRole("button");
|
|
1133
|
+
await userEvent.click(opener);
|
|
1134
|
+
|
|
1135
|
+
// Assert
|
|
1136
|
+
expect(opener).toHaveAttribute("aria-expanded", "true");
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
it("should have aria-expanded=false when closed and using a custom opener", async () => {
|
|
1140
|
+
// Arrange
|
|
1141
|
+
render(
|
|
1142
|
+
<ActionMenu
|
|
1143
|
+
menuText={"Action menu!"}
|
|
1144
|
+
onChange={onChange}
|
|
1145
|
+
opener={() => (
|
|
1146
|
+
<button aria-label="Search" onClick={jest.fn()} />
|
|
1147
|
+
)}
|
|
1148
|
+
>
|
|
1149
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1150
|
+
</ActionMenu>,
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
// Act
|
|
1154
|
+
const opener = await screen.findByLabelText("Search");
|
|
1155
|
+
|
|
1156
|
+
// Assert
|
|
1157
|
+
expect(opener).toHaveAttribute("aria-expanded", "false");
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
it("updates the aria-expanded value when opening and using a custom opener", async () => {
|
|
1161
|
+
// Arrange
|
|
1162
|
+
render(
|
|
1163
|
+
<ActionMenu
|
|
1164
|
+
menuText={"Action menu!"}
|
|
1165
|
+
onChange={onChange}
|
|
1166
|
+
opener={() => (
|
|
1167
|
+
<button aria-label="Search" onClick={jest.fn()} />
|
|
1168
|
+
)}
|
|
1169
|
+
>
|
|
1170
|
+
<ActionItem label="Action" onClick={onClick} />
|
|
1171
|
+
</ActionMenu>,
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
// Act
|
|
1175
|
+
const opener = await screen.findByLabelText("Search");
|
|
1176
|
+
await userEvent.click(opener);
|
|
1177
|
+
|
|
1178
|
+
// Assert
|
|
1179
|
+
expect(opener).toHaveAttribute("aria-expanded", "true");
|
|
1180
|
+
});
|
|
1181
|
+
});
|
|
575
1182
|
});
|