completion tests: parameterize test case over different shells

The completion mechanism works differently in different shells:

For example, when the command line `jj aaa bb ccc` is completed at the
end of the `bb` token, bash and zsh pass the completer the whole line
`-- jj aaa bb ccc` and an index of 2 which refers to the `bb` token;
they are then expected to complete `bb`. Meanwhile, fish and Powershell
only pass the command up to the completion point, so `-- jj aaa bb`;
the completer is always expected to complete the last token. In all
cases, the shell ultimately decides what to do with the completions,
e.g. to complete up to a common prefix (bash), to show an interactive
picker (zsh, fish), or to insert the completion if it is the only one
(all shells). Remaining tokens (`ccc`) are also always appended by the
shell and not part of the completion.

While this is mostly handled by the clap_complete crate, we do expand
aliases and present clap_complete with a fake view of the real command
line, thereby reaching into its internals by wrapping the interface
between the completion shell script that is provided by clap_complete
and its Rust code in `CommandEnv::try_complete()`. If we get this wrong,
completion might yield unexpected results, so it is worth testing
completion for both flavors of shells whenever aliases are potentially
in the mix.

To avoid redundancy, the shell-specific invocation of `jj` is factored
into a `complete_at()` function of the test fixture. The `test-case`
crate is then used to instantiate each test case for different values of
clap_complete's `Shell` enum.

filter

bash/zsh specific behavior

move impl
This commit is contained in:
Jonas Greitemann 2025-04-23 10:55:19 +02:00 committed by Jonas Greitemann
parent 2c4a0328f9
commit eaaaf058a8

View File

