Files
oh-my-pi/packages/tui/test/autocomplete.test.ts
T
can1357 ca2860b200 feat: added OpenAI compatibility config and 10 new AI models with reasoning support
- Added support for provider-level OpenAI compatibility configuration enabling reasoning effort mapping and streaming usage fallback across models.
- Added 10 new AI models (DeepSeek V3.2, Llama 3.1 405B, Mistral Large 3, Pixtral Large, and others) with updated pricing and context windows.
- Fixed autocomplete to preserve ./ prefix in relative file/directory path completions and paste marker expansion to handle regex tokens literally.
- Changed system prompt date format to ISO 8601 and tool download timeout from 15s to 120s for improved cross-platform compatibility.
- Refactored OpenAI completions provider to extract token parsing logic and support choice-level usage fallback with improved message serialization.
2026-03-14 13:38:29 +01:00

171 lines
6.2 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { CombinedAutocompleteProvider } from "@oh-my-pi/pi-tui/autocomplete";
describe("CombinedAutocompleteProvider", () => {
describe("extractPathPrefix", () => {
it("extracts / from 'hey /' when forced", async () => {
const provider = new CombinedAutocompleteProvider([], "/tmp");
const lines = ["hey /"];
const cursorLine = 0;
const cursorCol = 5; // After the "/"
const result = await provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
expect(result).not.toBeNull();
if (result) {
expect(result.prefix).toBe("/");
}
});
it("extracts /A from '/A' when forced", async () => {
const provider = new CombinedAutocompleteProvider([], "/tmp");
const lines = ["/A"];
const cursorLine = 0;
const cursorCol = 2; // After the "A"
const result = await provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
// This might return null if /A doesn't match anything, which is fine
// We're mainly testing that the prefix extraction works
if (result) {
expect(result.prefix).toBe("/A");
}
});
it("does not trigger for slash commands", async () => {
const provider = new CombinedAutocompleteProvider([], "/tmp");
const lines = ["/model"];
const cursorLine = 0;
const cursorCol = 6; // After "model"
const result = await provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
expect(result).toBe(null);
});
it("triggers for absolute paths after slash command argument", async () => {
const provider = new CombinedAutocompleteProvider([], "/tmp");
const lines = ["/command /"];
const cursorLine = 0;
const cursorCol = 10; // After the second "/"
const result = await provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
expect(result).not.toBeNull();
if (result) {
expect(result.prefix).toBe("/");
}
});
});
describe("hidden paths", () => {
let baseDir: string;
beforeEach(() => {
baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "autocomplete-test-"));
});
afterEach(() => {
fs.rmSync(baseDir, { recursive: true, force: true });
});
it("matches segmented filenames from abbreviated fuzzy query", async () => {
fs.writeFileSync(path.join(baseDir, "history-search.ts"), "export const x = 1;\n");
const provider = new CombinedAutocompleteProvider([], baseDir);
const line = "@histsr";
const result = await provider.getSuggestions([line], 0, line.length);
const values = result?.items.map(item => item.value) ?? [];
expect(values).toContain("@history-search.ts");
});
it("includes hidden paths but excludes .git", async () => {
for (const dir of [".github", ".git"]) {
fs.mkdirSync(path.join(baseDir, dir), { recursive: true });
}
fs.mkdirSync(path.join(baseDir, ".github", "workflows"), { recursive: true });
fs.writeFileSync(path.join(baseDir, ".github", "workflows", "ci.yml"), "name: ci");
fs.writeFileSync(path.join(baseDir, ".git", "config"), "[core]");
const provider = new CombinedAutocompleteProvider([], baseDir);
const line = "@";
const result = await provider.getSuggestions([line], 0, line.length);
const values = result?.items.map(item => item.value) ?? [];
expect(values).toContain("@.github/");
expect(values.some(value => value === "@.git" || value.startsWith("@.git/"))).toBe(false);
});
});
describe("@ fuzzy search scoped paths", () => {
let rootDir: string;
let baseDir: string;
let outsideDir: string;
beforeEach(() => {
rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "autocomplete-scope-test-"));
baseDir = path.join(rootDir, "cwd");
outsideDir = path.join(rootDir, "outside");
fs.mkdirSync(baseDir, { recursive: true });
fs.mkdirSync(outsideDir, { recursive: true });
});
afterEach(() => {
fs.rmSync(rootDir, { recursive: true, force: true });
});
it("scopes @ fuzzy search to the typed relative path prefix", async () => {
fs.writeFileSync(path.join(baseDir, "alpha-local.ts"), "export const local = 1;\n");
fs.mkdirSync(path.join(outsideDir, "nested", "deeper"), { recursive: true });
fs.writeFileSync(path.join(outsideDir, "nested", "alpha.ts"), "export const alpha = 1;\n");
fs.writeFileSync(path.join(outsideDir, "nested", "deeper", "also-alpha.ts"), "export const also = 1;\n");
fs.writeFileSync(path.join(outsideDir, "nested", "deeper", "zzz.ts"), "export const zzz = 1;\n");
const provider = new CombinedAutocompleteProvider([], baseDir);
const line = "@../outside/a";
const result = await provider.getSuggestions([line], 0, line.length);
const values = result?.items.map(item => item.value) ?? [];
expect(values).toContain("@../outside/nested/alpha.ts");
expect(values).toContain("@../outside/nested/deeper/also-alpha.ts");
expect(values).not.toContain("@../outside/nested/deeper/zzz.ts");
expect(values.some(value => value.includes("alpha-local.ts"))).toBe(false);
});
});
describe("dot-slash path completion", () => {
let baseDir: string;
beforeEach(() => {
baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "autocomplete-dot-slash-test-"));
});
afterEach(() => {
fs.rmSync(baseDir, { recursive: true, force: true });
});
it("preserves ./ prefix when completing files", async () => {
fs.writeFileSync(path.join(baseDir, "update.sh"), "#!/bin/sh\n");
const provider = new CombinedAutocompleteProvider([], baseDir);
const line = "./up";
const result = await provider.getForceFileSuggestions([line], 0, line.length);
expect(result).not.toBeNull();
const values = result?.items.map(item => item.value) ?? [];
expect(values).toContain("./update.sh");
});
it("preserves ./ prefix when completing directories", async () => {
fs.mkdirSync(path.join(baseDir, "src"), { recursive: true });
fs.writeFileSync(path.join(baseDir, "src", "index.ts"), "export {};\n");
const provider = new CombinedAutocompleteProvider([], baseDir);
const line = "./sr";
const result = await provider.getForceFileSuggestions([line], 0, line.length);
expect(result).not.toBeNull();
const values = result?.items.map(item => item.value) ?? [];
expect(values).toContain("./src/");
});
});
});