Files
NetMesh/components/terminal/PromptDetector.fallback.test.ts

688 lines
22 KiB
TypeScript
Raw Permalink Normal View History

import test from "node:test";
import assert from "node:assert/strict";
import { getAlignedPrompt } from "./autocomplete/promptDetector.ts";
import { getCommandToRecordOnEnter } from "./autocomplete/useTerminalAutocomplete.ts";
function createFakeTerm(lineText: string, cursorX: number) {
return {
buffer: {
active: {
cursorX,
cursorY: 0,
baseY: 0,
getLine(line: number) {
if (line !== 0) return undefined;
return {
isWrapped: false,
translateToString() {
return lineText;
},
};
},
},
},
};
}
function createWrappedFakeTerm(rows: string[], cursorY: number, cursorX: number, cols: number) {
return {
cols,
buffer: {
active: {
cursorX,
cursorY,
baseY: 0,
getLine(line: number) {
const lineText = rows[line];
if (lineText === undefined) return undefined;
return {
isWrapped: line > 0,
translateToString() {
return lineText;
},
};
},
},
},
};
}
test("records aligned short commands when standard prompt echo lags by one character", () => {
const cases = [
{ lineText: "$ l", typedInput: "ls" },
{ lineText: "$ c", typedInput: "cd" },
{ lineText: "prod-web> l", typedInput: "ls", promptText: "prod-web> " },
{ lineText: "prod> l", typedInput: "ls", promptText: "prod> " },
{ lineText: "prod.web> l", typedInput: "ls", promptText: "prod.web> " },
{ lineText: "user@host:~$ l", typedInput: "ls", promptText: "user@host:~$ " },
{ lineText: "[user@host ~]$ l", typedInput: "ls", promptText: "[user@host ~]$ " },
{ lineText: "➜ netcatty $ l", typedInput: "ls", promptText: "➜ netcatty $ " },
{ lineText: "➜ git l", typedInput: "ls", promptText: "➜ git " },
{ lineText: "➜ git np", typedInput: "npm", promptText: "➜ git " },
];
for (const { lineText, typedInput, promptText = "$ " } of cases) {
const result = getAlignedPrompt(createFakeTerm(lineText, lineText.length) as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(result.prompt.promptText, promptText, lineText);
assert.equal(result.prompt.userInput, typedInput, lineText);
assert.equal(result.alignedTyped, typedInput, lineText);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, typedInput, true),
typedInput,
lineText,
);
}
});
test("records aligned typed input instead of lagging standard prompt input on Enter", () => {
const typedInput = "git status";
const term = createFakeTerm("$ git ", "$ git ".length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, typedInput, true),
typedInput,
);
});
test("does not record themed prompt decorations when typed input is unreliable", () => {
const cases = [
{
lineText: "➜ ~ git status",
promptText: "➜ ",
expectedUserInput: " ~ git status",
},
{
lineText: "➜ netcatty git:(main) ✗ git status",
promptText: "➜ ",
expectedUserInput: " netcatty git:(main) ✗ git status",
},
{
lineText: " ~ git status",
promptText: " ",
expectedUserInput: " ~ git status",
},
];
for (const { lineText, promptText, expectedUserInput } of cases) {
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
"",
false,
);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(result.prompt.promptText, promptText, lineText);
assert.equal(result.prompt.userInput, expectedUserInput, lineText);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, "", false),
null,
lineText,
);
}
});
test("records recognized themed prompts when typed input is unreliable", () => {
const cases = [
"➜ git status",
" git status",
"➜ netcatty $ git status",
"➜ netcatty git:(main) ✗ $ git status",
" ~ $ git status",
];
for (const lineText of cases) {
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
"",
false,
);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(result.prompt.userInput, "git status", lineText);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, "", false),
"git status",
lineText,
);
}
});
test("aligns themed bare directory prompts with reliable typed input", () => {
const cases = [
{ dir: "netcatty", typedInput: "ls" },
{ dir: "git", typedInput: "ls" },
{ dir: "git", typedInput: "npm" },
{ dir: "git", typedInput: "git status" },
{ dir: "git", typedInput: "npm test" },
{ dir: "make", typedInput: "sudo" },
{ dir: "make", typedInput: "make build" },
{ dir: "make", typedInput: "git status" },
{ dir: "node", typedInput: "yarn" },
{ dir: "node", typedInput: "npm test" },
{ dir: "docker", typedInput: "git status" },
{ dir: "go", typedInput: "test" },
{ dir: "go", typedInput: "npm test" },
{ dir: "kubectl", typedInput: "sudo" },
{ dir: "kubectl", typedInput: "git status" },
];
for (const { dir, typedInput } of cases) {
const lineText = `${dir} ${typedInput}`;
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
typedInput,
true,
);
assert.equal(result.prompt.isAtPrompt, true, dir);
assert.equal(result.prompt.promptText, `${dir} `, dir);
assert.equal(result.prompt.userInput, typedInput, dir);
assert.equal(result.alignedTyped, typedInput, dir);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, typedInput, true),
typedInput,
dir,
);
}
});
test("records reliable typed input before shell echo appears", () => {
const cases = [
{ lineText: "$ ", typedInput: "ls" },
{ lineText: "server> ", typedInput: "exit" },
{ lineText: "staging> ", typedInput: "show dbs" },
{ lineText: "test> ", typedInput: "exit" },
{ lineText: "test> ", typedInput: "help" },
{ lineText: "test> ", typedInput: "show dbs" },
{ lineText: "➜ git ", typedInput: "npm" },
{ lineText: "➜ make ", typedInput: "sudo" },
{ lineText: "➜ node ", typedInput: "yarn" },
];
for (const { lineText, typedInput } of cases) {
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
typedInput,
true,
);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, typedInput, true),
typedInput,
lineText,
);
}
});
test("does not record reliable typed input before interactive echo appears", () => {
const cases = [
{ lineText: "test> ", typedInput: "const x = 1" },
{ lineText: "test> ", typedInput: "await db.users.findOne()" },
{ lineText: "test> ", typedInput: "db" },
{ lineText: "rs0 [direct: primary] reporting> ", typedInput: "const x = 1" },
{ lineText: "rs0 [direct: primary] reporting> ", typedInput: "await db.users.findOne()" },
{ lineText: "rs0 [direct: primary] reporting> ", typedInput: "db.stats()" },
{ lineText: "Atlas a [primary] reporting> ", typedInput: "db.stats()" },
];
for (const { lineText, typedInput } of cases) {
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
typedInput,
true,
);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, typedInput, true),
null,
lineText,
);
}
});
test("detects themed bare directory prompts with standard terminators", () => {
const cases = [
{ lineText: "➜ git $ npm test", promptText: "➜ git $ ", typedInput: "npm test" },
{ lineText: "➜ make $ git status", promptText: "➜ make $ ", typedInput: "git status" },
];
for (const { lineText, promptText, typedInput } of cases) {
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
"",
false,
);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(result.prompt.promptText, promptText, lineText);
assert.equal(result.prompt.userInput, typedInput, lineText);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, "", false),
typedInput,
lineText,
);
}
});
test("does not record path-decorated themed prompts when typed input is unreliable", () => {
const cases = [
"➜ ~/repo git status",
" ~/repo git status",
];
for (const lineText of cases) {
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
"",
false,
);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, "", false),
null,
lineText,
);
}
});
test("does not record partial themed prompt decorations when short command echo lags", () => {
const cases = [
{ lineText: "➜ ~ l", typedInput: "ls" },
{ lineText: "➜ ~ c", typedInput: "cd" },
{ lineText: "➜ ~ s", typedInput: "sudo" },
];
for (const { lineText, typedInput } of cases) {
const result = getAlignedPrompt(
createFakeTerm(lineText, lineText.length) as never,
typedInput,
true,
);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(
getCommandToRecordOnEnter(result.prompt, result.alignedTyped, typedInput, true),
null,
lineText,
);
}
});
test("aligns typed input after a no-space root prompt when a short command echo lags by a word", () => {
const prompt = "root@host:~#";
const cases = [
{ echoedInput: "ls ", typedInput: "ls -la" },
{ echoedInput: "cd ", typedInput: "cd /tmp" },
];
for (const { echoedInput, typedInput } of cases) {
const lineText = `${prompt}${echoedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, true, typedInput);
assert.equal(result.prompt.promptText, prompt, typedInput);
assert.equal(result.prompt.userInput, typedInput, typedInput);
assert.equal(result.alignedTyped, typedInput, typedInput);
}
});
test("aligns typed input after a no-space root prompt when a short command echo lags by one character", () => {
const prompt = " root@stwo:~#";
const cases = [
{ echoedInput: "l", typedInput: "ls" },
{ echoedInput: "c", typedInput: "cd" },
];
for (const { echoedInput, typedInput } of cases) {
const lineText = `${prompt}${echoedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, true, typedInput);
assert.equal(result.prompt.promptText, prompt, typedInput);
assert.equal(result.prompt.userInput, typedInput, typedInput);
assert.equal(result.alignedTyped, typedInput, typedInput);
}
});
test("does not align stale typed input against unrelated prompt text", () => {
const term = createFakeTerm("$ ls", 4);
const result = getAlignedPrompt(term as never, "sudo", true);
assert.equal(result.prompt.isAtPrompt, true);
assert.equal(result.prompt.promptText, "$ ");
assert.equal(result.prompt.userInput, "ls");
assert.equal(result.alignedTyped, null);
});
test("does not align stale typed input when the live command ends with it", () => {
const term = createFakeTerm("$ echo sudo", "$ echo sudo".length);
const result = getAlignedPrompt(term as never, "sudo", true);
assert.equal(result.prompt.isAtPrompt, true);
assert.equal(result.prompt.promptText, "$ ");
assert.equal(result.prompt.userInput, "echo sudo");
assert.equal(result.alignedTyped, null);
});
test("does not align stale typed input after host prompt command symbols", () => {
const prompt = "user@host:~$ ";
const cases = [
`${prompt}echo # sudo`,
`${prompt}printf % sudo`,
`${prompt}echo $ sudo`,
];
for (const lineText of cases) {
const result = getAlignedPrompt(createFakeTerm(lineText, lineText.length) as never, "sudo", true);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(result.prompt.promptText, prompt, lineText);
assert.equal(result.prompt.userInput, lineText.slice(prompt.length), lineText);
assert.equal(result.alignedTyped, null, lineText);
}
});
test("does not align stale typed input when the live path ends with it", () => {
const cases = [
"$ cd ~/sudo",
"$ echo /tmp/sudo",
"$ printf foo:sudo",
"$ cat ./sudo",
"$ run [sudo",
"$ cat > sudo",
"$ echo path#sudo",
"$ echo 100%sudo",
];
for (const lineText of cases) {
const result = getAlignedPrompt(createFakeTerm(lineText, lineText.length) as never, "sudo", true);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(result.prompt.promptText, "$ ", lineText);
assert.equal(result.prompt.userInput, lineText.slice(2), lineText);
assert.equal(result.alignedTyped, null, lineText);
}
});
test("does not align stale typed input from partial echoes after a no-space prompt", () => {
const prompt = " root@stwo:~#";
const cases = [
`${prompt}s`,
`${prompt}sud`,
];
for (const lineText of cases) {
const result = getAlignedPrompt(createFakeTerm(lineText, lineText.length) as never, "sudo", true);
assert.equal(result.prompt.isAtPrompt, false, lineText);
assert.equal(result.alignedTyped, null, lineText);
}
});
test("does not align stale typed input after no-space prompt command suffixes", () => {
const prompt = " root@stwo:~#";
const cases = [
`${prompt}cat > sudo`,
`${prompt}echo # sudo`,
`${prompt}echo $ sudo`,
`${prompt}printf % sudo`,
`${prompt}echo path#sudo`,
`${prompt}> sudo`,
`${prompt}# sudo`,
`${prompt}% sudo`,
`${prompt}$ sudo`,
];
cases.push("root#echo $ sudo", "root@host:~#make $ sudo");
for (const lineText of cases) {
const result = getAlignedPrompt(createFakeTerm(lineText, lineText.length) as never, "sudo", true);
assert.equal(result.prompt.isAtPrompt, false, lineText);
assert.equal(result.alignedTyped, null, lineText);
}
});
test("does not align stale typed input from short standard prompt prefixes", () => {
for (const lineText of ["$ s", "$ su", "$ sud"]) {
const result = getAlignedPrompt(createFakeTerm(lineText, lineText.length) as never, "sudo", true);
assert.equal(result.prompt.isAtPrompt, true, lineText);
assert.equal(result.prompt.promptText, "$ ", lineText);
assert.equal(result.prompt.userInput, lineText.slice(2), lineText);
assert.equal(result.alignedTyped, null, lineText);
}
});
test("aligns wrapped typed input after a no-space root prompt", () => {
const prompt = " root@stwo:~#";
const typedInput = "printf 1234567890";
const cols = 20;
const firstInputSegmentLength = cols - prompt.length;
const rows = [
`${prompt}${typedInput.slice(0, firstInputSegmentLength)}`,
typedInput.slice(firstInputSegmentLength),
];
const term = createWrappedFakeTerm(rows, 1, rows[1].length, cols);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, true);
assert.equal(result.prompt.promptText, prompt);
assert.equal(result.prompt.userInput, typedInput);
assert.equal(result.alignedTyped, typedInput);
});
test("aligns wrapped typed input after a no-space root prompt when shell echo lags", () => {
const prompt = " root@stwo:~#";
const typedInput = "printf 1234567890";
const echoedInput = typedInput.slice(0, -2);
const cols = 20;
const firstInputSegmentLength = cols - prompt.length;
const rows = [
`${prompt}${echoedInput.slice(0, firstInputSegmentLength)}`,
echoedInput.slice(firstInputSegmentLength),
];
const term = createWrappedFakeTerm(rows, 1, rows[1].length, cols);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, true);
assert.equal(result.prompt.promptText, prompt);
assert.equal(result.prompt.userInput, typedInput);
assert.equal(result.alignedTyped, typedInput);
});
test("does not resurrect python REPL prompts during fallback alignment", () => {
const typedInput = "print('ok')";
const lineText = `>>> ${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("does not resurrect mysql REPL prompts during fallback alignment", () => {
const typedInput = "select 1";
const lineText = `mysql> ${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("does not resurrect mysql continuation prompts during fallback alignment", () => {
const prompts = [
" -> ",
" '> ",
" \"> ",
" `> ",
];
for (const prompt of prompts) {
const typedInput = "select 1";
const term = createFakeTerm(`${prompt}${typedInput}`, prompt.length + typedInput.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false, prompt);
assert.equal(result.alignedTyped, null, prompt);
}
});
test("does not resurrect redis-cli REPL prompts during fallback alignment", () => {
const prompts = [
"redis-cli> ",
"redis> ",
"127.0.0.1:6379> ",
"127.0.0.1:6379[1]> ",
"localhost:6379> ",
];
for (const prompt of prompts) {
const typedInput = "get key";
const term = createFakeTerm(`${prompt}${typedInput}`, prompt.length + typedInput.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false, prompt);
assert.equal(result.alignedTyped, null, prompt);
}
});
test("does not resurrect mariadb REPL prompts during fallback alignment", () => {
const typedInput = "select 1";
const prompt = "MariaDB [(none)]> ";
const term = createFakeTerm(`${prompt}${typedInput}`, prompt.length + typedInput.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("does not resurrect postgres REPL prompts during fallback alignment", () => {
for (const prompt of [
"postgres=# ",
"postgres=> ",
"postgres-# ",
"postgres'# ",
"postgres(# ",
"postgres*# ",
"postgres!# ",
"postgres^# ",
"postgres$tag$# ",
"postgres(> ",
"postgres*> ",
"postgres!> ",
"postgres^> ",
"postgres$tag$> ",
]) {
const typedInput = "select 1";
const term = createFakeTerm(`${prompt}${typedInput}`, prompt.length + typedInput.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false, prompt);
assert.equal(result.alignedTyped, null, prompt);
}
});
test("keeps host-style greater-than shell prompts", () => {
const prompt = "prod-web> ";
for (const typedInput of ["deploy", "exit", "show dbs", "use app", "it", "help", "print(1)"]) {
const term = createFakeTerm(`${prompt}${typedInput}`, prompt.length + typedInput.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, true, typedInput);
assert.equal(result.prompt.promptText, prompt, typedInput);
assert.equal(result.prompt.userInput, typedInput, typedInput);
assert.equal(result.alignedTyped, typedInput, typedInput);
}
});
test("does not resurrect shell continuation prompts during fallback alignment", () => {
const typedInput = "echo ok";
const lineText = `> ${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("does not resurrect no-space python REPL prompts during fallback alignment", () => {
const typedInput = "print(1)";
const lineText = `>>>${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("does not resurrect no-space mysql REPL prompts during fallback alignment", () => {
const typedInput = "select 1";
const lineText = `mysql>${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("does not resurrect host-like no-space REPL prompts during fallback alignment", () => {
const typedInput = "select 1";
const lineText = `user@db>${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("does not resurrect no-space shell continuation prompts during fallback alignment", () => {
const typedInput = "echo ok";
const lineText = `>${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, false);
assert.equal(result.alignedTyped, null);
});
test("keeps typed command intact for PUA-only prompts when command text contains Powerline glyphs", () => {
const typedInput = "echo  foo";
const lineText = ` root  ~  ${typedInput}`;
const term = createFakeTerm(lineText, lineText.length);
const result = getAlignedPrompt(term as never, typedInput, true);
assert.equal(result.prompt.isAtPrompt, true);
assert.equal(result.prompt.promptText, " root  ~  ");
assert.equal(result.prompt.userInput, typedInput);
assert.equal(result.alignedTyped, typedInput);
});