import { afterEach, describe, expect, test } from "vitest"; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { generateBashCompletions, generateFishCompletions, generateZshCompletions, installCompletions, } from "../../src/cli/autocomplete/completions"; describe("shell completions", () => { const tempDirs: string[] = []; afterEach(() => { for (const tempDir of tempDirs) { rmSync(tempDir, { recursive: true, force: true }); } tempDirs.length = 0; }); function createConfigFile(contents = ""): string { const tempDir = mkdtempSync(join(tmpdir(), "xo-cli-completions-test-")); tempDirs.push(tempDir); const configFile = join(tempDir, "shellrc"); writeFileSync(configFile, contents); return configFile; } test("uses shell-native mnemonic completion in bash", () => { const completions = generateBashCompletions("xo-cli"); expect(completions).toContain( 'local config_dir="${XO_CONFIG_DIR:-${HOME}/.config/xo-cli}"', ); expect(completions).toContain('__xo_complete_mnemonics "${cur}"'); expect(completions).not.toContain('__xo_complete mnemonics "${cur}"'); }); test("uses shell-native mnemonic completion in zsh", () => { const completions = generateZshCompletions("xo-cli"); expect(completions).toContain( 'local config_dir="${XO_CONFIG_DIR:-${HOME}/.config/xo-cli}"', ); expect(completions).toContain( '__xo_complete_mnemonics "${words[CURRENT]}"', ); expect(completions).not.toContain( '__xo_complete mnemonics "${words[CURRENT]}"', ); }); test("uses shell-native mnemonic completion in fish", () => { const completions = generateFishCompletions("xo-cli"); expect(completions).toContain("set -l config_dir \"$XO_CONFIG_DIR\""); expect(completions).toContain("(__xo_cli_complete_mnemonics)"); expect(completions).not.toContain("(__xo_cli_complete_dynamic mnemonics)"); }); test("installs the config default and completion loader once", () => { const configFile = createConfigFile(); expect(installCompletions("bash", "xo-cli", configFile)).toBe(true); expect(installCompletions("bash", "xo-cli", configFile)).toBe(false); const contents = readFileSync(configFile, "utf8"); expect(contents.match(/XO_CONFIG_DIR/g)).toHaveLength(2); expect(contents.match(/eval "\$\(xo-cli completions bash\)"/g)).toHaveLength( 1, ); }); test("adds a missing default without duplicating an existing loader", () => { const configFile = createConfigFile('eval "$(xo-cli completions bash)"\n'); expect(installCompletions("bash", "xo-cli", configFile)).toBe(true); const contents = readFileSync(configFile, "utf8"); expect(contents.match(/eval "\$\(xo-cli completions bash\)"/g)).toHaveLength( 1, ); expect(contents).toContain( 'export XO_CONFIG_DIR="${XO_CONFIG_DIR:-$HOME/.config/xo-cli}"', ); }); test("preserves an existing custom config directory assignment", () => { const configFile = createConfigFile("export XO_CONFIG_DIR=/tmp/custom-xo\n"); expect(installCompletions("zsh", "xo-cli", configFile)).toBe(true); const contents = readFileSync(configFile, "utf8"); expect(contents).toContain("export XO_CONFIG_DIR=/tmp/custom-xo"); expect(contents).not.toContain("${XO_CONFIG_DIR:-$HOME/.config/xo-cli}"); expect(contents).toContain('eval "$(xo-cli completions zsh)"'); }); test("uses fish syntax when installing fish completions", () => { const configFile = createConfigFile(); expect(installCompletions("fish", "xo-cli", configFile)).toBe(true); const contents = readFileSync(configFile, "utf8"); expect(contents).toContain( 'set -q XO_CONFIG_DIR; or set -gx XO_CONFIG_DIR "$HOME/.config/xo-cli"', ); expect(contents).toContain("xo-cli completions fish | source"); }); });