// Copyright 2024 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::ffi::OsStr; use clap_complete::Shell;use indoc::indoc;use itertools::Itertools as _;use test_case::test_case; use crate::common::CommandOutput;use crate::common::TestEnvironment;use crate::common::TestWorkDir; impl TestEnvironment { /// Runs `jj` as if shell completion had been triggered for the argument of /// the given index. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_at<I>(&self, shell: Shell, index: usize, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { self.work_dir("").complete_at(shell, index, args) } /// Run `jj` as if fish shell completion had been triggered at the last item /// in `args`. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_fish<I>(&self, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { self.work_dir("").complete_fish(args) }} impl TestWorkDir<'_> { /// Runs `jj` as if shell completion had been triggered for the argument of /// the given index. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_at<I>(&self, shell: Shell, index: usize, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { let args = args.into_iter(); assert!( index <= args.len(), "index out of bounds: append empty string to complete after the last argument" ); self.run_jj_with(|cmd| { let cmd = cmd.env("COMPLETE", shell.to_string()).args(["--", "jj"]); match shell { // Bash passes the whole command line except for intermediate empty tokens but // preserves trailing empty tokens: // `jj log <CURSOR>` => ["--", "jj", "log", ""] // with _CLAP_COMPLETE_INDEX=2 // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // `jj log --no-graph<CURSOR>` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // (indistinguishable from the above) // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph", ""] // with _CLAP_COMPLETE_INDEX=3 Shell::Bash => { cmd.env("_CLAP_COMPLETE_INDEX", index.to_string()) .args(args.coalesce(|a, b| { if a.as_ref().is_empty() { Ok(b) } else { Err((a, b)) } })) } // Zsh passes the whole command line except for empty tokens, including trailing // empty tokens: // `jj log <CURSOR>` => ["--", "jj", "log"] // with _CLAP_COMPLETE_INDEX=2 // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // `jj log --no-graph<CURSOR>` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // (indistinguishable from the above) // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=3 Shell::Zsh => cmd .env("_CLAP_COMPLETE_INDEX", index.to_string()) .args(args.filter(|a| !a.as_ref().is_empty())), // Fish truncates the command line at the cursor; empty tokens are preserved: // `jj log <CURSOR>` => ["--", "jj", "log", ""] // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", ""] // `jj log --no-graph<CURSOR>` => ["--", "jj", "log", "--no-graph"] // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph", ""] Shell::Fish => cmd.args(args.take(index)), _ => todo!("{shell} completion behavior not implemented yet"), } }) } /// Run `jj` as if fish shell completion had been triggered at the last item /// in `args`. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_fish<I>(&self, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { let args = args.into_iter(); self.complete_at(Shell::Fish, args.len(), args) }} #[test]fn test_bookmark_names() { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); test_env .run_jj_in(".", ["git", "init", "--colocate", "origin"]) .success(); let origin_dir = test_env.work_dir("origin"); test_env .run_jj_in(".", ["git", "init", "--colocate", "upstream"]) .success(); let _upstream_dir = test_env.work_dir("upstream"); work_dir .run_jj(["bookmark", "create", "-r@", "aaa-local"]) .success(); work_dir .run_jj(["bookmark", "create", "-r@", "bbb-local"]) .success(); // add various remote branches work_dir .run_jj(["git", "remote", "add", "origin", "../origin"]) .success(); work_dir .run_jj(["git", "remote", "add", "upstream", "../upstream"]) .success(); work_dir .run_jj(["bookmark", "create", "-r@", "aaa-tracked"]) .success(); work_dir .run_jj(["desc", "-r", "aaa-tracked", "-m", "x"]) .success(); work_dir .run_jj(["bookmark", "create", "-r@", "bbb-tracked"]) .success(); work_dir .run_jj(["desc", "-r", "bbb-tracked", "-m", "x"]) .success(); work_dir .run_jj(["bookmark", "track", "--remote=origin", "*-tracked"]) .success(); work_dir .run_jj(["bookmark", "track", "--remote=upstream", "aaa-tracked"]) .success(); work_dir .run_jj(["git", "push", "--remote=origin", "--tracked"]) .success(); work_dir .run_jj(["git", "push", "--remote=upstream", "--tracked"]) .success(); origin_dir .run_jj(["bookmark", "create", "-r@", "aaa-untracked"]) .success(); origin_dir .run_jj(["desc", "-r", "aaa-untracked", "-m", "x"]) .success(); origin_dir .run_jj(["bookmark", "create", "-r@", "bbb-untracked"]) .success(); origin_dir .run_jj(["desc", "-r", "bbb-untracked", "-m", "x"]) .success(); work_dir.run_jj(["git", "fetch", "--all-remotes"]).success(); insta::assert_snapshot!(work_dir.run_jj(["bookmark", "list", "--all"]), @" aaa-local: qpvuntsm fe38a82d (empty) x aaa-tracked: qpvuntsm fe38a82d (empty) x @origin: qpvuntsm fe38a82d (empty) x @upstream: qpvuntsm fe38a82d (empty) x aaa-untracked@origin: rlvkpnrz 434ae005 (empty) x bbb-local: qpvuntsm fe38a82d (empty) x bbb-tracked: qpvuntsm fe38a82d (empty) x @origin: qpvuntsm fe38a82d (empty) x bbb-untracked@origin: rlvkpnrz 434ae005 (empty) x [EOF] "); // Every shell hook is a little different, e.g. the zsh hooks add some // additional environment variables. But this is irrelevant for the purpose // of testing our own logic, so it's fine to test a single shell only. let work_dir = test_env.work_dir("repo"); let output = work_dir.complete_fish(["bookmark", "rename", ""]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x bbb-local x bbb-tracked x --overwrite-existing Allow renaming even if the new bookmark name already exists --help Print help (see more with '--help') --repository Path to repository to operate on --ignore-working-copy Don't snapshot the working copy, and don't update it --no-integrate-operation Run the command as usual but don't integrate any operations --ignore-immutable Allow rewriting immutable commits --at-operation Operation to load the repo at --debug Enable debug logging --color When to colorize output --quiet Silence non-primary command output --no-pager Disable the pager --config Additional configuration options (can be repeated) --config-file Additional configuration files (can be repeated) [EOF] "); let output = work_dir.complete_fish(["bookmark", "rename", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "delete", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "forget", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x aaa-untracked [EOF] "); let output = work_dir.complete_fish(["bookmark", "list", "--bookmark", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x aaa-untracked [EOF] "); let output = work_dir.complete_fish(["bookmark", "move", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "set", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "track", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-untracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "untrack", "a"]); insta::assert_snapshot!(output, @" aaa-tracked x [EOF] "); // TODO: Make it so this only lists untracked remotes let output = work_dir.complete_fish(["bookmark", "track", "a", "--remote", ""]); insta::assert_snapshot!(output, @" origin upstream [EOF] "); // TODO: Make it so this only lists tracked remotes let output = work_dir.complete_fish(["bookmark", "untrack", "a", "--remote", ""]); insta::assert_snapshot!(output, @" origin upstream [EOF] "); let output = work_dir.complete_fish(["git", "push", "-b", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["git", "fetch", "-b", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x aaa-untracked [EOF] ");} #[test]fn test_tag_names() { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); work_dir.run_jj(["commit", "-mcommit1"]).success(); work_dir .run_jj(["tag", "set", "-r@-", "aaa-local"]) .success(); work_dir .run_jj(["tag", "set", "-r@-", "bbb-local"]) .success(); let output = work_dir.complete_fish(["tag", "set", "a"]); insta::assert_snapshot!(output, @" aaa-local commit1 [EOF] "); let output = work_dir.complete_fish(["tag", "delete", "b"]); insta::assert_snapshot!(output, @" bbb-local commit1 [EOF] ");} #[test]fn test_global_arg_repository_is_respected() { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); work_dir .run_jj(["bookmark", "create", "-r@", "aaa"]) .success(); let output = test_env.complete_fish(["--repository", "repo", "bookmark", "rename", "a"]); insta::assert_snapshot!(output, @" aaa (no description set) [EOF] ");} #[test_case(Shell::Bash; "bash")]#[test_case(Shell::Zsh; "zsh")]#[test_case(Shell::Fish; "fish")]fn test_aliases_are_resolved(shell: Shell) { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); work_dir .run_jj(["bookmark", "create", "-r@", "aaa"]) .success(); // user config alias test_env.add_config(r#"aliases.b = ["bookmark"]"#); test_env.add_config(r#"aliases.rlog = ["log", "--reversed"]"#); // repo config alias work_dir .run_jj(["config", "set", "--repo", "aliases.b2", "['bookmark']"]) .success(); let output = work_dir.complete_at(shell, 3, ["b", "rename", "a"]); match shell { Shell::Bash => { insta::assert_snapshot!(output, @"aaa[EOF]"); } Shell::Zsh => { insta::assert_snapshot!(output, @"aaa:(no description set)[EOF]"); } Shell::Fish => { insta::assert_snapshot!(output, @" aaa (no description set) [EOF] "); } _ => unimplemented!("unexpected shell '{shell}'"), } let output = work_dir.complete_at(shell, 3, ["b2", "rename", "a"]); match shell { Shell::Bash => { insta::assert_snapshot!(output, @"aaa[EOF]"); } Shell::Zsh => { insta::assert_snapshot!(output, @"aaa:(no description set)[EOF]"); } Shell::Fish => { insta::assert_snapshot!(output, @" aaa (no description set) [EOF] "); } _ => unimplemented!("unexpected shell '{shell}'"), } let output = work_dir.complete_at(shell, 2, ["rlog", "--rev"]); match shell { Shell::Bash => { insta::assert_snapshot!(output, @" --revision --reversed[EOF] "); } Shell::Zsh => { insta::assert_snapshot!(output, @" --revision:Which revisions to show --reversed:Show revisions in the opposite order (older revisions first)[EOF] "); } Shell::Fish => { insta::assert_snapshot!(output, @" --revision Which revisions to show --reversed Show revisions in the opposite order (older revisions first) [EOF] "); } _ => unimplemented!("unexpected shell '{shell}'"), }} #[test]fn test_completions_are_generated() { let mut test_env = TestEnvironment::default(); test_env.add_env_var("COMPLETE", "fish"); let mut insta_settings = insta::Settings::clone_current(); insta_settings.add_filter(r"(--arguments) .*", "$1 .."); // omit path to jj binary let _guard = insta_settings.bind_to_scope(); let output = test_env.run_jj_in(".", [""; 0]); insta::assert_snapshot!(output, @" complete --keep-order --exclusive --command jj --arguments .. [EOF] "); let output = test_env.run_jj_in(".", ["--"]); insta::assert_snapshot!(output, @" complete --keep-order --exclusive --command jj --arguments .. [EOF] ");} #[test]fn test_bad_complete_env() { let mut test_env = TestEnvironment::default(); test_env.add_env_var("COMPLETE", "badshell"); let output = test_env.run_jj_in(".", [""; 0]); insta::assert_snapshot!(output, @" ------- stderr ------- error: unknown shell `badshell`, expected one of `bash`, `elvish`, `fish`, `powershell`, `zsh`[EOF] [exit status: 2] "); // Empty value of COMPLETE is ignored as is the value of "0". This could // change if `clap` changes the way it interprets an empty COMPLETE env var. // // In other words, jj runs normally instead of returning completions. We get // an error because the default jj command needs to be run in a jj repo. test_env.add_env_var("COMPLETE", ""); let output = test_env.run_jj_in(".", [""; 0]); insta::assert_snapshot!(output, @r#" ------- stderr ------- Hint: Use `jj -h` for a list of available commands. Run `jj config set --user ui.default-command log` to disable this message. Error: There is no jj repo in "." [EOF] [exit status: 1]Lines 1–500 of 2143. Load more
// Copyright 2024 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::ffi::OsStr; use clap_complete::Shell; use indoc::indoc; use itertools::Itertools as _; use test_case::test_case; use crate::common::CommandOutput; use crate::common::TestEnvironment; use crate::common::TestWorkDir; impl TestEnvironment { /// Runs `jj` as if shell completion had been triggered for the argument of /// the given index. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_at<I>(&self, shell: Shell, index: usize, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { self.work_dir("").complete_at(shell, index, args) } /// Run `jj` as if fish shell completion had been triggered at the last item /// in `args`. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_fish<I>(&self, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { self.work_dir("").complete_fish(args) } } impl TestWorkDir<'_> { /// Runs `jj` as if shell completion had been triggered for the argument of /// the given index. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_at<I>(&self, shell: Shell, index: usize, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { let args = args.into_iter(); assert!( index <= args.len(), "index out of bounds: append empty string to complete after the last argument" ); self.run_jj_with(|cmd| { let cmd = cmd.env("COMPLETE", shell.to_string()).args(["--", "jj"]); match shell { // Bash passes the whole command line except for intermediate empty tokens but // preserves trailing empty tokens: // `jj log <CURSOR>` => ["--", "jj", "log", ""] // with _CLAP_COMPLETE_INDEX=2 // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // `jj log --no-graph<CURSOR>` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // (indistinguishable from the above) // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph", ""] // with _CLAP_COMPLETE_INDEX=3 Shell::Bash => { cmd.env("_CLAP_COMPLETE_INDEX", index.to_string()) .args(args.coalesce(|a, b| { if a.as_ref().is_empty() { Ok(b) } else { Err((a, b)) } })) } // Zsh passes the whole command line except for empty tokens, including trailing // empty tokens: // `jj log <CURSOR>` => ["--", "jj", "log"] // with _CLAP_COMPLETE_INDEX=2 // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // `jj log --no-graph<CURSOR>` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=2 // (indistinguishable from the above) // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph"] // with _CLAP_COMPLETE_INDEX=3 Shell::Zsh => cmd .env("_CLAP_COMPLETE_INDEX", index.to_string()) .args(args.filter(|a| !a.as_ref().is_empty())), // Fish truncates the command line at the cursor; empty tokens are preserved: // `jj log <CURSOR>` => ["--", "jj", "log", ""] // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", ""] // `jj log --no-graph<CURSOR>` => ["--", "jj", "log", "--no-graph"] // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph", ""] Shell::Fish => cmd.args(args.take(index)), _ => todo!("{shell} completion behavior not implemented yet"), } }) } /// Run `jj` as if fish shell completion had been triggered at the last item /// in `args`. #[must_use = "either snapshot the output or assert the exit status with .success()"] fn complete_fish<I>(&self, args: I) -> CommandOutput where I: IntoIterator, I::IntoIter: ExactSizeIterator, I::Item: AsRef<OsStr>, { let args = args.into_iter(); self.complete_at(Shell::Fish, args.len(), args) } } #[test] fn test_bookmark_names() { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); test_env .run_jj_in(".", ["git", "init", "--colocate", "origin"]) .success(); let origin_dir = test_env.work_dir("origin"); test_env .run_jj_in(".", ["git", "init", "--colocate", "upstream"]) .success(); let _upstream_dir = test_env.work_dir("upstream"); work_dir .run_jj(["bookmark", "create", "-r@", "aaa-local"]) .success(); work_dir .run_jj(["bookmark", "create", "-r@", "bbb-local"]) .success(); // add various remote branches work_dir .run_jj(["git", "remote", "add", "origin", "../origin"]) .success(); work_dir .run_jj(["git", "remote", "add", "upstream", "../upstream"]) .success(); work_dir .run_jj(["bookmark", "create", "-r@", "aaa-tracked"]) .success(); work_dir .run_jj(["desc", "-r", "aaa-tracked", "-m", "x"]) .success(); work_dir .run_jj(["bookmark", "create", "-r@", "bbb-tracked"]) .success(); work_dir .run_jj(["desc", "-r", "bbb-tracked", "-m", "x"]) .success(); work_dir .run_jj(["bookmark", "track", "--remote=origin", "*-tracked"]) .success(); work_dir .run_jj(["bookmark", "track", "--remote=upstream", "aaa-tracked"]) .success(); work_dir .run_jj(["git", "push", "--remote=origin", "--tracked"]) .success(); work_dir .run_jj(["git", "push", "--remote=upstream", "--tracked"]) .success(); origin_dir .run_jj(["bookmark", "create", "-r@", "aaa-untracked"]) .success(); origin_dir .run_jj(["desc", "-r", "aaa-untracked", "-m", "x"]) .success(); origin_dir .run_jj(["bookmark", "create", "-r@", "bbb-untracked"]) .success(); origin_dir .run_jj(["desc", "-r", "bbb-untracked", "-m", "x"]) .success(); work_dir.run_jj(["git", "fetch", "--all-remotes"]).success(); insta::assert_snapshot!(work_dir.run_jj(["bookmark", "list", "--all"]), @" aaa-local: qpvuntsm fe38a82d (empty) x aaa-tracked: qpvuntsm fe38a82d (empty) x @origin: qpvuntsm fe38a82d (empty) x @upstream: qpvuntsm fe38a82d (empty) x aaa-untracked@origin: rlvkpnrz 434ae005 (empty) x bbb-local: qpvuntsm fe38a82d (empty) x bbb-tracked: qpvuntsm fe38a82d (empty) x @origin: qpvuntsm fe38a82d (empty) x bbb-untracked@origin: rlvkpnrz 434ae005 (empty) x [EOF] "); // Every shell hook is a little different, e.g. the zsh hooks add some // additional environment variables. But this is irrelevant for the purpose // of testing our own logic, so it's fine to test a single shell only. let work_dir = test_env.work_dir("repo"); let output = work_dir.complete_fish(["bookmark", "rename", ""]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x bbb-local x bbb-tracked x --overwrite-existing Allow renaming even if the new bookmark name already exists --help Print help (see more with '--help') --repository Path to repository to operate on --ignore-working-copy Don't snapshot the working copy, and don't update it --no-integrate-operation Run the command as usual but don't integrate any operations --ignore-immutable Allow rewriting immutable commits --at-operation Operation to load the repo at --debug Enable debug logging --color When to colorize output --quiet Silence non-primary command output --no-pager Disable the pager --config Additional configuration options (can be repeated) --config-file Additional configuration files (can be repeated) [EOF] "); let output = work_dir.complete_fish(["bookmark", "rename", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "delete", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "forget", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x aaa-untracked [EOF] "); let output = work_dir.complete_fish(["bookmark", "list", "--bookmark", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x aaa-untracked [EOF] "); let output = work_dir.complete_fish(["bookmark", "move", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "set", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "track", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-untracked x [EOF] "); let output = work_dir.complete_fish(["bookmark", "untrack", "a"]); insta::assert_snapshot!(output, @" aaa-tracked x [EOF] "); // TODO: Make it so this only lists untracked remotes let output = work_dir.complete_fish(["bookmark", "track", "a", "--remote", ""]); insta::assert_snapshot!(output, @" origin upstream [EOF] "); // TODO: Make it so this only lists tracked remotes let output = work_dir.complete_fish(["bookmark", "untrack", "a", "--remote", ""]); insta::assert_snapshot!(output, @" origin upstream [EOF] "); let output = work_dir.complete_fish(["git", "push", "-b", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x [EOF] "); let output = work_dir.complete_fish(["git", "fetch", "-b", "a"]); insta::assert_snapshot!(output, @" aaa-local x aaa-tracked x aaa-untracked [EOF] "); } #[test] fn test_tag_names() { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); work_dir.run_jj(["commit", "-mcommit1"]).success(); work_dir .run_jj(["tag", "set", "-r@-", "aaa-local"]) .success(); work_dir .run_jj(["tag", "set", "-r@-", "bbb-local"]) .success(); let output = work_dir.complete_fish(["tag", "set", "a"]); insta::assert_snapshot!(output, @" aaa-local commit1 [EOF] "); let output = work_dir.complete_fish(["tag", "delete", "b"]); insta::assert_snapshot!(output, @" bbb-local commit1 [EOF] "); } #[test] fn test_global_arg_repository_is_respected() { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); work_dir .run_jj(["bookmark", "create", "-r@", "aaa"]) .success(); let output = test_env.complete_fish(["--repository", "repo", "bookmark", "rename", "a"]); insta::assert_snapshot!(output, @" aaa (no description set) [EOF] "); } #[test_case(Shell::Bash; "bash")] #[test_case(Shell::Zsh; "zsh")] #[test_case(Shell::Fish; "fish")] fn test_aliases_are_resolved(shell: Shell) { let test_env = TestEnvironment::default(); test_env.run_jj_in(".", ["git", "init", "repo"]).success(); let work_dir = test_env.work_dir("repo"); work_dir .run_jj(["bookmark", "create", "-r@", "aaa"]) .success(); // user config alias test_env.add_config(r#"aliases.b = ["bookmark"]"#); test_env.add_config(r#"aliases.rlog = ["log", "--reversed"]"#); // repo config alias work_dir .run_jj(["config", "set", "--repo", "aliases.b2", "['bookmark']"]) .success(); let output = work_dir.complete_at(shell, 3, ["b", "rename", "a"]); match shell { Shell::Bash => { insta::assert_snapshot!(output, @"aaa[EOF]"); } Shell::Zsh => { insta::assert_snapshot!(output, @"aaa:(no description set)[EOF]"); } Shell::Fish => { insta::assert_snapshot!(output, @" aaa (no description set) [EOF] "); } _ => unimplemented!("unexpected shell '{shell}'"), } let output = work_dir.complete_at(shell, 3, ["b2", "rename", "a"]); match shell { Shell::Bash => { insta::assert_snapshot!(output, @"aaa[EOF]"); } Shell::Zsh => { insta::assert_snapshot!(output, @"aaa:(no description set)[EOF]"); } Shell::Fish => { insta::assert_snapshot!(output, @" aaa (no description set) [EOF] "); } _ => unimplemented!("unexpected shell '{shell}'"), } let output = work_dir.complete_at(shell, 2, ["rlog", "--rev"]); match shell { Shell::Bash => { insta::assert_snapshot!(output, @" --revision --reversed[EOF] "); } Shell::Zsh => { insta::assert_snapshot!(output, @" --revision:Which revisions to show --reversed:Show revisions in the opposite order (older revisions first)[EOF] "); } Shell::Fish => { insta::assert_snapshot!(output, @" --revision Which revisions to show --reversed Show revisions in the opposite order (older revisions first) [EOF] "); } _ => unimplemented!("unexpected shell '{shell}'"), } } #[test] fn test_completions_are_generated() { let mut test_env = TestEnvironment::default(); test_env.add_env_var("COMPLETE", "fish"); let mut insta_settings = insta::Settings::clone_current(); insta_settings.add_filter(r"(--arguments) .*", "$1 .."); // omit path to jj binary let _guard = insta_settings.bind_to_scope(); let output = test_env.run_jj_in(".", [""; 0]); insta::assert_snapshot!(output, @" complete --keep-order --exclusive --command jj --arguments .. [EOF] "); let output = test_env.run_jj_in(".", ["--"]); insta::assert_snapshot!(output, @" complete --keep-order --exclusive --command jj --arguments .. [EOF] "); } #[test] fn test_bad_complete_env() { let mut test_env = TestEnvironment::default(); test_env.add_env_var("COMPLETE", "badshell"); let output = test_env.run_jj_in(".", [""; 0]); insta::assert_snapshot!(output, @" ------- stderr ------- error: unknown shell `badshell`, expected one of `bash`, `elvish`, `fish`, `powershell`, `zsh`[EOF] [exit status: 2] "); // Empty value of COMPLETE is ignored as is the value of "0". This could // change if `clap` changes the way it interprets an empty COMPLETE env var. // // In other words, jj runs normally instead of returning completions. We get // an error because the default jj command needs to be run in a jj repo. test_env.add_env_var("COMPLETE", ""); let output = test_env.run_jj_in(".", [""; 0]); insta::assert_snapshot!(output, @r#" ------- stderr ------- Hint: Use `jj -h` for a list of available commands. Run `jj config set --user ui.default-command log` to disable this message. Error: There is no jj repo in "." [EOF] [exit status: 1]