1import os
2import re
3
4import pytest
5
6import click
7from click import Option
8
9
10def test_prefixes(runner):
11    @click.command()
12    @click.option("++foo", is_flag=True, help="das foo")
13    @click.option("--bar", is_flag=True, help="das bar")
14    def cli(foo, bar):
15        click.echo(f"foo={foo} bar={bar}")
16
17    result = runner.invoke(cli, ["++foo", "--bar"])
18    assert not result.exception
19    assert result.output == "foo=True bar=True\n"
20
21    result = runner.invoke(cli, ["--help"])
22    assert re.search(r"\+\+foo\s+das foo", result.output) is not None
23    assert re.search(r"--bar\s+das bar", result.output) is not None
24
25
26def test_invalid_option(runner):
27    with pytest.raises(TypeError, match="name was passed") as exc_info:
28        click.Option(["foo"])
29
30    message = str(exc_info.value)
31    assert "name was passed (foo)" in message
32    assert "declare an argument" in message
33    assert "'--foo'" in message
34
35
36def test_invalid_nargs(runner):
37    with pytest.raises(TypeError, match="nargs=-1"):
38
39        @click.command()
40        @click.option("--foo", nargs=-1)
41        def cli(foo):
42            pass
43
44
45def test_nargs_tup_composite_mult(runner):
46    @click.command()
47    @click.option("--item", type=(str, int), multiple=True)
48    def copy(item):
49        for name, id in item:
50            click.echo(f"name={name} id={id:d}")
51
52    result = runner.invoke(copy, ["--item", "peter", "1", "--item", "max", "2"])
53    assert not result.exception
54    assert result.output.splitlines() == ["name=peter id=1", "name=max id=2"]
55
56
57def test_counting(runner):
58    @click.command()
59    @click.option("-v", count=True, help="Verbosity", type=click.IntRange(0, 3))
60    def cli(v):
61        click.echo(f"verbosity={v:d}")
62
63    result = runner.invoke(cli, ["-vvv"])
64    assert not result.exception
65    assert result.output == "verbosity=3\n"
66
67    result = runner.invoke(cli, ["-vvvv"])
68    assert result.exception
69    assert "Invalid value for '-v': 4 is not in the range 0<=x<=3." in result.output
70
71    result = runner.invoke(cli, [])
72    assert not result.exception
73    assert result.output == "verbosity=0\n"
74
75    result = runner.invoke(cli, ["--help"])
76    assert re.search(r"-v\s+Verbosity", result.output) is not None
77
78
79@pytest.mark.parametrize("unknown_flag", ["--foo", "-f"])
80def test_unknown_options(runner, unknown_flag):
81    @click.command()
82    def cli():
83        pass
84
85    result = runner.invoke(cli, [unknown_flag])
86    assert result.exception
87    assert f"No such option: {unknown_flag}" in result.output
88
89
90@pytest.mark.parametrize(
91    ("value", "expect"),
92    [
93        ("--cat", "Did you mean --count?"),
94        ("--bounds", "(Possible options: --bound, --count)"),
95        ("--bount", "(Possible options: --bound, --count)"),
96    ],
97)
98def test_suggest_possible_options(runner, value, expect):
99    cli = click.Command(
100        "cli", params=[click.Option(["--bound"]), click.Option(["--count"])]
101    )
102    result = runner.invoke(cli, [value])
103    assert expect in result.output
104
105
106def test_multiple_required(runner):
107    @click.command()
108    @click.option("-m", "--message", multiple=True, required=True)
109    def cli(message):
110        click.echo("\n".join(message))
111
112    result = runner.invoke(cli, ["-m", "foo", "-mbar"])
113    assert not result.exception
114    assert result.output == "foo\nbar\n"
115
116    result = runner.invoke(cli, [])
117    assert result.exception
118    assert "Error: Missing option '-m' / '--message'." in result.output
119
120
121@pytest.mark.parametrize(
122    ("multiple", "nargs", "default"),
123    [
124        (True, 1, []),
125        (True, 1, [1]),
126        # (False, -1, []),
127        # (False, -1, [1]),
128        (False, 2, [1, 2]),
129        # (True, -1, [[]]),
130        # (True, -1, []),
131        # (True, -1, [[1]]),
132        (True, 2, []),
133        (True, 2, [[1, 2]]),
134    ],
135)
136def test_init_good_default_list(runner, multiple, nargs, default):
137    click.Option(["-a"], multiple=multiple, nargs=nargs, default=default)
138
139
140@pytest.mark.parametrize(
141    ("multiple", "nargs", "default"),
142    [
143        (True, 1, 1),
144        # (False, -1, 1),
145        (False, 2, [1]),
146        (True, 2, [[1]]),
147    ],
148)
149def test_init_bad_default_list(runner, multiple, nargs, default):
150    type = (str, str) if nargs == 2 else None
151
152    with pytest.raises(ValueError, match="default"):
153        click.Option(["-a"], type=type, multiple=multiple, nargs=nargs, default=default)
154
155
156def test_empty_envvar(runner):
157    @click.command()
158    @click.option("--mypath", type=click.Path(exists=True), envvar="MYPATH")
159    def cli(mypath):
160        click.echo(f"mypath: {mypath}")
161
162    result = runner.invoke(cli, [], env={"MYPATH": ""})
163    assert result.exit_code == 0
164    assert result.output == "mypath: None\n"
165
166
167def test_multiple_envvar(runner):
168    @click.command()
169    @click.option("--arg", multiple=True)
170    def cmd(arg):
171        click.echo("|".join(arg))
172
173    result = runner.invoke(
174        cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "foo bar baz"}
175    )
176    assert not result.exception
177    assert result.output == "foo|bar|baz\n"
178
179    @click.command()
180    @click.option("--arg", multiple=True, envvar="X")
181    def cmd(arg):
182        click.echo("|".join(arg))
183
184    result = runner.invoke(cmd, [], env={"X": "foo bar baz"})
185    assert not result.exception
186    assert result.output == "foo|bar|baz\n"
187
188    @click.command()
189    @click.option("--arg", multiple=True, type=click.Path())
190    def cmd(arg):
191        click.echo("|".join(arg))
192
193    result = runner.invoke(
194        cmd,
195        [],
196        auto_envvar_prefix="TEST",
197        env={"TEST_ARG": f"foo{os.path.pathsep}bar"},
198    )
199    assert not result.exception
200    assert result.output == "foo|bar\n"
201
202
203def test_trailing_blanks_boolean_envvar(runner):
204    @click.command()
205    @click.option("--shout/--no-shout", envvar="SHOUT")
206    def cli(shout):
207        click.echo(f"shout: {shout!r}")
208
209    result = runner.invoke(cli, [], env={"SHOUT": " true "})
210    assert result.exit_code == 0
211    assert result.output == "shout: True\n"
212
213
214def test_multiple_default_help(runner):
215    @click.command()
216    @click.option("--arg1", multiple=True, default=("foo", "bar"), show_default=True)
217    @click.option("--arg2", multiple=True, default=(1, 2), type=int, show_default=True)
218    def cmd(arg, arg2):
219        pass
220
221    result = runner.invoke(cmd, ["--help"])
222    assert not result.exception
223    assert "foo, bar" in result.output
224    assert "1, 2" in result.output
225
226
227def test_show_default_default_map(runner):
228    @click.command()
229    @click.option("--arg", default="a", show_default=True)
230    def cmd(arg):
231        click.echo(arg)
232
233    result = runner.invoke(cmd, ["--help"], default_map={"arg": "b"})
234
235    assert not result.exception
236    assert "[default: b]" in result.output
237
238
239def test_multiple_default_type():
240    opt = click.Option(["-a"], multiple=True, default=(1, 2))
241    assert opt.nargs == 1
242    assert opt.multiple
243    assert opt.type is click.INT
244    ctx = click.Context(click.Command("test"))
245    assert opt.get_default(ctx) == (1, 2)
246
247
248def test_multiple_default_composite_type():
249    opt = click.Option(["-a"], multiple=True, default=[(1, "a")])
250    assert opt.nargs == 2
251    assert opt.multiple
252    assert isinstance(opt.type, click.Tuple)
253    assert opt.type.types == [click.INT, click.STRING]
254    ctx = click.Context(click.Command("test"))
255    assert opt.type_cast_value(ctx, opt.get_default(ctx)) == ((1, "a"),)
256
257
258def test_parse_multiple_default_composite_type(runner):
259    @click.command()
260    @click.option("-a", multiple=True, default=("a", "b"))
261    @click.option("-b", multiple=True, default=[(1, "a")])
262    def cmd(a, b):
263        click.echo(a)
264        click.echo(b)
265
266    # result = runner.invoke(cmd, "-a c -a 1 -a d -b 2 two -b 4 four".split())
267    # assert result.output == "('c', '1', 'd')\n((2, 'two'), (4, 'four'))\n"
268    result = runner.invoke(cmd)
269    assert result.output == "('a', 'b')\n((1, 'a'),)\n"
270
271
272def test_dynamic_default_help_unset(runner):
273    @click.command()
274    @click.option(
275        "--username",
276        prompt=True,
277        default=lambda: os.environ.get("USER", ""),
278        show_default=True,
279    )
280    def cmd(username):
281        print("Hello,", username)
282
283    result = runner.invoke(cmd, ["--help"])
284    assert result.exit_code == 0
285    assert "--username" in result.output
286    assert "lambda" not in result.output
287    assert "(dynamic)" in result.output
288
289
290def test_dynamic_default_help_text(runner):
291    @click.command()
292    @click.option(
293        "--username",
294        prompt=True,
295        default=lambda: os.environ.get("USER", ""),
296        show_default="current user",
297    )
298    def cmd(username):
299        print("Hello,", username)
300
301    result = runner.invoke(cmd, ["--help"])
302    assert result.exit_code == 0
303    assert "--username" in result.output
304    assert "lambda" not in result.output
305    assert "(current user)" in result.output
306
307
308@pytest.mark.parametrize(
309    ("type", "expect"),
310    [
311        (click.IntRange(1, 32), "1<=x<=32"),
312        (click.IntRange(1, 32, min_open=True, max_open=True), "1<x<32"),
313        (click.IntRange(1), "x>=1"),
314        (click.IntRange(max=32), "x<=32"),
315    ],
316)
317def test_intrange_default_help_text(type, expect):
318    option = click.Option(["--num"], type=type, show_default=True, default=2)
319    context = click.Context(click.Command("test"))
320    result = option.get_help_record(context)[1]
321    assert expect in result
322
323
324def test_count_default_type_help():
325    """A count option with the default type should not show >=0 in help."""
326    option = click.Option(["--count"], count=True, help="some words")
327    context = click.Context(click.Command("test"))
328    result = option.get_help_record(context)[1]
329    assert result == "some words"
330
331
332def test_file_type_help_default():
333    """The default for a File type is a filename string. The string
334    should be displayed in help, not an open file object.
335
336    Type casting is only applied to defaults in processing, not when
337    getting the default value.
338    """
339    option = click.Option(
340        ["--in"], type=click.File(), default=__file__, show_default=True
341    )
342    context = click.Context(click.Command("test"))
343    result = option.get_help_record(context)[1]
344    assert __file__ in result
345
346
347def test_toupper_envvar_prefix(runner):
348    @click.command()
349    @click.option("--arg")
350    def cmd(arg):
351        click.echo(arg)
352
353    result = runner.invoke(cmd, [], auto_envvar_prefix="test", env={"TEST_ARG": "foo"})
354    assert not result.exception
355    assert result.output == "foo\n"
356
357
358def test_nargs_envvar(runner):
359    @click.command()
360    @click.option("--arg", nargs=2)
361    def cmd(arg):
362        click.echo("|".join(arg))
363
364    result = runner.invoke(
365        cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "foo bar"}
366    )
367    assert not result.exception
368    assert result.output == "foo|bar\n"
369
370    @click.command()
371    @click.option("--arg", nargs=2, multiple=True)
372    def cmd(arg):
373        for item in arg:
374            click.echo("|".join(item))
375
376    result = runner.invoke(
377        cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "x 1 y 2"}
378    )
379    assert not result.exception
380    assert result.output == "x|1\ny|2\n"
381
382
383def test_show_envvar(runner):
384    @click.command()
385    @click.option("--arg1", envvar="ARG1", show_envvar=True)
386    def cmd(arg):
387        pass
388
389    result = runner.invoke(cmd, ["--help"])
390    assert not result.exception
391    assert "ARG1" in result.output
392
393
394def test_show_envvar_auto_prefix(runner):
395    @click.command()
396    @click.option("--arg1", show_envvar=True)
397    def cmd(arg):
398        pass
399
400    result = runner.invoke(cmd, ["--help"], auto_envvar_prefix="TEST")
401    assert not result.exception
402    assert "TEST_ARG1" in result.output
403
404
405def test_show_envvar_auto_prefix_dash_in_command(runner):
406    @click.group()
407    def cli():
408        pass
409
410    @cli.command()
411    @click.option("--baz", show_envvar=True)
412    def foo_bar(baz):
413        pass
414
415    result = runner.invoke(cli, ["foo-bar", "--help"], auto_envvar_prefix="TEST")
416    assert not result.exception
417    assert "TEST_FOO_BAR_BAZ" in result.output
418
419
420def test_custom_validation(runner):
421    def validate_pos_int(ctx, param, value):
422        if value < 0:
423            raise click.BadParameter("Value needs to be positive")
424        return value
425
426    @click.command()
427    @click.option("--foo", callback=validate_pos_int, default=1)
428    def cmd(foo):
429        click.echo(foo)
430
431    result = runner.invoke(cmd, ["--foo", "-1"])
432    assert "Invalid value for '--foo': Value needs to be positive" in result.output
433
434    result = runner.invoke(cmd, ["--foo", "42"])
435    assert result.output == "42\n"
436
437
438def test_callback_validates_prompt(runner, monkeypatch):
439    def validate(ctx, param, value):
440        if value < 0:
441            raise click.BadParameter("should be positive")
442
443        return value
444
445    @click.command()
446    @click.option("-a", type=int, callback=validate, prompt=True)
447    def cli(a):
448        click.echo(a)
449
450    result = runner.invoke(cli, input="-12\n60\n")
451    assert result.output == "A: -12\nError: should be positive\nA: 60\n60\n"
452
453
454def test_winstyle_options(runner):
455    @click.command()
456    @click.option("/debug;/no-debug", help="Enables or disables debug mode.")
457    def cmd(debug):
458        click.echo(debug)
459
460    result = runner.invoke(cmd, ["/debug"], help_option_names=["/?"])
461    assert result.output == "True\n"
462    result = runner.invoke(cmd, ["/no-debug"], help_option_names=["/?"])
463    assert result.output == "False\n"
464    result = runner.invoke(cmd, [], help_option_names=["/?"])
465    assert result.output == "False\n"
466    result = runner.invoke(cmd, ["/?"], help_option_names=["/?"])
467    assert "/debug; /no-debug  Enables or disables debug mode." in result.output
468    assert "/?                 Show this message and exit." in result.output
469
470
471def test_legacy_options(runner):
472    @click.command()
473    @click.option("-whatever")
474    def cmd(whatever):
475        click.echo(whatever)
476
477    result = runner.invoke(cmd, ["-whatever", "42"])
478    assert result.output == "42\n"
479    result = runner.invoke(cmd, ["-whatever=23"])
480    assert result.output == "23\n"
481
482
483def test_missing_option_string_cast():
484    ctx = click.Context(click.Command(""))
485
486    with pytest.raises(click.MissingParameter) as excinfo:
487        click.Option(["-a"], required=True).process_value(ctx, None)
488
489    assert str(excinfo.value) == "Missing parameter: a"
490
491
492def test_missing_choice(runner):
493    @click.command()
494    @click.option("--foo", type=click.Choice(["foo", "bar"]), required=True)
495    def cmd(foo):
496        click.echo(foo)
497
498    result = runner.invoke(cmd)
499    assert result.exit_code == 2
500    error, separator, choices = result.output.partition("Choose from")
501    assert "Error: Missing option '--foo'. " in error
502    assert "Choose from" in separator
503    assert "foo" in choices
504    assert "bar" in choices
505
506
507def test_case_insensitive_choice(runner):
508    @click.command()
509    @click.option("--foo", type=click.Choice(["Orange", "Apple"], case_sensitive=False))
510    def cmd(foo):
511        click.echo(foo)
512
513    result = runner.invoke(cmd, ["--foo", "apple"])
514    assert result.exit_code == 0
515    assert result.output == "Apple\n"
516
517    result = runner.invoke(cmd, ["--foo", "oRANGe"])
518    assert result.exit_code == 0
519    assert result.output == "Orange\n"
520
521    result = runner.invoke(cmd, ["--foo", "Apple"])
522    assert result.exit_code == 0
523    assert result.output == "Apple\n"
524
525    @click.command()
526    @click.option("--foo", type=click.Choice(["Orange", "Apple"]))
527    def cmd2(foo):
528        click.echo(foo)
529
530    result = runner.invoke(cmd2, ["--foo", "apple"])
531    assert result.exit_code == 2
532
533    result = runner.invoke(cmd2, ["--foo", "oRANGe"])
534    assert result.exit_code == 2
535
536    result = runner.invoke(cmd2, ["--foo", "Apple"])
537    assert result.exit_code == 0
538
539
540def test_case_insensitive_choice_returned_exactly(runner):
541    @click.command()
542    @click.option("--foo", type=click.Choice(["Orange", "Apple"], case_sensitive=False))
543    def cmd(foo):
544        click.echo(foo)
545
546    result = runner.invoke(cmd, ["--foo", "apple"])
547    assert result.exit_code == 0
548    assert result.output == "Apple\n"
549
550
551def test_option_help_preserve_paragraphs(runner):
552    @click.command()
553    @click.option(
554        "-C",
555        "--config",
556        type=click.Path(),
557        help="""Configuration file to use.
558
559        If not given, the environment variable CONFIG_FILE is consulted
560        and used if set. If neither are given, a default configuration
561        file is loaded.""",
562    )
563    def cmd(config):
564        pass
565
566    result = runner.invoke(cmd, ["--help"])
567    assert result.exit_code == 0
568    i = " " * 21
569    assert (
570        "  -C, --config PATH  Configuration file to use.\n"
571        f"{i}\n"
572        f"{i}If not given, the environment variable CONFIG_FILE is\n"
573        f"{i}consulted and used if set. If neither are given, a default\n"
574        f"{i}configuration file is loaded."
575    ) in result.output
576
577
578def test_argument_custom_class(runner):
579    class CustomArgument(click.Argument):
580        def get_default(self, ctx, call=True):
581            """a dumb override of a default value for testing"""
582            return "I am a default"
583
584    @click.command()
585    @click.argument("testarg", cls=CustomArgument, default="you wont see me")
586    def cmd(testarg):
587        click.echo(testarg)
588
589    result = runner.invoke(cmd)
590    assert "I am a default" in result.output
591    assert "you wont see me" not in result.output
592
593
594def test_option_custom_class(runner):
595    class CustomOption(click.Option):
596        def get_help_record(self, ctx):
597            """a dumb override of a help text for testing"""
598            return ("--help", "I am a help text")
599
600    @click.command()
601    @click.option("--testoption", cls=CustomOption, help="you wont see me")
602    def cmd(testoption):
603        click.echo(testoption)
604
605    result = runner.invoke(cmd, ["--help"])
606    assert "I am a help text" in result.output
607    assert "you wont see me" not in result.output
608
609
610def test_option_custom_class_reusable(runner):
611    """Ensure we can reuse a custom class option. See Issue #926"""
612
613    class CustomOption(click.Option):
614        def get_help_record(self, ctx):
615            """a dumb override of a help text for testing"""
616            return ("--help", "I am a help text")
617
618    # Assign to a variable to re-use the decorator.
619    testoption = click.option("--testoption", cls=CustomOption, help="you wont see me")
620
621    @click.command()
622    @testoption
623    def cmd1(testoption):
624        click.echo(testoption)
625
626    @click.command()
627    @testoption
628    def cmd2(testoption):
629        click.echo(testoption)
630
631    # Both of the commands should have the --help option now.
632    for cmd in (cmd1, cmd2):
633
634        result = runner.invoke(cmd, ["--help"])
635        assert "I am a help text" in result.output
636        assert "you wont see me" not in result.output
637
638
639def test_bool_flag_with_type(runner):
640    @click.command()
641    @click.option("--shout/--no-shout", default=False, type=bool)
642    def cmd(shout):
643        pass
644
645    result = runner.invoke(cmd)
646    assert not result.exception
647
648
649def test_aliases_for_flags(runner):
650    @click.command()
651    @click.option("--warnings/--no-warnings", " /-W", default=True)
652    def cli(warnings):
653        click.echo(warnings)
654
655    result = runner.invoke(cli, ["--warnings"])
656    assert result.output == "True\n"
657    result = runner.invoke(cli, ["--no-warnings"])
658    assert result.output == "False\n"
659    result = runner.invoke(cli, ["-W"])
660    assert result.output == "False\n"
661
662    @click.command()
663    @click.option("--warnings/--no-warnings", "-w", default=True)
664    def cli_alt(warnings):
665        click.echo(warnings)
666
667    result = runner.invoke(cli_alt, ["--warnings"])
668    assert result.output == "True\n"
669    result = runner.invoke(cli_alt, ["--no-warnings"])
670    assert result.output == "False\n"
671    result = runner.invoke(cli_alt, ["-w"])
672    assert result.output == "True\n"
673
674
675@pytest.mark.parametrize(
676    "option_args,expected",
677    [
678        (["--aggressive", "--all", "-a"], "aggressive"),
679        (["--first", "--second", "--third", "-a", "-b", "-c"], "first"),
680        (["--apple", "--banana", "--cantaloupe", "-a", "-b", "-c"], "apple"),
681        (["--cantaloupe", "--banana", "--apple", "-c", "-b", "-a"], "cantaloupe"),
682        (["-a", "-b", "-c"], "a"),
683        (["-c", "-b", "-a"], "c"),
684        (["-a", "--apple", "-b", "--banana", "-c", "--cantaloupe"], "apple"),
685        (["-c", "-a", "--cantaloupe", "-b", "--banana", "--apple"], "cantaloupe"),
686        (["--from", "-f", "_from"], "_from"),
687        (["--return", "-r", "_ret"], "_ret"),
688    ],
689)
690def test_option_names(runner, option_args, expected):
691    @click.command()
692    @click.option(*option_args, is_flag=True)
693    def cmd(**kwargs):
694        click.echo(str(kwargs[expected]))
695
696    assert cmd.params[0].name == expected
697
698    for form in option_args:
699        if form.startswith("-"):
700            result = runner.invoke(cmd, [form])
701            assert result.output == "True\n"
702
703
704def test_flag_duplicate_names(runner):
705    with pytest.raises(ValueError, match="cannot use the same flag for true/false"):
706        click.Option(["--foo/--foo"], default=False)
707
708
709@pytest.mark.parametrize(("default", "expect"), [(False, "no-cache"), (True, "cache")])
710def test_show_default_boolean_flag_name(runner, default, expect):
711    """When a boolean flag has distinct True/False opts, it should show
712    the default opt name instead of the default value. It should only
713    show one name even if multiple are declared.
714    """
715    opt = click.Option(
716        ("--cache/--no-cache", "--c/--nc"),
717        default=default,
718        show_default=True,
719        help="Enable/Disable the cache.",
720    )
721    ctx = click.Context(click.Command("test"))
722    message = opt.get_help_record(ctx)[1]
723    assert f"[default: {expect}]" in message
724
725
726def test_show_default_boolean_flag_value(runner):
727    """When a boolean flag only has one opt, it will show the default
728    value, not the opt name.
729    """
730    opt = click.Option(
731        ("--cache",), is_flag=True, show_default=True, help="Enable the cache."
732    )
733    ctx = click.Context(click.Command("test"))
734    message = opt.get_help_record(ctx)[1]
735    assert "[default: False]" in message
736
737
738def test_show_default_string(runner):
739    """When show_default is a string show that value as default."""
740    opt = click.Option(["--limit"], show_default="unlimited")
741    ctx = click.Context(click.Command("cli"))
742    message = opt.get_help_record(ctx)[1]
743    assert "[default: (unlimited)]" in message
744
745
746def test_do_not_show_no_default(runner):
747    """When show_default is True and no default is set do not show None."""
748    opt = click.Option(["--limit"], show_default=True)
749    ctx = click.Context(click.Command("cli"))
750    message = opt.get_help_record(ctx)[1]
751    assert "[default: None]" not in message
752
753
754def test_do_not_show_default_empty_multiple():
755    """When show_default is True and multiple=True is set, it should not
756    print empty default value in --help output.
757    """
758    opt = click.Option(["-a"], multiple=True, help="values", show_default=True)
759    ctx = click.Context(click.Command("cli"))
760    message = opt.get_help_record(ctx)[1]
761    assert message == "values"
762
763
764@pytest.mark.parametrize(
765    ("args", "expect"),
766    [
767        (None, (None, None, ())),
768        (["--opt"], ("flag", None, ())),
769        (["--opt", "-a", 42], ("flag", "42", ())),
770        (["--opt", "test", "-a", 42], ("test", "42", ())),
771        (["--opt=test", "-a", 42], ("test", "42", ())),
772        (["-o"], ("flag", None, ())),
773        (["-o", "-a", 42], ("flag", "42", ())),
774        (["-o", "test", "-a", 42], ("test", "42", ())),
775        (["-otest", "-a", 42], ("test", "42", ())),
776        (["a", "b", "c"], (None, None, ("a", "b", "c"))),
777        (["--opt", "a", "b", "c"], ("a", None, ("b", "c"))),
778        (["--opt", "test"], ("test", None, ())),
779        (["-otest", "a", "b", "c"], ("test", None, ("a", "b", "c"))),
780        (["--opt=test", "a", "b", "c"], ("test", None, ("a", "b", "c"))),
781    ],
782)
783def test_option_with_optional_value(runner, args, expect):
784    @click.command()
785    @click.option("-o", "--opt", is_flag=False, flag_value="flag")
786    @click.option("-a")
787    @click.argument("b", nargs=-1)
788    def cli(opt, a, b):
789        return opt, a, b
790
791    result = runner.invoke(cli, args, standalone_mode=False, catch_exceptions=False)
792    assert result.return_value == expect
793
794
795def test_multiple_option_with_optional_value(runner):
796    cli = click.Command(
797        "cli",
798        params=[
799            click.Option(["-f"], is_flag=False, flag_value="flag", multiple=True),
800            click.Option(["-a"]),
801            click.Argument(["b"], nargs=-1),
802        ],
803        callback=lambda **kwargs: kwargs,
804    )
805    result = runner.invoke(
806        cli,
807        ["-f", "-f", "other", "-f", "-a", "1", "a", "b"],
808        standalone_mode=False,
809        catch_exceptions=False,
810    )
811    assert result.return_value == {
812        "f": ("flag", "other", "flag"),
813        "a": "1",
814        "b": ("a", "b"),
815    }
816
817
818def test_type_from_flag_value():
819    param = click.Option(["-a", "x"], default=True, flag_value=4)
820    assert param.type is click.INT
821    param = click.Option(["-b", "x"], flag_value=8)
822    assert param.type is click.INT
823
824
825@pytest.mark.parametrize(
826    ("option", "expected"),
827    [
828        # Not boolean flags
829        pytest.param(Option(["-a"], type=int), False, id="int option"),
830        pytest.param(Option(["-a"], type=bool), False, id="bool non-flag [None]"),
831        pytest.param(Option(["-a"], default=True), False, id="bool non-flag [True]"),
832        pytest.param(Option(["-a"], default=False), False, id="bool non-flag [False]"),
833        pytest.param(Option(["-a"], flag_value=1), False, id="non-bool flag_value"),
834        # Boolean flags
835        pytest.param(Option(["-a"], is_flag=True), True, id="is_flag=True"),
836        pytest.param(Option(["-a/-A"]), True, id="secondary option [implicit flag]"),
837        pytest.param(Option(["-a"], flag_value=True), True, id="bool flag_value"),
838    ],
839)
840def test_is_bool_flag_is_correctly_set(option, expected):
841    assert option.is_bool_flag is expected
842