Skip to content

Commit 60d536f

Browse files
committed
Added EnumChoice completion regression test
Mark pallets/click#3471 as fixed upstream
1 parent 3ca1e7e commit 60d536f

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

docs/benchmark.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Click's built-in completion covers command and option names but has several know
8888

8989
**Fish multiline help** ([pallets/click#3043](https://github.com/pallets/click/issues/3043)): help text containing newlines used to break fish completion. **Fixed upstream** in [pallets/click#3126](https://github.com/pallets/click/pull/3126) (merged 2026-04-29), which also closes the bug report. Once click-extra's Click floor bumps to the release that ships the fix, fish completion handles multi-line help text out of the box and no click-extra workaround is needed.
9090

91-
**Enum Choice mismatch** ([pallets/click#3015](https://github.com/pallets/click/issues/3015)): `click.Choice(MyEnum)` completes `MyEnum.foo` instead of `foo` because completion skips the normalization the `Choice` type applies at parse time.
91+
**Enum Choice mismatch** ([pallets/click#3015](https://github.com/pallets/click/issues/3015)): `click.Choice(MyEnum)` completed `MyEnum.foo` instead of `foo` because completion skipped the normalization the `Choice` type applies at parse time. **Fixed upstream** in [pallets/click#3471](https://github.com/pallets/click/pull/3471) (merged 2026-05-19, first released in Click `8.4.1`), which routes completion through `Choice.normalize_choice()` so suggestions match what the parser accepts. click-extra's lock already resolves to Click `8.4.1`; once its floor rises from `8.3.1` to `>=8.4.1` the gap closes for every supported Click version. click-extra's own [`EnumChoice`](types.md#enumchoice) was never affected: it stores choice strings rather than `Enum` members, so its completions never carried the `Enum.member` form.
9292

9393
**Additional shells rejected upstream** ([pallets/click#2888](https://github.com/pallets/click/issues/2888), [pallets/click#3188](https://github.com/pallets/click/issues/3188), [pallets/click#2672](https://github.com/pallets/click/issues/2672)): Click explicitly rejected adding nushell, Carapace, and PowerShell completion to core: all three issues are closed as `not_planned`, so the door is closed upstream and remains a click-extra opportunity. click-extra could provide `NushellComplete` and `PowerShellComplete` classes, and generate Carapace YAML from its parameter introspection.
9494

tests/test_types.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,54 @@ def test_enum_choice_case_sensitivity(case_sensitive: bool) -> None:
566566
)
567567

568568

569+
@pytest.mark.parametrize(
570+
"source, expected",
571+
(
572+
(ChoiceSource.KEY, ("first_value", "second_value")),
573+
(ChoiceSource.VALUE, ("first-value", "second-value")),
574+
(ChoiceSource.STR, ("my-first-value", "my-second-value")),
575+
),
576+
)
577+
def test_enum_choice_shell_complete(
578+
source: ChoiceSource,
579+
expected: tuple[str, str],
580+
) -> None:
581+
"""Completion offers normalized choice strings, never the ``Enum.member`` form.
582+
583+
Regression guard for pallets/click#3015, fixed upstream in pallets/click#3471:
584+
completion routes through ``Choice.normalize_choice()``, so an ``EnumChoice``
585+
suggests parseable strings (case-folded, as it is case-insensitive by default)
586+
instead of ``MyEnum.FIRST_VALUE``.
587+
"""
588+
enum_choice = EnumChoice(MyEnum, choice_source=source)
589+
590+
# A bare Context/Parameter is enough to drive completion.
591+
cli = click.Command("cli", params=[click.Option(["--fmt"], type=enum_choice)])
592+
ctx = click.Context(cli)
593+
param = cli.params[0]
594+
595+
def complete(incomplete: str) -> list[str]:
596+
items = enum_choice.shell_complete(ctx, param, incomplete)
597+
return [item.value for item in items]
598+
599+
all_choices = complete("")
600+
601+
# Empty input lists every normalized choice, in declaration order.
602+
assert all_choices == list(expected)
603+
604+
# No suggestion leaks the ``Enum.member`` representation.
605+
assert not any("MyEnum" in choice for choice in all_choices)
606+
607+
# Every suggestion parses back to its member.
608+
for choice in all_choices:
609+
assert isinstance(enum_choice.convert(choice, param, ctx), MyEnum)
610+
611+
# Prefix filtering stays case-insensitive.
612+
second = expected[1]
613+
assert complete(second) == [second]
614+
assert complete(second.upper()) == [second]
615+
616+
569617
def test_enum_choice_duplicate_string() -> None:
570618
class BadEnum(StrEnum):
571619
FIRST = auto()

0 commit comments

Comments
 (0)