@ -12,9 +12,124 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::ffi::OsStr;
use clap_complete::Shell;
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();
@ -78,15 +193,12 @@ fn test_bookmark_names() {
origin_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["git", "fetch"]).success();
let mut test_env = test_env;
// 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.
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["--", "jj", "bookmark", "rename", ""]);
let output = work_dir.complete_fish(["bookmark", "rename", ""]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
@ -106,21 +218,21 @@ fn test_bookmark_names() {
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "rename", "a"]);
let output = work_dir.complete_fish(["bookmark", "rename", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "delete", "a"]);
let output = work_dir.complete_fish(["bookmark", "delete", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "forget", "a"]);
let output = work_dir.complete_fish(["bookmark", "forget", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
@ -128,7 +240,7 @@ fn test_bookmark_names() {
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "list", "--bookmark", "a"]);
let output = work_dir.complete_fish(["bookmark", "list", "--bookmark", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
@ -136,40 +248,40 @@ fn test_bookmark_names() {
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "move", "a"]);
let output = work_dir.complete_fish(["bookmark", "move", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "set", "a"]);
let output = work_dir.complete_fish(["bookmark", "set", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "track", "a"]);
let output = work_dir.complete_fish(["bookmark", "track", "a"]);
insta::assert_snapshot!(output, @r"
aaa-untracked@origin x
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "bookmark", "untrack", "a"]);
let output = work_dir.complete_fish(["bookmark", "untrack", "a"]);
insta::assert_snapshot!(output, @r"
aaa-tracked@origin x
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "git", "push", "-b", "a"]);
let output = work_dir.complete_fish(["git", "push", "-b", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "git", "fetch", "-b", "a"]);
let output = work_dir.complete_fish(["git", "fetch", "-b", "a"]);
insta::assert_snapshot!(output, @r"
aaa-local x
aaa-tracked x
@ -188,30 +300,17 @@ fn test_global_arg_repository_is_respected() {
.run_jj(["bookmark", "create", "-r@", "aaa"])
.success();
let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let output = test_env.run_jj_in(
".",
[
"--",
"jj",
"--repository",
"repo",
"bookmark",
"rename",
"a",
],
);
let output = test_env.complete_fish(["--repository", "repo", "bookmark", "rename", "a"]);
insta::assert_snapshot!(output, @r"
aaa (no description set)
[EOF]
");
}
#[test]
fn test_aliases_are_resolved() {
#[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");
@ -227,22 +326,39 @@ fn test_aliases_are_resolved() {
.run_jj(["config", "set", "--repo", "aliases.b2", "['bookmark']"])
.success();
let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let work_dir = test_env.work_dir("repo");
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, @r"
aaa (no description set)
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
let output = work_dir.run_jj(["--", "jj", "b", "rename", "a"]);
insta::assert_snapshot!(output, @r"
aaa (no description set)
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "b2", "rename", "a"]);
insta::assert_snapshot!(output, @r"
aaa (no description set)
[EOF]
");
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, @r"
aaa (no description set)
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
}
#[test]
@ -265,44 +381,108 @@ fn test_completions_are_generated() {
");
}
#[test]
fn test_zsh_completion() {
let mut test_env = TestEnvironment::default();
test_env.add_env_var("COMPLETE", "zsh");
// ["--", "jj"]
// ^^^^ index = 0
let complete_at = |index: usize, args: &[&str]| {
test_env.run_jj_with(|cmd| {
cmd.args(args)
.env("_CLAP_COMPLETE_INDEX", index.to_string())
})
};
#[test_case(Shell::Bash; "bash")]
#[test_case(Shell::Zsh; "zsh")]
#[test_case(Shell::Fish; "fish")]
fn test_command_completion(shell: Shell) {
let test_env = TestEnvironment::default();
// Command names should be suggested. If the default command were expanded,
// only "log" would be listed.
let output = complete_at(1, &["--", "jj"]);
insta::assert_snapshot!(
output.normalize_stdout_with(|s| s.split_inclusive('\n').take(2).collect()), @r"
abandon:Abandon a revision
absorb:Move changes from a revision into the stack of mutable revisions
[EOF]
");
let output = complete_at(2, &["--", "jj", "--no-pager"]);
insta::assert_snapshot!(
output.normalize_stdout_with(|s| s.split_inclusive('\n').take(2).collect()), @r"
abandon:Abandon a revision
absorb:Move changes from a revision into the stack of mutable revisions
[EOF]
");
let output = test_env
.complete_at(shell, 1, [""])
.normalize_stdout_with(|s| s.split_inclusive('\n').take(2).collect());
match shell {
Shell::Bash => {
insta::assert_snapshot!(output, @r"
abandon
absorb
[EOF]
");
}
Shell::Zsh => {
insta::assert_snapshot!(output, @r"
abandon:Abandon a revision
absorb:Move changes from a revision into the stack of mutable revisions
[EOF]
");
}
Shell::Fish => {
insta::assert_snapshot!(output, @r"
abandon Abandon a revision
absorb Move changes from a revision into the stack of mutable revisions
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
let output = complete_at(1, &["--", "jj", "b"]);
insta::assert_snapshot!(output, @"bookmark:Manage bookmarks [default alias: b][EOF]");
let output = test_env
.complete_at(shell, 2, ["--no-pager", ""])
.normalize_stdout_with(|s| s.split_inclusive('\n').take(2).collect());
match shell {
Shell::Bash => {
insta::assert_snapshot!(output, @r"
abandon
absorb
[EOF]
");
}
Shell::Zsh => {
insta::assert_snapshot!(output, @r"
abandon:Abandon a revision
absorb:Move changes from a revision into the stack of mutable revisions
[EOF]
");
}
Shell::Fish => {
insta::assert_snapshot!(output, @r"
abandon Abandon a revision
absorb Move changes from a revision into the stack of mutable revisions
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
let output = test_env.complete_at(shell, 1, ["b"]);
match shell {
Shell::Zsh => {
insta::assert_snapshot!(output, @"bookmark:Manage bookmarks [default alias: b][EOF]");
}
Shell::Bash => {
insta::assert_snapshot!(output, @"bookmark[EOF]");
}
Shell::Fish => {
insta::assert_snapshot!(output, @r"
bookmark Manage bookmarks [default alias: b]
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
let output = test_env.complete_at(shell, 1, ["aban", "-r", "@"]);
match shell {
Shell::Bash => {
insta::assert_snapshot!(output, @"abandon[EOF]");
}
Shell::Zsh => {
insta::assert_snapshot!(output, @"abandon:Abandon a revision[EOF]");
}
Shell::Fish => {
insta::assert_snapshot!(output, @r"
abandon Abandon a revision
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
}
#[test]
fn test_remote_names() {
let mut test_env = TestEnvironment::default();
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init"]).success();
test_env
@ -312,50 +492,51 @@ fn test_remote_names() {
)
.success();
test_env.add_env_var("COMPLETE", "fish");
let output = test_env.run_jj_in(".", ["--", "jj", "git", "remote", "remove", "o"]);
let output = test_env.complete_fish(["git", "remote", "remove", "o"]);
insta::assert_snapshot!(output, @r"
origin
[EOF]
");
let output = test_env.run_jj_in(".", ["--", "jj", "git", "remote", "rename", "o"]);
let output = test_env.complete_fish(["git", "remote", "rename", "o"]);
insta::assert_snapshot!(output, @r"
origin
[EOF]
");
let output = test_env.run_jj_in(".", ["--", "jj", "git", "remote", "set-url", "o"]);
let output = test_env.complete_fish(["git", "remote", "set-url", "o"]);
insta::assert_snapshot!(output, @r"
origin
[EOF]
");
let output = test_env.run_jj_in(".", ["--", "jj", "git", "push", "--remote", "o"]);
let output = test_env.complete_fish(["git", "push", "--remote", "o"]);
insta::assert_snapshot!(output, @r"
origin
[EOF]
");
let output = test_env.run_jj_in(".", ["--", "jj", "git", "fetch", "--remote", "o"]);
let output = test_env.complete_fish(["git", "fetch", "--remote", "o"]);
insta::assert_snapshot!(output, @r"
origin
[EOF]
");
let output = test_env.run_jj_in(".", ["--", "jj", "bookmark", "list", "--remote", "o"]);
let output = test_env.complete_fish(["bookmark", "list", "--remote", "o"]);
insta::assert_snapshot!(output, @r"
origin
[EOF]
");
}
#[test]
fn test_aliases_are_completed() {
#[test_case(Shell::Bash; "bash")]
#[test_case(Shell::Zsh; "zsh")]
#[test_case(Shell::Fish; "fish")]
fn test_aliases_are_completed(shell: Shell) {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let repo_path = work_dir.root().to_str().unwrap();
// user config alias
test_env.add_config(r#"aliases.user-alias = ["bookmark"]"#);
@ -370,44 +551,51 @@ fn test_aliases_are_completed() {
])
.success();
let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["--", "jj", "user-al"]);
insta::assert_snapshot!(output, @r"
user-alias
[EOF]
");
let output = work_dir.complete_at(shell, 1, ["user-al"]);
match shell {
Shell::Bash => {
insta::assert_snapshot!(output, @"user-alias[EOF]");
}
Shell::Zsh => {
insta::assert_snapshot!(output, @"user-alias[EOF]");
}
Shell::Fish => {
insta::assert_snapshot!(output, @r"
user-alias
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
// make sure --repository flag is respected
let output = test_env.run_jj_in(
".",
[
"--",
"jj",
"--repository",
work_dir.root().to_str().unwrap(),
"repo-al",
],
);
insta::assert_snapshot!(output, @r"
repo-alias
[EOF]
");
let output = test_env.complete_at(shell, 3, ["--repository", repo_path, "repo-al"]);
match shell {
Shell::Bash => {
insta::assert_snapshot!(output, @"repo-alias[EOF]");
}
Shell::Zsh => {
insta::assert_snapshot!(output, @"repo-alias[EOF]");
}
Shell::Fish => {
insta::assert_snapshot!(output, @r"
repo-alias
[EOF]
");
}
_ => unimplemented!("unexpected shell '{shell}'"),
}
// cannot load aliases from --config flag
let output = test_env.run_jj_in(
".",
[
"--",
"jj",
"--config=aliases.cli-alias=['bookmark']",
"cli-al",
],
let output = test_env.complete_at(
shell,
2,
["--config=aliases.cli-alias=['bookmark']", "cli-al"],
);
assert!(
output.status.success() && output.stdout.is_empty(),
"completion expected to come back empty, but got: {output}"
);
insta::assert_snapshot!(output, @"");
}
#[test]
@ -466,9 +654,6 @@ fn test_revisions() {
.run_jj(["describe", "-m", "working_copy"])
.success();
let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let work_dir = test_env.work_dir("repo");
// There are _a lot_ of commands and arguments accepting revisions.
@ -476,7 +661,7 @@ fn test_revisions() {
// completion function should be sufficient.
// complete all revisions
let output = work_dir.run_jj(["--", "jj", "diff", "--from", ""]);
let output = work_dir.complete_fish(["diff", "--from", ""]);
insta::assert_snapshot!(output, @r"
immutable_bookmark immutable
mutable_bookmark mutable
@ -492,7 +677,7 @@ fn test_revisions() {
");
// complete only mutable revisions
let output = work_dir.run_jj(["--", "jj", "squash", "--into", ""]);
let output = work_dir.complete_fish(["squash", "--into", ""]);
insta::assert_snapshot!(output, @r"
mutable_bookmark mutable
k working_copy
@ -505,7 +690,7 @@ fn test_revisions() {
// complete args of the default command
test_env.add_config("ui.default-command = 'log'");
let output = work_dir.run_jj(["--", "jj", "-r", ""]);
let output = work_dir.complete_fish(["-r", ""]);
insta::assert_snapshot!(output, @r"
immutable_bookmark immutable
mutable_bookmark mutable
@ -524,12 +709,12 @@ fn test_revisions() {
// The name of a bookmark does not get completed, since we want to create a new
// bookmark
let output = work_dir.run_jj(["--", "jj", "git", "push", "--named", ""]);
let output = work_dir.complete_fish(["git", "push", "--named", ""]);
insta::assert_snapshot!(output, @"");
let output = work_dir.run_jj(["--", "jj", "git", "push", "--named", "a"]);
let output = work_dir.complete_fish(["git", "push", "--named", "a"]);
insta::assert_snapshot!(output, @"");
let output = work_dir.run_jj(["--", "jj", "git", "push", "--named", "a="]);
let output = work_dir.complete_fish(["git", "push", "--named", "a="]);
insta::assert_snapshot!(output, @r"
a=immutable_bookmark immutable
a=mutable_bookmark mutable
@ -544,7 +729,7 @@ fn test_revisions() {
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "git", "push", "--named", "a=a"]);
let output = work_dir.complete_fish(["git", "push", "--named", "a=a"]);
insta::assert_snapshot!(output, @r"
a=alias_with_newline roots(
[EOF]
@ -576,12 +761,9 @@ fn test_operations() {
.run_jj(["describe", "-m", "description 4"])
.success();
let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["--", "jj", "op", "show", ""]).success();
let output = work_dir.complete_fish(["op", "show", ""]).success();
let add_workspace_id = output
.stdout
.raw()
@ -593,54 +775,54 @@ fn test_operations() {
.unwrap();
insta::assert_snapshot!(add_workspace_id, @"eac759b9ab75");
let output = work_dir.run_jj(["--", "jj", "op", "show", "5"]);
let output = work_dir.complete_fish(["op", "show", "5"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
518b588abbc6 (2001-02-03 08:05:09) describe commit 19611c995a342c01f525583e5fcafdd211f6d009
[EOF]
");
// make sure global --at-op flag is respected
let output = work_dir.run_jj(["--", "jj", "--at-op", "518b588abbc6", "op", "show", "5"]);
let output = work_dir.complete_fish(["--at-op", "518b588abbc6", "op", "show", "5"]);
insta::assert_snapshot!(output, @r"
518b588abbc6 (2001-02-03 08:05:09) describe commit 19611c995a342c01f525583e5fcafdd211f6d009
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "--at-op", "5b"]);
let output = work_dir.complete_fish(["--at-op", "5b"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "op", "abandon", "5b"]);
let output = work_dir.complete_fish(["op", "abandon", "5b"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "op", "diff", "--op", "5b"]);
let output = work_dir.complete_fish(["op", "diff", "--op", "5b"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "op", "diff", "--from", "5b"]);
let output = work_dir.complete_fish(["op", "diff", "--from", "5b"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "op", "diff", "--to", "5b"]);
let output = work_dir.complete_fish(["op", "diff", "--to", "5b"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "op", "restore", "5b"]);
let output = work_dir.complete_fish(["op", "restore", "5b"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "op", "undo", "5b"]);
let output = work_dir.complete_fish(["op", "undo", "5b"]);
insta::assert_snapshot!(output, @r"
5bbb4ca536a8 (2001-02-03 08:05:12) describe commit 968261075dddabf4b0e333c1cc9a49ce26a3f710
[EOF]
@ -661,12 +843,9 @@ fn test_workspaces() {
.run_jj(["workspace", "add", "--name", "def-second", "../secondary"])
.success();
let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let main_dir = test_env.work_dir("main");
let output = main_dir.run_jj(["--", "jj", "workspace", "forget", "def"]);
let output = main_dir.complete_fish(["workspace", "forget", "def"]);
insta::assert_snapshot!(output, @r"
def-second (no description set)
default initial
@ -676,18 +855,16 @@ fn test_workspaces() {
#[test]
fn test_config() {
let mut test_env = TestEnvironment::default();
test_env.add_env_var("COMPLETE", "fish");
let dir = test_env.env_root();
let test_env = TestEnvironment::default();
let output = test_env.run_jj_in(dir, ["--", "jj", "config", "get", "c"]);
let output = test_env.complete_fish(["config", "get", "c"]);
insta::assert_snapshot!(output, @r"
core.fsmonitor Whether to use an external filesystem monitor, useful for large repos
core.watchman.register-snapshot-trigger Whether to use triggers to monitor for changes in the background.
[EOF]
");
let output = test_env.run_jj_in(dir, ["--", "jj", "config", "list", "c"]);
let output = test_env.complete_fish(["config", "list", "c"]);
insta::assert_snapshot!(output, @r"
colors Mapping from jj formatter labels to colors
core
@ -697,42 +874,28 @@ fn test_config() {
[EOF]
");
let output = test_env.run_jj_in(dir, ["--", "jj", "log", "--config", "c"]);
let output = test_env.complete_fish(["log", "--config", "c"]);
insta::assert_snapshot!(output, @r"
core.fsmonitor= Whether to use an external filesystem monitor, useful for large repos
core.watchman.register-snapshot-trigger= Whether to use triggers to monitor for changes in the background.
[EOF]
");
let output = test_env.run_jj_in(
dir,
["--", "jj", "log", "--config", "ui.conflict-marker-style="],
);
let output = test_env.complete_fish(["log", "--config", "ui.conflict-marker-style="]);
insta::assert_snapshot!(output, @r"
ui.conflict-marker-style=diff
ui.conflict-marker-style=snapshot
ui.conflict-marker-style=git
[EOF]
");
let output = test_env.run_jj_in(
dir,
["--", "jj", "log", "--config", "ui.conflict-marker-style=g"],
);
let output = test_env.complete_fish(["log", "--config", "ui.conflict-marker-style=g"]);
insta::assert_snapshot!(output, @r"
ui.conflict-marker-style=git
[EOF]
");
let output = test_env.run_jj_in(
dir,
[
"--",
"jj",
"log",
"--config",
"git.abandon-unreachable-commits=",
],
);
let output = test_env.complete_fish(["log", "--config", "git.abandon-unreachable-commits="]);
insta::assert_snapshot!(output, @r"
git.abandon-unreachable-commits=false
git.abandon-unreachable-commits=true
@ -742,11 +905,9 @@ fn test_config() {
#[test]
fn test_template_alias() {
let mut test_env = TestEnvironment::default();
test_env.add_env_var("COMPLETE", "fish");
let dir = test_env.env_root();
let test_env = TestEnvironment::default();
let output = test_env.run_jj_in(dir, ["--", "jj", "log", "-T", ""]);
let output = test_env.complete_fish(["log", "-T", ""]);
insta::assert_snapshot!(output, @r"
builtin_config_list
builtin_config_list_detailed
@ -921,12 +1082,9 @@ fn test_files() {
[EOF]
");
let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["--", "jj", "file", "show", "f_"]);
let output = work_dir.complete_fish(["file", "show", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_added
f_added_2
@ -937,7 +1095,7 @@ fn test_files() {
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "file", "annotate", "-r@-", "f_"]);
let output = work_dir.complete_fish(["file", "annotate", "-r@-", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_added
f_dir/
@ -946,7 +1104,8 @@ fn test_files() {
f_unchanged
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "diff", "-r", "@-", "f_"]);
let output = work_dir.complete_fish(["diff", "-r", "@-", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_added Added
f_deleted Deleted
@ -957,9 +1116,7 @@ fn test_files() {
[EOF]
");
let output = work_dir.run_jj([
"--",
"jj",
let output = work_dir.complete_fish([
"diff",
"-r",
"@-",
@ -972,7 +1129,7 @@ fn test_files() {
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "diff", "--from", "root()", "--to", "@-", "f_"]);
let output = work_dir.complete_fish(["diff", "--from", "root()", "--to", "@-", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_added Added
f_dir/
@ -983,9 +1140,7 @@ fn test_files() {
");
// interdiff has a different behavior with --from and --to flags
let output = work_dir.run_jj([
"--",
"jj",
let output = work_dir.complete_fish([
"interdiff",
"--to=interdiff_to",
"--from=interdiff_from",
@ -1000,7 +1155,7 @@ fn test_files() {
");
// squash has a different behavior with --from and --to flags
let output = work_dir.run_jj(["--", "jj", "squash", "-f=first", "f_"]);
let output = work_dir.complete_fish(["squash", "-f=first", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_deleted Added
f_modified Added
@ -1009,14 +1164,14 @@ fn test_files() {
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "resolve", "-r=conflicted", "f_"]);
let output = work_dir.complete_fish(["resolve", "-r=conflicted", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_dir/
f_modified
[EOF]
");
let output = work_dir.run_jj(["--", "jj", "log", "f_"]);
let output = work_dir.complete_fish(["log", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_added
f_added_2
@ -1026,15 +1181,8 @@ fn test_files() {
f_unchanged
[EOF]
");
let output = work_dir.run_jj([
"--",
"jj",
"log",
"-r=first",
"--revisions",
"conflicted",
"f_",
]);
let output = work_dir.complete_fish(["log", "-r=first", "--revisions", "conflicted", "f_"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
f_added_2
f_deleted
@ -1046,6 +1194,6 @@ fn test_files() {
");
let outside_repo = test_env.env_root();
let output = test_env.run_jj_in(outside_repo, ["--", "jj", "log", "f_"]);
let output = test_env.work_dir(outside_repo).complete_fish(["log", "f_"]);
insta::assert_snapshot!(output, @"");
}