Files
llmx/codex-cli/tests/text-buffer-gaps.test.ts

251 lines
9.2 KiB
TypeScript
Raw Normal View History

import TextBuffer from "../src/text-buffer";
import { describe, it, expect } from "vitest";
// The purpose of this testsuite is NOT to make the implementation green today
// quite the opposite. We capture behaviours that are already covered by the
// reference Rust implementation (textarea.rs) but are *still missing* from the
// current TypeScript port. Every test is therefore marked with `.fails()` so
// that the suite passes while the functionality is absent. When a particular
// gap is closed the corresponding test will begin to succeed, causing Vitest to
// raise an error (a *good* error) that reminds us to remove the `.fails` flag.
/* -------------------------------------------------------------------------- */
/* Softtab insertion */
/* -------------------------------------------------------------------------- */
describe("softtab insertion (↹ => 4 spaces)", () => {
it.fails(
"inserts 4 spaces at caret position when hardtab mode is off",
() => {
const buf = new TextBuffer("");
// A literal "\t" character is treated as user pressing the Tab key. The
// Rust version expands it to softtabs by default.
buf.insert("\t");
expect(buf.getText()).toBe(" ");
expect(buf.getCursor()).toEqual([0, 4]);
},
);
});
/* -------------------------------------------------------------------------- */
/* Undo / Redo grouping & stack clearing */
/* -------------------------------------------------------------------------- */
describe("undo / redo advanced behaviour", () => {
it.fails(
"typing a word characterbycharacter should undo in one step",
() => {
const buf = new TextBuffer("");
for (const ch of "hello") {
buf.insert(ch);
}
// One single undo should revert the *whole* word, leaving empty buffer.
buf.undo();
expect(buf.getText()).toBe("");
expect(buf.getCursor()).toEqual([0, 0]);
},
);
});
/* -------------------------------------------------------------------------- */
/* Selection cut / delete selection */
/* -------------------------------------------------------------------------- */
describe("selection cut/delete", () => {
it.fails(
"cut() removes the selected range and yanks it into clipboard",
() => {
const buf = new TextBuffer("foo bar baz");
// Select the middle word "bar"
buf.move("wordRight"); // after "foo" + space => col 4
buf.startSelection();
buf.move("wordRight"); // after "bar" (col 8)
// @ts-expect-error method missing in current implementation
buf.cut();
// Text should now read "foo baz" (two spaces collapsed only if impl trims)
expect(buf.getText()).toBe("foo baz");
// Cursor should be at the start of the gap where text was removed
expect(buf.getCursor()).toEqual([0, 4]);
// And clipboard/yank buffer should contain the deleted word
// @ts-expect-error clipboard getter not exposed yet
expect(buf.getClipboard()).toBe("bar");
},
);
});
/* -------------------------------------------------------------------------- */
/* Wordwise forward deletion (Ctrl+Delete) */
/* -------------------------------------------------------------------------- */
describe("delete_next_word (Ctrl+Delete)", () => {
it.fails("removes everything until the next word boundary", () => {
const vp = { width: 80, height: 25 };
const buf = new TextBuffer("hello world!! next");
// Place caret at start of line (0,0). One Ctrl+Delete should wipe the
// word "hello" and the following space.
buf.handleInput(undefined, { delete: true, ctrl: true }, vp);
expect(buf.getText()).toBe("world!! next");
expect(buf.getCursor()).toEqual([0, 0]);
});
});
/* -------------------------------------------------------------------------- */
/* Configurable tab length */
/* -------------------------------------------------------------------------- */
describe("tab length configuration", () => {
it.fails("inserts the configured number of spaces when tabLen=2", () => {
// @ts-expect-error constructor currently has no config object
const buf = new TextBuffer("", { tabLen: 2 });
buf.insert("\t");
expect(buf.getText()).toBe(" "); // two spaces
expect(buf.getCursor()).toEqual([0, 2]);
});
});
/* -------------------------------------------------------------------------- */
/* Search subsystem */
/* -------------------------------------------------------------------------- */
describe("search / regex navigation", () => {
it.fails("search_forward jumps to the next match", () => {
const text = [
"alpha beta gamma",
"beta gamma alpha",
"gamma alpha beta",
].join("\n");
const buf = new TextBuffer(text);
// @ts-expect-error method missing
buf.setSearchPattern(/beta/);
// Cursor starts at 0,0. First search_forward should land on the first
// occurrence (row 0, col 6)
// @ts-expect-error method missing
buf.searchForward();
expect(buf.getCursor()).toEqual([0, 6]);
// Second invocation should wrap within viewport and find next occurrence
// (row 1, col 0)
// @ts-expect-error method missing
buf.searchForward();
expect(buf.getCursor()).toEqual([1, 0]);
});
});
/* -------------------------------------------------------------------------- */
/* Wordwise navigation accuracy */
/* -------------------------------------------------------------------------- */
describe("wordLeft / wordRight punctuation boundaries", () => {
it.fails("wordLeft stops after punctuation like hyphen (-)", () => {
const buf = new TextBuffer("hello-world");
// Place caret at end of line
buf.move("end");
// Perform a single wordLeft in Rust implementation this lands right
// *after* the hyphen, i.e. between '-' and 'w' (column index 6).
buf.move("wordLeft");
expect(buf.getCursor()).toEqual([0, 6]);
});
it.fails(
"wordRight stops after punctuation like underscore (_) which is not in JS boundary set",
() => {
const buf = new TextBuffer("foo_bar");
// From start, one wordRight should land right after the underscore (col 4)
buf.move("wordRight");
expect(buf.getCursor()).toEqual([0, 4]);
},
);
});
/* -------------------------------------------------------------------------- */
/* Wordwise deletion (Ctrl+Backspace) */
/* -------------------------------------------------------------------------- */
describe("word deletion shortcuts", () => {
it.fails("Ctrl+Backspace deletes the previous word", () => {
const vp = { width: 80, height: 25 };
const buf = new TextBuffer("hello world");
// Place caret after the last character
buf.move("end");
// Simulate Ctrl+Backspace (terminal usually sends backspace with ctrl flag)
buf.handleInput(undefined, { backspace: true, ctrl: true }, vp);
// The whole word "world" (and the preceding space) should be removed,
// leaving just "hello".
expect(buf.getText()).toBe("hello");
expect(buf.getCursor()).toEqual([0, 5]);
});
});
/* -------------------------------------------------------------------------- */
/* Paragraph navigation */
/* -------------------------------------------------------------------------- */
describe("paragraph navigation", () => {
it.fails("Jumping forward by paragraph stops after a blank line", () => {
const text = [
"first paragraph line 1",
"first paragraph line 2",
"", // blank line separates paragraphs
"second paragraph line 1",
].join("\n");
const buf = new TextBuffer(text);
// Start at very beginning
// (No method exposed yet once implemented we will call move("paragraphForward"))
// For now we imitate the call; test will fail until the command exists.
// @ts-expect-error method not implemented yet
buf.move("paragraphForward");
// Expect caret to land at start of the first line _after_ the blank one
expect(buf.getCursor()).toEqual([3, 0]);
});
});
/* -------------------------------------------------------------------------- */
/* Independent scrolling */
/* -------------------------------------------------------------------------- */
describe("viewport scrolling independent of cursor", () => {
it.fails("scrolls without moving the caret", () => {
const lines = Array.from({ length: 100 }, (_, i) => `line ${i}`);
const buf = new TextBuffer(lines.join("\n"));
const vp = { width: 10, height: 5 };
// Cursor stays at 0,0. We now ask the view to scroll down by one page.
// @ts-expect-error method not implemented yet
buf.scroll("pageDown", vp);
// Cursor must remain at (0,0) even though viewport origin changed.
expect(buf.getCursor()).toEqual([0, 0]);
// The first visible line should now be "line 5".
expect(buf.getVisibleLines(vp)[0]).toBe("line 5");
});
});