|
1 | 1 | import { render, screen, waitFor } from '@testing-library/react';
|
2 | 2 | import userEvent from '@testing-library/user-event';
|
3 |
| -import React from 'react'; |
| 3 | +import React, { useState } from 'react'; |
4 | 4 |
|
5 | 5 | import { CommandMenu } from './CommandMenu';
|
6 | 6 |
|
@@ -845,6 +845,246 @@ describe('CommandMenu', () => {
|
845 | 845 | expect(selectedDisplay).toHaveTextContent(/Selected:.*1.*2/);
|
846 | 846 | });
|
847 | 847 |
|
| 848 | + it('sorts selected items to the top in multiple selection mode', async () => { |
| 849 | + const user = userEvent.setup(); |
| 850 | + const items = [ |
| 851 | + { id: '1', textValue: 'First Item' }, |
| 852 | + { id: '2', textValue: 'Second Item' }, |
| 853 | + { id: '3', textValue: 'Third Item' }, |
| 854 | + { id: '4', textValue: 'Fourth Item' }, |
| 855 | + ]; |
| 856 | + |
| 857 | + const TestComponent = () => { |
| 858 | + const [selectedKeys, setSelectedKeys] = useState<string[]>(['2', '4']); |
| 859 | + |
| 860 | + const handleSelectionChange = (keys: string[]) => { |
| 861 | + setSelectedKeys(keys); |
| 862 | + }; |
| 863 | + |
| 864 | + return ( |
| 865 | + <CommandMenu |
| 866 | + selectionMode="multiple" |
| 867 | + selectedKeys={selectedKeys} |
| 868 | + onSelectionChange={handleSelectionChange} |
| 869 | + > |
| 870 | + {items.map((item) => ( |
| 871 | + <CommandMenu.Item key={item.id} id={item.id}> |
| 872 | + {item.textValue} |
| 873 | + </CommandMenu.Item> |
| 874 | + ))} |
| 875 | + </CommandMenu> |
| 876 | + ); |
| 877 | + }; |
| 878 | + |
| 879 | + render(<TestComponent />); |
| 880 | + |
| 881 | + // Get all menu items (in multiple selection mode, they have role "menuitemcheckbox") |
| 882 | + const menuItems = screen.getAllByRole('menuitemcheckbox'); |
| 883 | + |
| 884 | + // The selected items (2 and 4) should appear first |
| 885 | + // So the order should be: Second Item, Fourth Item, First Item, Third Item |
| 886 | + expect(menuItems[0]).toHaveTextContent('Second Item'); |
| 887 | + expect(menuItems[1]).toHaveTextContent('Fourth Item'); |
| 888 | + expect(menuItems[2]).toHaveTextContent('First Item'); |
| 889 | + expect(menuItems[3]).toHaveTextContent('Third Item'); |
| 890 | + }); |
| 891 | + |
| 892 | + it('sorts selected items to the top in multiple selection mode even with search filtering', async () => { |
| 893 | + const user = userEvent.setup(); |
| 894 | + const items = [ |
| 895 | + { id: '1', textValue: 'Apple' }, |
| 896 | + { id: '2', textValue: 'Banana' }, |
| 897 | + { id: '3', textValue: 'Apricot' }, |
| 898 | + { id: '4', textValue: 'Berry' }, |
| 899 | + ]; |
| 900 | + |
| 901 | + const TestComponent = () => { |
| 902 | + const [selectedKeys, setSelectedKeys] = useState<string[]>(['3', '4']); // Apricot and Berry are selected |
| 903 | + |
| 904 | + const handleSelectionChange = (keys: string[]) => { |
| 905 | + setSelectedKeys(keys); |
| 906 | + }; |
| 907 | + |
| 908 | + return ( |
| 909 | + <CommandMenu |
| 910 | + selectionMode="multiple" |
| 911 | + selectedKeys={selectedKeys} |
| 912 | + onSelectionChange={handleSelectionChange} |
| 913 | + > |
| 914 | + {items.map((item) => ( |
| 915 | + <CommandMenu.Item key={item.id} id={item.id}> |
| 916 | + {item.textValue} |
| 917 | + </CommandMenu.Item> |
| 918 | + ))} |
| 919 | + </CommandMenu> |
| 920 | + ); |
| 921 | + }; |
| 922 | + |
| 923 | + render(<TestComponent />); |
| 924 | + |
| 925 | + // Search for "Ap" - should match "Apple" and "Apricot" |
| 926 | + const searchInput = screen.getByPlaceholderText('Search commands...'); |
| 927 | + await user.type(searchInput, 'Ap'); |
| 928 | + |
| 929 | + // Get all filtered menu items |
| 930 | + const menuItems = screen.getAllByRole('menuitemcheckbox'); |
| 931 | + |
| 932 | + // Only "Apple" and "Apricot" should be visible |
| 933 | + // "Apricot" should appear first because it's selected |
| 934 | + expect(menuItems).toHaveLength(2); |
| 935 | + expect(menuItems[0]).toHaveTextContent('Apricot'); // Selected item first |
| 936 | + expect(menuItems[1]).toHaveTextContent('Apple'); // Unselected item second |
| 937 | + }); |
| 938 | + |
| 939 | + it('sorts selected items to the top in multiple selection mode with sections', async () => { |
| 940 | + const TestComponent = () => { |
| 941 | + const [selectedKeys, setSelectedKeys] = useState<string[]>([ |
| 942 | + 'item2', |
| 943 | + 'item4', |
| 944 | + ]); |
| 945 | + |
| 946 | + const handleSelectionChange = (keys: string[]) => { |
| 947 | + setSelectedKeys(keys); |
| 948 | + }; |
| 949 | + |
| 950 | + return ( |
| 951 | + <CommandMenu |
| 952 | + selectionMode="multiple" |
| 953 | + selectedKeys={selectedKeys} |
| 954 | + onSelectionChange={handleSelectionChange} |
| 955 | + > |
| 956 | + <CommandMenu.Section key="section1" aria-label="Files"> |
| 957 | + <CommandMenu.Item key="item1" id="item1"> |
| 958 | + Create File |
| 959 | + </CommandMenu.Item> |
| 960 | + <CommandMenu.Item key="item2" id="item2"> |
| 961 | + Open File |
| 962 | + </CommandMenu.Item> |
| 963 | + </CommandMenu.Section> |
| 964 | + <CommandMenu.Section key="section2" aria-label="Edit"> |
| 965 | + <CommandMenu.Item key="item3" id="item3"> |
| 966 | + Cut |
| 967 | + </CommandMenu.Item> |
| 968 | + <CommandMenu.Item key="item4" id="item4"> |
| 969 | + Copy |
| 970 | + </CommandMenu.Item> |
| 971 | + </CommandMenu.Section> |
| 972 | + </CommandMenu> |
| 973 | + ); |
| 974 | + }; |
| 975 | + |
| 976 | + render(<TestComponent />); |
| 977 | + |
| 978 | + // Get all menu items |
| 979 | + const menuItems = screen.getAllByRole('menuitemcheckbox'); |
| 980 | + |
| 981 | + // Within each section, selected items should appear first |
| 982 | + // Section 1: "Open File" (selected) should come before "Create File" (unselected) |
| 983 | + // Section 2: "Copy" (selected) should come before "Cut" (unselected) |
| 984 | + expect(menuItems[0]).toHaveTextContent('Open File'); // Selected item in section 1 |
| 985 | + expect(menuItems[1]).toHaveTextContent('Create File'); // Unselected item in section 1 |
| 986 | + expect(menuItems[2]).toHaveTextContent('Copy'); // Selected item in section 2 |
| 987 | + expect(menuItems[3]).toHaveTextContent('Cut'); // Unselected item in section 2 |
| 988 | + }); |
| 989 | + |
| 990 | + it('does not re-sort items during user interaction to prevent content shifting', async () => { |
| 991 | + const user = userEvent.setup(); |
| 992 | + const items = [ |
| 993 | + { id: '1', textValue: 'First Item' }, |
| 994 | + { id: '2', textValue: 'Second Item' }, |
| 995 | + { id: '3', textValue: 'Third Item' }, |
| 996 | + { id: '4', textValue: 'Fourth Item' }, |
| 997 | + ]; |
| 998 | + |
| 999 | + const TestComponent = () => { |
| 1000 | + const [selectedKeys, setSelectedKeys] = useState<string[]>(['2', '4']); // Initially select items 2 and 4 |
| 1001 | + |
| 1002 | + const handleSelectionChange = (keys: string[]) => { |
| 1003 | + setSelectedKeys(keys); |
| 1004 | + }; |
| 1005 | + |
| 1006 | + return ( |
| 1007 | + <CommandMenu |
| 1008 | + selectionMode="multiple" |
| 1009 | + selectedKeys={selectedKeys} |
| 1010 | + onSelectionChange={handleSelectionChange} |
| 1011 | + > |
| 1012 | + {items.map((item) => ( |
| 1013 | + <CommandMenu.Item key={item.id} id={item.id}> |
| 1014 | + {item.textValue} |
| 1015 | + </CommandMenu.Item> |
| 1016 | + ))} |
| 1017 | + </CommandMenu> |
| 1018 | + ); |
| 1019 | + }; |
| 1020 | + |
| 1021 | + render(<TestComponent />); |
| 1022 | + |
| 1023 | + // Initially, items 2 and 4 should be sorted to the top |
| 1024 | + let menuItems = screen.getAllByRole('menuitemcheckbox'); |
| 1025 | + expect(menuItems[0]).toHaveTextContent('Second Item'); // Selected |
| 1026 | + expect(menuItems[1]).toHaveTextContent('Fourth Item'); // Selected |
| 1027 | + expect(menuItems[2]).toHaveTextContent('First Item'); // Unselected |
| 1028 | + expect(menuItems[3]).toHaveTextContent('Third Item'); // Unselected |
| 1029 | + |
| 1030 | + // Click on "First Item" to select it - the order should NOT change (no content shifting) |
| 1031 | + await user.click(menuItems[2]); // Click "First Item" |
| 1032 | + |
| 1033 | + // Get menu items again after the selection change |
| 1034 | + menuItems = screen.getAllByRole('menuitemcheckbox'); |
| 1035 | + |
| 1036 | + // The order should remain the same - no content shifting |
| 1037 | + expect(menuItems[0]).toHaveTextContent('Second Item'); // Still first |
| 1038 | + expect(menuItems[1]).toHaveTextContent('Fourth Item'); // Still second |
| 1039 | + expect(menuItems[2]).toHaveTextContent('First Item'); // Still third (now selected) |
| 1040 | + expect(menuItems[3]).toHaveTextContent('Third Item'); // Still fourth |
| 1041 | + |
| 1042 | + // Verify that "First Item" is now selected but didn't move |
| 1043 | + expect(menuItems[2]).toHaveAttribute('aria-checked', 'true'); |
| 1044 | + }); |
| 1045 | + |
| 1046 | + it('sorts selected items to the top with defaultSelectedKeys', async () => { |
| 1047 | + const items = [ |
| 1048 | + { id: '1', textValue: 'First Item' }, |
| 1049 | + { id: '2', textValue: 'Second Item' }, |
| 1050 | + { id: '3', textValue: 'Third Item' }, |
| 1051 | + { id: '4', textValue: 'Fourth Item' }, |
| 1052 | + ]; |
| 1053 | + |
| 1054 | + const TestComponent = () => { |
| 1055 | + return ( |
| 1056 | + <CommandMenu |
| 1057 | + selectionMode="multiple" |
| 1058 | + defaultSelectedKeys={['3', '1']} // Default select Third and First items |
| 1059 | + > |
| 1060 | + {items.map((item) => ( |
| 1061 | + <CommandMenu.Item key={item.id} id={item.id}> |
| 1062 | + {item.textValue} |
| 1063 | + </CommandMenu.Item> |
| 1064 | + ))} |
| 1065 | + </CommandMenu> |
| 1066 | + ); |
| 1067 | + }; |
| 1068 | + |
| 1069 | + render(<TestComponent />); |
| 1070 | + |
| 1071 | + // Get all menu items |
| 1072 | + const menuItems = screen.getAllByRole('menuitemcheckbox'); |
| 1073 | + |
| 1074 | + // The default selected items (3 and 1) should appear first |
| 1075 | + // The order follows the original array order for selected items: First Item (1), Third Item (3) |
| 1076 | + expect(menuItems[0]).toHaveTextContent('First Item'); // Selected (appears first in original array) |
| 1077 | + expect(menuItems[1]).toHaveTextContent('Third Item'); // Selected (appears third in original array) |
| 1078 | + expect(menuItems[2]).toHaveTextContent('Second Item'); // Unselected |
| 1079 | + expect(menuItems[3]).toHaveTextContent('Fourth Item'); // Unselected |
| 1080 | + |
| 1081 | + // Verify the selected items are indeed selected |
| 1082 | + expect(menuItems[0]).toHaveAttribute('aria-checked', 'true'); |
| 1083 | + expect(menuItems[1]).toHaveAttribute('aria-checked', 'true'); |
| 1084 | + expect(menuItems[2]).toHaveAttribute('aria-checked', 'false'); |
| 1085 | + expect(menuItems[3]).toHaveAttribute('aria-checked', 'false'); |
| 1086 | + }); |
| 1087 | + |
848 | 1088 | describe('CommandMenu mods', () => {
|
849 | 1089 | it('should apply popover mod when used with MenuTrigger', () => {
|
850 | 1090 | const { MenuContext } = require('../Menu/context');
|
|
0 commit comments