1// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
2// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
3// Ana Hobden (@hoverbear) <operator@hoverbear.org>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10//
11// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
12// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
13// MIT/Apache 2.0 license.
14
15use crate::utils;
16
17use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
18
19#[test]
20fn doc_comments() {
21    /// Lorem ipsum
22    #[derive(Parser, PartialEq, Debug)]
23    struct LoremIpsum {
24        /// Fooify a bar
25        /// and a baz
26        #[arg(short, long)]
27        foo: bool,
28    }
29
30    let help = utils::get_long_help::<LoremIpsum>();
31    assert!(help.contains("Lorem ipsum"));
32    assert!(help.contains("Fooify a bar and a baz"));
33}
34
35#[test]
36fn help_is_better_than_comments() {
37    /// Lorem ipsum
38    #[derive(Parser, PartialEq, Debug)]
39    #[command(name = "lorem-ipsum", about = "Dolor sit amet")]
40    struct LoremIpsum {
41        /// Fooify a bar
42        #[arg(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")]
43        foo: bool,
44    }
45
46    let help = utils::get_long_help::<LoremIpsum>();
47    assert!(help.contains("Dolor sit amet"));
48    assert!(!help.contains("Lorem ipsum"));
49    assert!(help.contains("DO NOT PASS A BAR"));
50}
51
52#[test]
53fn empty_line_in_doc_comment_is_double_linefeed() {
54    /// Foo.
55    ///
56    /// Bar
57    #[derive(Parser, PartialEq, Debug)]
58    #[command(name = "lorem-ipsum")]
59    struct LoremIpsum {}
60
61    let help = utils::get_long_help::<LoremIpsum>();
62    assert!(help.starts_with(
63        "\
64Foo.
65
66Bar
67
68Usage:"
69    ));
70}
71
72#[test]
73fn field_long_doc_comment_both_help_long_help() {
74    /// Lorem ipsumclap
75    #[derive(Parser, PartialEq, Debug)]
76    #[command(name = "lorem-ipsum", about = "Dolor sit amet")]
77    struct LoremIpsum {
78        /// Dot is removed from multiline comments.
79        ///
80        /// Long help
81        #[arg(long)]
82        foo: bool,
83
84        /// Dot is removed from one short comment.
85        #[arg(long)]
86        bar: bool,
87    }
88
89    let short_help = utils::get_help::<LoremIpsum>();
90    let long_help = utils::get_long_help::<LoremIpsum>();
91
92    assert!(short_help.contains("Dot is removed from one short comment"));
93    assert!(!short_help.contains("Dot is removed from one short comment."));
94    assert!(short_help.contains("Dot is removed from multiline comments"));
95    assert!(!short_help.contains("Dot is removed from multiline comments."));
96    assert!(long_help.contains("Long help"));
97    assert!(!short_help.contains("Long help"));
98}
99
100#[test]
101fn top_long_doc_comment_both_help_long_help() {
102    /// Lorem ipsumclap
103    #[derive(Parser, Debug)]
104    #[command(name = "lorem-ipsum", about = "Dolor sit amet")]
105    struct LoremIpsum {
106        #[command(subcommand)]
107        foo: SubCommand,
108    }
109
110    #[derive(Parser, Debug)]
111    pub enum SubCommand {
112        /// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES
113        ///
114        /// Or something else
115        Foo {
116            #[arg(help = "foo")]
117            bars: String,
118        },
119    }
120
121    let short_help = utils::get_help::<LoremIpsum>();
122    let long_help = utils::get_subcommand_long_help::<LoremIpsum>("foo");
123
124    assert!(!short_help.contains("Or something else"));
125    assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES"));
126    assert!(long_help.contains("Or something else"));
127}
128
129#[test]
130fn verbatim_doc_comment() {
131    /// DANCE!
132    ///
133    ///                    ()
134    ///                    |
135    ///               (   ()   )
136    ///     ) ________    //  )
137    ///  ()  |\       \  //
138    /// ( \\__ \ ______\//
139    ///    \__) |       |
140    ///      |  |       |
141    ///       \ |       |
142    ///        \|_______|
143    ///        //    \\
144    ///       ((     ||
145    ///        \\    ||
146    ///      ( ()    ||
147    ///       (      () ) )
148    #[derive(Parser, Debug)]
149    #[command(verbatim_doc_comment)]
150    struct SeeFigure1 {
151        #[arg(long)]
152        foo: bool,
153    }
154
155    let help = utils::get_long_help::<SeeFigure1>();
156    let sample = r#"
157                   ()
158                   |
159              (   ()   )
160    ) ________    //  )
161 ()  |\       \  //
162( \\__ \ ______\//
163   \__) |       |
164     |  |       |
165      \ |       |
166       \|_______|
167       //    \\
168      ((     ||
169       \\    ||
170     ( ()    ||
171      (      () ) )"#;
172
173    assert!(help.contains(sample))
174}
175
176#[test]
177fn verbatim_doc_comment_field() {
178    #[derive(Parser, Debug)]
179    struct Command {
180        /// This help ends in a period.
181        #[arg(long, verbatim_doc_comment)]
182        foo: bool,
183        /// This help does not end in a period.
184        #[arg(long)]
185        bar: bool,
186    }
187
188    let help = utils::get_long_help::<Command>();
189
190    assert!(help.contains("This help ends in a period."));
191    assert!(help.contains("This help does not end in a period"));
192}
193
194#[test]
195fn multiline_separates_default() {
196    #[derive(Parser, Debug)]
197    struct Command {
198        /// Multiline
199        ///
200        /// Doc comment
201        #[arg(long, default_value = "x")]
202        x: String,
203    }
204
205    let help = utils::get_long_help::<Command>();
206    assert!(!help.contains("Doc comment [default"));
207    assert!(help.lines().any(|s| s.trim().starts_with("[default")));
208
209    // The short help should still have the default on the same line
210    let help = utils::get_help::<Command>();
211    assert!(help.contains("Multiline [default"));
212}
213
214#[test]
215fn value_enum_multiline_doc_comment() {
216    #[derive(Parser, Debug)]
217    struct Command {
218        x: LoremIpsum,
219    }
220
221    #[derive(ValueEnum, Clone, PartialEq, Debug)]
222    enum LoremIpsum {
223        /// Doc comment summary
224        ///
225        /// The doc comment body is ignored
226        Bar,
227    }
228
229    let help = utils::get_long_help::<Command>();
230
231    assert!(help.contains("Doc comment summary"));
232
233    // There is no long help text for possible values. The long help only contains the summary.
234    assert!(!help.contains("The doc comment body is ignored"));
235}
236
237#[test]
238fn doc_comment_about_handles_both_abouts() {
239    /// Opts doc comment summary
240    #[derive(Parser, Debug)]
241    pub struct Opts {
242        #[command(subcommand)]
243        pub cmd: Sub,
244    }
245
246    /// Sub doc comment summary
247    ///
248    /// Sub doc comment body
249    #[derive(Parser, PartialEq, Eq, Debug)]
250    pub enum Sub {
251        Compress { output: String },
252    }
253
254    let cmd = Opts::command();
255    assert_eq!(
256        cmd.get_about().map(|s| s.to_string()),
257        Some("Opts doc comment summary".to_owned())
258    );
259    // clap will fallback to `about` on `None`.  The main care about is not providing a `Sub` doc
260    // comment.
261    assert_eq!(cmd.get_long_about(), None);
262}
263
264#[test]
265fn respect_subcommand_doc_comment() {
266    #[derive(Parser, Debug)]
267    pub enum Cmd {
268        /// For child
269        #[command(subcommand)]
270        Child(Child),
271    }
272
273    #[derive(Subcommand, Debug)]
274    pub enum Child {
275        One,
276        Twp,
277    }
278
279    const OUTPUT: &str = "\
280Usage: cmd <COMMAND>
281
282Commands:
283  child  For child
284  help   Print this message or the help of the given subcommand(s)
285
286Options:
287  -h, --help  Print help
288";
289    utils::assert_output::<Cmd>("cmd --help", OUTPUT, false);
290}
291
292#[test]
293fn force_long_help() {
294    /// Lorem ipsum
295    #[derive(Parser, PartialEq, Debug)]
296    struct LoremIpsum {
297        /// Fooify a bar
298        /// and a baz.
299        #[arg(short, long, long_help)]
300        foo: bool,
301    }
302
303    let help = utils::get_long_help::<LoremIpsum>();
304    assert!(help.contains("Fooify a bar and a baz."));
305}
306