diff --git a/src/completion-scripts_.toit b/src/completion-scripts_.toit index 9d02331..888ae7c 100644 --- a/src/completion-scripts_.toit +++ b/src/completion-scripts_.toit @@ -2,13 +2,18 @@ // Use of this source code is governed by an MIT-style license that can be // found in the package's LICENSE file. +import fs + /** -Extracts the basename from the given $path, stripping any directory components. +Extracts the basename from the given $path, stripping any directory components + and the .exe extension on Windows. */ basename_ path/string -> string: - slash := path.index-of --last "/" - if slash >= 0: return path[slash + 1..] - return path + name := fs.basename path + // Strip .exe suffix so that completions work on Windows where + // system.program-path includes the extension but users type without it. + if name.ends-with ".exe": name = name[..name.size - 4] + return name /** Sanitizes the given $name for use as a shell function name. @@ -175,12 +180,19 @@ powershell-completion-script_ --program-path/string -> string: param(\$wordToComplete, \$commandAst, \$cursorPosition) \$tokens = \$commandAst.ToString() -split '\\s+' - \$args = \$tokens[1..(\$tokens.Length - 1)] + if (\$tokens.Length -gt 1) { + \$completionArgs = \$tokens[1..(\$tokens.Length - 1)] + } else { + \$completionArgs = @() + } + if (\$completionArgs.Length -eq 0 -or \$completionArgs[-1] -ne \$wordToComplete) { + \$completionArgs += \$wordToComplete + } - \$output = & $program-path __complete -- @args 2>\$null - if (\$LASTEXITCODE -ne 0) { return } + \$output = & '$program-path' __complete -- @completionArgs 2>\$null + if (\$LASTEXITCODE -ne 0 -or -not \$output) { return } - \$lines = \$output -split '\\n' + \$lines = \$output -split '\\r?\\n' \$directive = (\$lines[-1] -replace '^:', '') \$lines = \$lines[0..(\$lines.Length - 2)] @@ -193,6 +205,7 @@ powershell-completion-script_ --program-path/string -> string: \$value = \$line \$desc = \$line } + if (\$value -notlike "\$wordToComplete*") { continue } [System.Management.Automation.CompletionResult]::new( \$value, \$value, diff --git a/tests/completion_shell.toit b/tests/completion_shell.toit new file mode 100644 index 0000000..bb38696 --- /dev/null +++ b/tests/completion_shell.toit @@ -0,0 +1,114 @@ +// Copyright (C) 2026 Toit contributors. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +import fs +import host.directory +import host.file +import host.pipe +import system + +/** +A tmux session wrapper for testing interactive shell completions. +*/ +class Tmux: + /** The socket name, unique per session to avoid server conflicts. */ + socket-name/string + + constructor .socket-name --shell-cmd/List --width/int=200 --height/int=50: + args := [ + "tmux", + "-L", socket-name, // Use a dedicated server socket. + "new-session", + "-d", // Detached. + "-s", socket-name, + "-x", "$width", + "-y", "$height", + ] + shell-cmd + exit-code := pipe.run-program args + if exit-code != 0: + throw "tmux new-session failed with exit code $exit-code for shell '$shell-cmd'" + // Wait for the shell to initialize. + send-line "echo tmux-ready" + wait-for "tmux-ready" + + /** + Sends keystrokes to the tmux session. + Each argument is a tmux key name (e.g. "Enter", "Tab", "C-c"). + */ + send-keys keys/List -> none: + pipe.run-program ["tmux", "-L", socket-name, "send-keys", "-t", socket-name] + keys + + /** Sends text followed by Enter. */ + send-line text/string -> none: + send-keys [text, "Enter"] + + /** Sends Ctrl-C to cancel the current line, then waits for the shell to be ready. */ + cancel -> none: + send-keys ["C-c"] + // Echo a marker and wait for it so we know the shell is ready. + marker := "ready-$Time.monotonic-us" + send-line "echo $marker" + wait-for marker + + /** Captures the current pane content as a string. */ + capture -> string: + return pipe.backticks ["tmux", "-L", socket-name, "capture-pane", "-t", socket-name, "-p"] + + /** + Waits until the pane contains the given $expected string, or throws on timeout. + */ + wait-for expected/string --timeout-ms/int=10000 -> none: + deadline := Time.monotonic-us + timeout-ms * 1000 + delay-ms := 10 + while Time.monotonic-us < deadline: + content := capture + if content.contains expected: return + sleep --ms=delay-ms + delay-ms = min 500 (delay-ms * 2) + content := capture + throw "Timed out waiting for '$expected' in tmux pane. Content:\n$content" + + /** Kills the tmux server (and its session). */ + close -> none: + catch: pipe.run-program ["tmux", "-L", socket-name, "kill-server"] + +// Unique session prefix for this test run. +session-id_ := 0 + +next-session-name_ -> string: + session-id_++ + return "completion-test-$Time.monotonic-us-$session-id_" + +with-tmp-dir [block]: + tmpdir := directory.mkdtemp "/tmp/completion-shell-test-" + try: + block.call tmpdir + finally: + directory.rmdir --recursive --force tmpdir + +has-command_ name/string -> bool: + exit-code := pipe.run-program ["which", name] + return exit-code == 0 + +/** +Compiles the test app binary and creates OptionPath test artifacts. +Returns the path to the compiled binary. +The $tmpdir must already exist. +*/ +setup-test-binary_ tmpdir/string -> string: + test-dir := fs.dirname system.program-path + app-source := "$test-dir/completion_shell_test_app.toit" + binary := "$tmpdir/fleet" + if system.platform == system.PLATFORM-WINDOWS: + binary = "$tmpdir/fleet.exe" + print "Compiling test app..." + pipe.run-program ["toit", "compile", "-o", binary, app-source] + print "Binary compiled: $binary" + + // Create artifacts for OptionPath completion testing. + file.write-contents --path="$tmpdir/xfirmware.bin" "" + directory.mkdir "$tmpdir/xreleases" + + return binary + diff --git a/tests/completion_shell_bash_test.toit b/tests/completion_shell_bash_test.toit new file mode 100644 index 0000000..856111d --- /dev/null +++ b/tests/completion_shell_bash_test.toit @@ -0,0 +1,78 @@ +// Copyright (C) 2026 Toit contributors. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +import expect show * +import host.pipe +import .completion_shell + +main: + if not has-command_ "tmux": + print "tmux not found, skipping bash completion tests." + return + + // Bash 3.x (macOS default) lacks compopt and has limited programmable + // completion support. Skip if bash is too old. + bash-version := (pipe.backticks ["bash", "-c", "echo \$BASH_VERSINFO"]).trim + if bash-version == "" or bash-version[0] < '4': + print "" + print "=== Skipping bash tests (bash $bash-version too old, need 4+) ===" + return + + with-tmp-dir: | tmpdir | + binary := setup-test-binary_ tmpdir + test-bash binary tmpdir + + print "" + print "All bash completion tests passed!" + +test-bash binary/string tmpdir/string: + print "" + print "=== Testing bash completion ===" + tmux := Tmux (next-session-name_) --shell-cmd=["bash", "--norc", "--noprofile"] + try: + tmux.send-line "source <($binary completion bash); echo sourced" + tmux.wait-for "sourced" + + // Subcommand completion (double Tab for ambiguous matches). + tmux.send-keys ["$binary ", "Tab", "Tab"] + tmux.wait-for "deploy" + content := tmux.capture + expect (content.contains "status") + expect (content.contains "help") + expect (content.contains "completion") + tmux.cancel + + // Unique prefix auto-completes inline. + tmux.send-keys ["$binary dep", "Tab"] + tmux.wait-for "deploy" + tmux.cancel + + // Enum value completion. + tmux.send-keys ["$binary deploy --channel ", "Tab", "Tab"] + tmux.wait-for "stable" + content = tmux.capture + expect (content.contains "beta") + expect (content.contains "dev") + tmux.cancel + + // Short option -d triggers device completion. + tmux.send-keys ["$binary deploy -d ", "Tab", "Tab"] + tmux.wait-for "d3b07384" + tmux.cancel + + // OptionPath: file option falls back to file completion. + tmux.send-line "cd $tmpdir && echo cd-done" + tmux.wait-for "cd-done" + tmux.send-keys ["$binary deploy --firmware xfirm", "Tab"] + tmux.wait-for "xfirmware.bin" + tmux.cancel + + // OptionPath --directory: falls back to directory-only completion. + tmux.send-keys ["$binary deploy --output-dir xrel", "Tab"] + tmux.wait-for "xreleases" + tmux.cancel + + print " All bash tests passed." + finally: + tmux.close diff --git a/tests/completion_shell_fish_test.toit b/tests/completion_shell_fish_test.toit new file mode 100644 index 0000000..a551f5a --- /dev/null +++ b/tests/completion_shell_fish_test.toit @@ -0,0 +1,66 @@ +// Copyright (C) 2026 Toit contributors. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +import expect show * +import .completion_shell + +main: + if not has-command_ "tmux": + print "tmux not found, skipping fish completion tests." + return + + if not has-command_ "fish": + print "" + print "=== Skipping fish tests (fish not installed) ===" + return + + with-tmp-dir: | tmpdir | + binary := setup-test-binary_ tmpdir + test-fish binary tmpdir + + print "" + print "All fish completion tests passed!" + +test-fish binary/string tmpdir/string: + print "" + print "=== Testing fish completion ===" + tmux := Tmux (next-session-name_) --shell-cmd=["fish", "--no-config"] + try: + tmux.send-line "$binary completion fish | source; echo sourced" + tmux.wait-for "sourced" + + // Subcommand completion. + tmux.send-keys ["$binary ", "Tab"] + tmux.wait-for "deploy" + content := tmux.capture + expect (content.contains "status") + tmux.cancel + + // Enum value completion. + tmux.send-keys ["$binary deploy --channel ", "Tab"] + tmux.wait-for "stable" + content = tmux.capture + expect (content.contains "beta") + tmux.cancel + + // Device completion with descriptions. + tmux.send-keys ["$binary deploy --device ", "Tab"] + tmux.wait-for "Living Room Sensor" + tmux.cancel + + // OptionPath: file option falls back to file completion. + tmux.send-line "cd $tmpdir && echo cd-done" + tmux.wait-for "cd-done" + tmux.send-keys ["$binary deploy --firmware xfirm", "Tab"] + tmux.wait-for "xfirmware.bin" + tmux.cancel + + // OptionPath --directory: falls back to directory-only completion. + tmux.send-keys ["$binary deploy --output-dir xrel", "Tab"] + tmux.wait-for "xreleases" + tmux.cancel + + print " All fish tests passed." + finally: + tmux.close diff --git a/tests/completion_shell_powershell_test.toit b/tests/completion_shell_powershell_test.toit new file mode 100644 index 0000000..e8129f7 --- /dev/null +++ b/tests/completion_shell_powershell_test.toit @@ -0,0 +1,85 @@ +// Copyright (C) 2026 Toit contributors. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +import expect show * +import host.pipe +import .completion_shell + +main: + if not has-command_ "pwsh": + print "" + print "=== Skipping powershell tests (pwsh not installed) ===" + return + + with-tmp-dir: | tmpdir | + binary := setup-test-binary_ tmpdir + test-powershell binary tmpdir + + print "" + print "All powershell completion tests passed!" + +/** +Invokes the registered PowerShell completer for the given $input string and +returns the raw output (one completion per line, value TAB tooltip). +The completer is sourced from the binary and TabExpansion2 is used to trigger it. +The working directory is set to $tmpdir so relative path completions resolve correctly. +*/ +pwsh-complete_ binary/string tmpdir/string input/string -> string: + cursor-col := input.size + script := """ + \$env:PATH = "\$env:PATH\$([System.IO.Path]::PathSeparator)$tmpdir" + Set-Location '$tmpdir' + Invoke-Expression (& '$binary' completion powershell | Out-String) + \$r = TabExpansion2 -inputScript '$input' -cursorColumn $cursor-col + \$r.CompletionMatches | ForEach-Object { \$_.CompletionText + \"`t\" + \$_.ToolTip } + """ + return pipe.backticks ["pwsh", "-NoProfile", "-Command", script] + +test-powershell binary/string tmpdir/string: + print "" + print "=== Testing powershell completion ===" + + // Unique prefix auto-completes to subcommand. + output := pwsh-complete_ binary tmpdir "fleet dep" + expect (output.contains "deploy") + print " prefix completion: ok" + + // Empty word after space lists all subcommands (exercises Bug 1: trailing-space). + output = pwsh-complete_ binary tmpdir "fleet " + expect (output.contains "deploy") + expect (output.contains "status") + expect (output.contains "help") + expect (output.contains "completion") + print " empty-word subcommand listing: ok" + + // Enum value completion with trailing space (exercises Bug 1 again). + output = pwsh-complete_ binary tmpdir "fleet deploy --channel " + expect (output.contains "stable") + expect (output.contains "beta") + expect (output.contains "dev") + print " enum completion: ok" + + // Enum prefix filters results. + output = pwsh-complete_ binary tmpdir "fleet deploy --channel st" + expect (output.contains "stable") + expect (not (output.contains "beta")) + print " enum prefix filtering: ok" + + // Custom completion returns values and descriptions. + output = pwsh-complete_ binary tmpdir "fleet deploy --device " + expect (output.contains "d3b07384") + expect (output.contains "Living Room Sensor") + print " custom completion with descriptions: ok" + + // OptionPath: file completion (directive 4). + output = pwsh-complete_ binary tmpdir "fleet deploy --firmware xfirm" + expect (output.contains "xfirmware.bin") + print " file path completion: ok" + + // OptionPath: directory-only completion (directive 8). + output = pwsh-complete_ binary tmpdir "fleet deploy --output-dir xrel" + expect (output.contains "xreleases") + print " directory path completion: ok" + + print " All powershell tests passed." diff --git a/tests/completion_shell_test.toit b/tests/completion_shell_test.toit deleted file mode 100644 index 61fe57a..0000000 --- a/tests/completion_shell_test.toit +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (C) 2026 Toit contributors. -// Use of this source code is governed by a Zero-Clause BSD license that can -// be found in the tests/LICENSE file. - -import fs -import host.pipe -import host.directory -import system - -/** -A tmux session wrapper for testing interactive shell completions. -*/ -class Tmux: - /** The socket name, unique per session to avoid server conflicts. */ - socket-name/string - - constructor .socket-name --shell-cmd/List --width/int=200 --height/int=50: - args := [ - "tmux", - "-L", socket-name, // Use a dedicated server socket. - "new-session", - "-d", // Detached. - "-s", socket-name, - "-x", "$width", - "-y", "$height", - ] + shell-cmd - exit-code := pipe.run-program args - if exit-code != 0: - throw "tmux new-session failed with exit code $exit-code for shell '$shell-cmd'" - // Wait for the shell to initialize. - send-line "echo tmux-ready" - wait-for "tmux-ready" - - /** - Sends keystrokes to the tmux session. - Each argument is a tmux key name (e.g. "Enter", "Tab", "C-c"). - */ - send-keys keys/List -> none: - pipe.run-program ["tmux", "-L", socket-name, "send-keys", "-t", socket-name] + keys - - /** Sends text followed by Enter. */ - send-line text/string -> none: - send-keys [text, "Enter"] - - /** Sends Ctrl-C to cancel the current line, then waits for the shell to be ready. */ - cancel -> none: - send-keys ["C-c"] - // Echo a marker and wait for it so we know the shell is ready. - marker := "ready-$Time.monotonic-us" - send-line "echo $marker" - wait-for marker - - /** Captures the current pane content as a string. */ - capture -> string: - return pipe.backticks ["tmux", "-L", socket-name, "capture-pane", "-t", socket-name, "-p"] - - /** - Waits until the pane contains the given $expected string, or throws on timeout. - */ - wait-for expected/string --timeout-ms/int=10_000 -> none: - deadline := Time.monotonic-us + timeout-ms * 1000 - delay-ms := 10 - while Time.monotonic-us < deadline: - content := capture - if content.contains expected: return - sleep --ms=delay-ms - delay-ms = min 500 (delay-ms * 2) - content := capture - throw "Timed out waiting for '$expected' in tmux pane. Content:\n$content" - - /** Kills the tmux server (and its session). */ - close -> none: - catch: pipe.run-program ["tmux", "-L", socket-name, "kill-server"] - -// Unique session prefix for this test run. -session-id_ := 0 - -next-session-name_ -> string: - session-id_++ - return "completion-test-$Time.monotonic-us-$session-id_" - -with-tmp-dir [block]: - tmpdir := directory.mkdtemp "/tmp/completion-shell-test-" - try: - block.call tmpdir - finally: - catch: pipe.run-program ["rm", "-rf", tmpdir] - -binary_ := ? - -has-command_ name/string -> bool: - exit-code := pipe.run-program ["which", name] - return exit-code == 0 - -main: - if not has-command_ "tmux": - print "tmux not found, skipping shell completion tests." - return - - test-dir := fs.dirname system.program-path - app-source := "$test-dir/completion_shell_test_app.toit" - - with-tmp-dir: | tmpdir | - binary_ = "$tmpdir/fleet" - print "Compiling test app..." - pipe.run-program ["toit", "compile", "-o", binary_, app-source] - print "Binary compiled: $binary_" - - // Create artifacts for OptionPath completion testing. - pipe.run-program ["touch", "$tmpdir/xfirmware.bin"] - pipe.run-program ["mkdir", "$tmpdir/xreleases"] - - test-bash tmpdir - test-zsh tmpdir - test-fish tmpdir - - print "" - print "All shell completion tests passed!" - -test-bash tmpdir/string: - // Bash 3.x (macOS default) lacks compopt and has limited programmable - // completion support. Skip if bash is too old. - bash-version := (pipe.backticks ["bash", "-c", "echo \$BASH_VERSINFO"]).trim - if bash-version == "" or bash-version[0] < '4': - print "" - print "=== Skipping bash tests (bash $bash-version too old, need 4+) ===" - return - - print "" - print "=== Testing bash completion ===" - tmux := Tmux (next-session-name_) --shell-cmd=["bash", "--norc", "--noprofile"] - try: - tmux.send-line "source <($binary_ completion bash); echo sourced" - tmux.wait-for "sourced" - - // Subcommand completion (double Tab for ambiguous matches). - tmux.send-keys ["$binary_ ", "Tab", "Tab"] - tmux.wait-for "deploy" - content := tmux.capture - assert_ (content.contains "status") - assert_ (content.contains "help") - assert_ (content.contains "completion") - tmux.cancel - - // Unique prefix auto-completes inline. - tmux.send-keys ["$binary_ dep", "Tab"] - tmux.wait-for "deploy" - tmux.cancel - - // Enum value completion. - tmux.send-keys ["$binary_ deploy --channel ", "Tab", "Tab"] - tmux.wait-for "stable" - content = tmux.capture - assert_ (content.contains "beta") - assert_ (content.contains "dev") - tmux.cancel - - // Short option -d triggers device completion. - tmux.send-keys ["$binary_ deploy -d ", "Tab", "Tab"] - tmux.wait-for "d3b07384" - tmux.cancel - - // OptionPath: file option falls back to file completion. - tmux.send-line "cd $tmpdir && echo cd-done" - tmux.wait-for "cd-done" - tmux.send-keys ["$binary_ deploy --firmware xfirm", "Tab"] - tmux.wait-for "xfirmware.bin" - tmux.cancel - - // OptionPath --directory: falls back to directory-only completion. - tmux.send-keys ["$binary_ deploy --output-dir xrel", "Tab"] - tmux.wait-for "xreleases" - tmux.cancel - - print " All bash tests passed." - finally: - tmux.close - -test-zsh tmpdir/string: - if not has-command_ "zsh": - print "" - print "=== Skipping zsh tests (zsh not installed) ===" - return - - print "" - print "=== Testing zsh completion ===" - tmux := Tmux (next-session-name_) --shell-cmd=["zsh", "-f"] - try: - tmux.send-line "autoload -U compinit && compinit -u && echo ready" - tmux.wait-for "ready" - tmux.send-line "source <($binary_ completion zsh) && echo sourced" - tmux.wait-for "sourced" - - // Subcommand completion. - tmux.send-keys ["$binary_ ", "Tab"] - tmux.wait-for "deploy" - content := tmux.capture - assert_ (content.contains "status") - tmux.cancel - - // Enum value completion. - tmux.send-keys ["$binary_ deploy --channel ", "Tab"] - tmux.wait-for "stable" - content = tmux.capture - assert_ (content.contains "beta") - tmux.cancel - - // Device completion with descriptions. - tmux.send-keys ["$binary_ deploy --device ", "Tab"] - tmux.wait-for "Living Room Sensor" - tmux.cancel - - // OptionPath: file option falls back to file completion. - tmux.send-line "cd $tmpdir && echo cd-done" - tmux.wait-for "cd-done" - tmux.send-keys ["$binary_ deploy --firmware xfirm", "Tab"] - tmux.wait-for "xfirmware.bin" - tmux.cancel - - // OptionPath --directory: falls back to directory-only completion. - tmux.send-keys ["$binary_ deploy --output-dir xrel", "Tab"] - tmux.wait-for "xreleases" - tmux.cancel - - print " All zsh tests passed." - finally: - tmux.close - -test-fish tmpdir/string: - if not has-command_ "fish": - print "" - print "=== Skipping fish tests (fish not installed) ===" - return - - print "" - print "=== Testing fish completion ===" - tmux := Tmux (next-session-name_) --shell-cmd=["fish", "--no-config"] - try: - tmux.send-line "$binary_ completion fish | source; echo sourced" - tmux.wait-for "sourced" - - // Subcommand completion. - tmux.send-keys ["$binary_ ", "Tab"] - tmux.wait-for "deploy" - content := tmux.capture - assert_ (content.contains "status") - tmux.cancel - - // Enum value completion. - tmux.send-keys ["$binary_ deploy --channel ", "Tab"] - tmux.wait-for "stable" - content = tmux.capture - assert_ (content.contains "beta") - tmux.cancel - - // Device completion with descriptions. - tmux.send-keys ["$binary_ deploy --device ", "Tab"] - tmux.wait-for "Living Room Sensor" - tmux.cancel - - // OptionPath: file option falls back to file completion. - tmux.send-line "cd $tmpdir; and echo cd-done" - tmux.wait-for "cd-done" - tmux.send-keys ["$binary_ deploy --firmware xfirm", "Tab"] - tmux.wait-for "xfirmware.bin" - tmux.cancel - - // OptionPath --directory: falls back to directory-only completion. - tmux.send-keys ["$binary_ deploy --output-dir xrel", "Tab"] - tmux.wait-for "xreleases" - tmux.cancel - - print " All fish tests passed." - finally: - tmux.close - -assert_ condition/bool: - if not condition: throw "ASSERTION_FAILED" diff --git a/tests/completion_shell_zsh_test.toit b/tests/completion_shell_zsh_test.toit new file mode 100644 index 0000000..e618ad4 --- /dev/null +++ b/tests/completion_shell_zsh_test.toit @@ -0,0 +1,68 @@ +// Copyright (C) 2026 Toit contributors. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +import expect show * +import .completion_shell + +main: + if not has-command_ "tmux": + print "tmux not found, skipping zsh completion tests." + return + + if not has-command_ "zsh": + print "" + print "=== Skipping zsh tests (zsh not installed) ===" + return + + with-tmp-dir: | tmpdir | + binary := setup-test-binary_ tmpdir + test-zsh binary tmpdir + + print "" + print "All zsh completion tests passed!" + +test-zsh binary/string tmpdir/string: + print "" + print "=== Testing zsh completion ===" + tmux := Tmux (next-session-name_) --shell-cmd=["zsh", "-f"] + try: + tmux.send-line "autoload -U compinit && compinit -u && echo ready" + tmux.wait-for "ready" + tmux.send-line "source <($binary completion zsh) && echo sourced" + tmux.wait-for "sourced" + + // Subcommand completion. + tmux.send-keys ["$binary ", "Tab"] + tmux.wait-for "deploy" + content := tmux.capture + expect (content.contains "status") + tmux.cancel + + // Enum value completion. + tmux.send-keys ["$binary deploy --channel ", "Tab"] + tmux.wait-for "stable" + content = tmux.capture + expect (content.contains "beta") + tmux.cancel + + // Device completion with descriptions. + tmux.send-keys ["$binary deploy --device ", "Tab"] + tmux.wait-for "Living Room Sensor" + tmux.cancel + + // OptionPath: file option falls back to file completion. + tmux.send-line "cd $tmpdir && echo cd-done" + tmux.wait-for "cd-done" + tmux.send-keys ["$binary deploy --firmware xfirm", "Tab"] + tmux.wait-for "xfirmware.bin" + tmux.cancel + + // OptionPath --directory: falls back to directory-only completion. + tmux.send-keys ["$binary deploy --output-dir xrel", "Tab"] + tmux.wait-for "xreleases" + tmux.cancel + + print " All zsh tests passed." + finally: + tmux.close