|
19 | 19 | Completions, |
20 | 20 | utils, |
21 | 21 | ) |
| 22 | +from cmd2.completion import all_display_numeric |
22 | 23 |
|
23 | 24 | from .conftest import ( |
24 | 25 | normalize, |
@@ -877,6 +878,125 @@ def test_completions_iteration() -> None: |
877 | 878 | assert list(reversed(completions)) == items[::-1] |
878 | 879 |
|
879 | 880 |
|
| 881 | +def test_numeric_sorting() -> None: |
| 882 | + """Test that numbers and numeric strings are sorted numerically.""" |
| 883 | + numbers = [5, 6, 4, 3, 7.2, 9.1] |
| 884 | + completions = Completions.from_values(numbers) |
| 885 | + assert [item.value for item in completions] == sorted(numbers) |
| 886 | + |
| 887 | + number_strs = ["5", "6", "4", "3", "7.2", "9.1"] |
| 888 | + completions = Completions.from_values(number_strs) |
| 889 | + assert list(completions.to_strings()) == sorted(number_strs, key=float) |
| 890 | + |
| 891 | + mixed = ["5", "6", "4", 3, "7.2", 9.1] |
| 892 | + completions = Completions.from_values(mixed) |
| 893 | + assert list(completions.to_strings()) == [str(v) for v in sorted(number_strs, key=float)] |
| 894 | + |
| 895 | + |
| 896 | +def test_is_sorted() -> None: |
| 897 | + """Test that already sorted results are not re-sorted.""" |
| 898 | + values = [5, 6, 4, 3] |
| 899 | + already_sorted = Completions.from_values(values, is_sorted=True) |
| 900 | + sorted_on_creation = Completions.from_values(values, is_sorted=False) |
| 901 | + |
| 902 | + assert already_sorted.to_strings() != sorted_on_creation.to_strings() |
| 903 | + assert [item.value for item in already_sorted] == values |
| 904 | + |
| 905 | + |
| 906 | +@pytest.mark.parametrize( |
| 907 | + ('values', 'all_nums'), |
| 908 | + [ |
| 909 | + ([2, 3], True), |
| 910 | + ([2, 3.7], True), |
| 911 | + ([2, "3"], True), |
| 912 | + ([2.2, "3.4"], True), |
| 913 | + ([2, "3g"], False), |
| 914 | + # The display_plain field strips off ANSI sequences |
| 915 | + (["\x1b[31m5\x1b[0m", "\x1b[32m9.2\x1b[0m"], True), |
| 916 | + (["\x1b[31mNOT_STRING\x1b[0m", "\x1b[32m9.2\x1b[0m"], False), |
| 917 | + ], |
| 918 | +) |
| 919 | +def test_all_display_numeric(values: list[int | float | str], all_nums: bool) -> None: |
| 920 | + """Test that all_display_numeric() evaluates the display_plain field.""" |
| 921 | + |
| 922 | + items = [CompletionItem(v) for v in values] |
| 923 | + assert all_display_numeric(items) == all_nums |
| 924 | + |
| 925 | + |
| 926 | +def test_remove_duplicates() -> None: |
| 927 | + """Test that duplicate CompletionItems are removed.""" |
| 928 | + |
| 929 | + # Create items which alter the fields used in CompletionItem.__eq__(). |
| 930 | + orig_item = CompletionItem(value="orig item", display="orig display", display_meta="orig meta") |
| 931 | + new_value = dataclasses.replace(orig_item, value="new value") |
| 932 | + new_text = dataclasses.replace(orig_item, text="new text") |
| 933 | + new_display = dataclasses.replace(orig_item, display="new display") |
| 934 | + new_meta = dataclasses.replace(orig_item, display_meta="new meta") |
| 935 | + |
| 936 | + # Include each item twice. |
| 937 | + items = [orig_item, orig_item, new_value, new_value, new_text, new_text, new_display, new_display, new_meta, new_meta] |
| 938 | + completions = Completions(items) |
| 939 | + |
| 940 | + # Make sure we have exactly 1 of each item. |
| 941 | + assert len(completions) == 5 |
| 942 | + assert orig_item in completions |
| 943 | + assert new_value in completions |
| 944 | + assert new_text in completions |
| 945 | + assert new_display in completions |
| 946 | + assert new_meta in completions |
| 947 | + |
| 948 | + |
| 949 | +def test_plain_fields() -> None: |
| 950 | + """Test the plain text fields in CompletionItem.""" |
| 951 | + display = "\x1b[31mApple\x1b[0m" |
| 952 | + display_meta = "\x1b[32mA tasty apple\x1b[0m" |
| 953 | + |
| 954 | + # Show that the plain fields remove the ANSI sequences. |
| 955 | + completion_item = CompletionItem("apple", display=display, display_meta=display_meta) |
| 956 | + assert completion_item.display == display |
| 957 | + assert completion_item.display_plain == "Apple" |
| 958 | + assert completion_item.display_meta == display_meta |
| 959 | + assert completion_item.display_meta_plain == "A tasty apple" |
| 960 | + |
| 961 | + |
| 962 | +def test_styled_completion_sort() -> None: |
| 963 | + """Test that sorting is done with the display_plain field.""" |
| 964 | + |
| 965 | + # First sort with strings that include ANSI style sequences. |
| 966 | + red_apple = "\x1b[31mApple\x1b[0m" |
| 967 | + green_cherry = "\x1b[32mCherry\x1b[0m" |
| 968 | + blue_banana = "\x1b[34mBanana\x1b[0m" |
| 969 | + |
| 970 | + # This sorts by ASCII: [31m (Red), [32m (Green), [34m (Blue) |
| 971 | + unsorted_strs = [blue_banana, red_apple, green_cherry] |
| 972 | + sorted_strs = sorted(unsorted_strs, key=utils.DEFAULT_STR_SORT_KEY) |
| 973 | + assert sorted_strs == [red_apple, green_cherry, blue_banana] |
| 974 | + |
| 975 | + # Now create a Completions object with these values. |
| 976 | + unsorted_items = [ |
| 977 | + CompletionItem("banana", display=blue_banana), |
| 978 | + CompletionItem("cherry", display=green_cherry), |
| 979 | + CompletionItem("apple", display=red_apple), |
| 980 | + ] |
| 981 | + |
| 982 | + completions = Completions(unsorted_items) |
| 983 | + |
| 984 | + # Expected order: Apple (A), Banana (B), Cherry (C) |
| 985 | + expected_plain = ["Apple", "Banana", "Cherry"] |
| 986 | + expected_styled = [red_apple, blue_banana, green_cherry] |
| 987 | + |
| 988 | + for index, item in enumerate(completions): |
| 989 | + # Prove the ANSI stripping worked correctly |
| 990 | + assert item.display_plain == expected_plain[index] |
| 991 | + |
| 992 | + # Prove the sort order used the plain text, not the ANSI codes |
| 993 | + assert item.display == expected_styled[index] |
| 994 | + |
| 995 | + # Prove the order of completions is not the same as the raw string sort order |
| 996 | + completion_displays = [item.display for item in completions] |
| 997 | + assert completion_displays != sorted_strs |
| 998 | + |
| 999 | + |
880 | 1000 | # Used by redirect_complete tests |
881 | 1001 | class RedirCompType(enum.Enum): |
882 | 1002 | SHELL_CMD = (1,) |
|
0 commit comments