Massive refactor of API
- Switch to function based API - Anthropic SDK style async generator - Fully typed with escape hatches for custom models
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
import { test, describe } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { VirtualTerminal } from "./virtual-terminal.js";
|
||||
import { describe, test } from "node:test";
|
||||
import {
|
||||
TUI,
|
||||
Container,
|
||||
TextComponent,
|
||||
TextEditor,
|
||||
WhitespaceComponent,
|
||||
MarkdownComponent,
|
||||
SelectList,
|
||||
TextComponent,
|
||||
TextEditor,
|
||||
TUI,
|
||||
WhitespaceComponent,
|
||||
} from "../src/index.js";
|
||||
import { VirtualTerminal } from "./virtual-terminal.js";
|
||||
|
||||
describe("TUI Rendering", () => {
|
||||
|
||||
test("renders single text component", async () => {
|
||||
const terminal = new VirtualTerminal(80, 24);
|
||||
const ui = new TUI(terminal);
|
||||
@@ -22,7 +21,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(text);
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
// Wait for writes to complete and get the rendered output
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
@@ -48,7 +47,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(new TextComponent("Line 3"));
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
assert.strictEqual(output[0], "Line 1");
|
||||
@@ -68,7 +67,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(new TextComponent("Bottom text"));
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
assert.strictEqual(output[0], "Top text");
|
||||
@@ -96,7 +95,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(new TextComponent("After container"));
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
assert.strictEqual(output[0], "Before container");
|
||||
@@ -117,11 +116,11 @@ describe("TUI Rendering", () => {
|
||||
ui.setFocus(editor);
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
// Initial state - empty editor with cursor
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
|
||||
|
||||
// Check that we have the border characters
|
||||
assert.ok(output[0].includes("╭"));
|
||||
assert.ok(output[0].includes("╮"));
|
||||
@@ -142,7 +141,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(dynamicText);
|
||||
|
||||
// Wait for initial render
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
await terminal.flush();
|
||||
|
||||
// Save initial state
|
||||
@@ -153,8 +152,8 @@ describe("TUI Rendering", () => {
|
||||
ui.requestRender();
|
||||
|
||||
// Wait for render
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
// Flush terminal buffer
|
||||
await terminal.flush();
|
||||
|
||||
@@ -180,7 +179,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(text3);
|
||||
|
||||
// Wait for initial render
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
let output = await terminal.flushAndGetViewport();
|
||||
assert.strictEqual(output[0], "Line 1");
|
||||
@@ -191,7 +190,7 @@ describe("TUI Rendering", () => {
|
||||
ui.removeChild(text2);
|
||||
ui.requestRender();
|
||||
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
output = await terminal.flushAndGetViewport();
|
||||
assert.strictEqual(output[0], "Line 1");
|
||||
@@ -212,7 +211,7 @@ describe("TUI Rendering", () => {
|
||||
}
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
|
||||
@@ -241,7 +240,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(new TextComponent("After"));
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
assert.strictEqual(output[0], "Before");
|
||||
@@ -262,7 +261,7 @@ describe("TUI Rendering", () => {
|
||||
ui.addChild(markdown);
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
// Should have formatted markdown
|
||||
@@ -289,7 +288,7 @@ describe("TUI Rendering", () => {
|
||||
ui.setFocus(selectList);
|
||||
|
||||
// Wait for next tick for render to complete
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
|
||||
const output = await terminal.flushAndGetViewport();
|
||||
// First option should be selected (has → indicator)
|
||||
@@ -303,28 +302,28 @@ describe("TUI Rendering", () => {
|
||||
|
||||
test("preserves existing terminal content when rendering", async () => {
|
||||
const terminal = new VirtualTerminal(80, 24);
|
||||
|
||||
|
||||
// Write some content to the terminal before starting TUI
|
||||
// This simulates having existing content in the scrollback buffer
|
||||
terminal.write("Previous command output line 1\r\n");
|
||||
terminal.write("Previous command output line 2\r\n");
|
||||
terminal.write("Some important information\r\n");
|
||||
terminal.write("Last line before TUI starts\r\n");
|
||||
|
||||
|
||||
// Flush to ensure writes are complete
|
||||
await terminal.flush();
|
||||
|
||||
|
||||
// Get the initial state with existing content
|
||||
const initialOutput = [...terminal.getViewport()];
|
||||
assert.strictEqual(initialOutput[0], "Previous command output line 1");
|
||||
assert.strictEqual(initialOutput[1], "Previous command output line 2");
|
||||
assert.strictEqual(initialOutput[2], "Some important information");
|
||||
assert.strictEqual(initialOutput[3], "Last line before TUI starts");
|
||||
|
||||
|
||||
// Now start the TUI with a text editor
|
||||
const ui = new TUI(terminal);
|
||||
ui.start();
|
||||
|
||||
|
||||
const editor = new TextEditor();
|
||||
let submittedText = "";
|
||||
editor.onSubmit = (text) => {
|
||||
@@ -332,87 +331,87 @@ describe("TUI Rendering", () => {
|
||||
};
|
||||
ui.addChild(editor);
|
||||
ui.setFocus(editor);
|
||||
|
||||
|
||||
// Wait for initial render
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
await terminal.flush();
|
||||
|
||||
|
||||
// Check that the editor is rendered after the existing content
|
||||
const afterTuiStart = terminal.getViewport();
|
||||
|
||||
|
||||
// The existing content should still be visible above the editor
|
||||
assert.strictEqual(afterTuiStart[0], "Previous command output line 1");
|
||||
assert.strictEqual(afterTuiStart[1], "Previous command output line 2");
|
||||
assert.strictEqual(afterTuiStart[2], "Some important information");
|
||||
assert.strictEqual(afterTuiStart[3], "Last line before TUI starts");
|
||||
|
||||
|
||||
// The editor should appear after the existing content
|
||||
// The editor is 3 lines tall (top border, content line, bottom border)
|
||||
// Top border with box drawing characters filling the width (80 chars)
|
||||
assert.strictEqual(afterTuiStart[4][0], "╭");
|
||||
assert.strictEqual(afterTuiStart[4][78], "╮");
|
||||
|
||||
|
||||
// Content line should have the prompt
|
||||
assert.strictEqual(afterTuiStart[5].substring(0, 4), "│ > ");
|
||||
// And should end with vertical bar
|
||||
assert.strictEqual(afterTuiStart[5][78], "│");
|
||||
|
||||
|
||||
// Bottom border
|
||||
assert.strictEqual(afterTuiStart[6][0], "╰");
|
||||
assert.strictEqual(afterTuiStart[6][78], "╯");
|
||||
|
||||
|
||||
// Type some text into the editor
|
||||
terminal.sendInput("Hello World");
|
||||
|
||||
|
||||
// Wait for the input to be processed
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
await terminal.flush();
|
||||
|
||||
|
||||
// Check that text appears in the editor
|
||||
const afterTyping = terminal.getViewport();
|
||||
assert.strictEqual(afterTyping[0], "Previous command output line 1");
|
||||
assert.strictEqual(afterTyping[1], "Previous command output line 2");
|
||||
assert.strictEqual(afterTyping[2], "Some important information");
|
||||
assert.strictEqual(afterTyping[3], "Last line before TUI starts");
|
||||
|
||||
|
||||
// The editor content should show the typed text with the prompt ">"
|
||||
assert.strictEqual(afterTyping[5].substring(0, 15), "│ > Hello World");
|
||||
|
||||
|
||||
// Send SHIFT+ENTER to the editor (adds a new line)
|
||||
// According to text-editor.ts line 251, SHIFT+ENTER is detected as "\n" which calls addNewLine()
|
||||
terminal.sendInput("\n");
|
||||
|
||||
|
||||
// Wait for the input to be processed
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
await terminal.flush();
|
||||
|
||||
|
||||
// Check that existing content is still preserved after adding new line
|
||||
const afterNewLine = terminal.getViewport();
|
||||
assert.strictEqual(afterNewLine[0], "Previous command output line 1");
|
||||
assert.strictEqual(afterNewLine[1], "Previous command output line 2");
|
||||
assert.strictEqual(afterNewLine[2], "Some important information");
|
||||
assert.strictEqual(afterNewLine[3], "Last line before TUI starts");
|
||||
|
||||
|
||||
// Editor should now be 4 lines tall (top border, first line, second line, bottom border)
|
||||
// Top border at line 4
|
||||
assert.strictEqual(afterNewLine[4][0], "╭");
|
||||
assert.strictEqual(afterNewLine[4][78], "╮");
|
||||
|
||||
|
||||
// First line with text at line 5
|
||||
assert.strictEqual(afterNewLine[5].substring(0, 15), "│ > Hello World");
|
||||
assert.strictEqual(afterNewLine[5][78], "│");
|
||||
|
||||
|
||||
// Second line (empty, with continuation prompt " ") at line 6
|
||||
assert.strictEqual(afterNewLine[6].substring(0, 4), "│ ");
|
||||
assert.strictEqual(afterNewLine[6][78], "│");
|
||||
|
||||
|
||||
// Bottom border at line 7
|
||||
assert.strictEqual(afterNewLine[7][0], "╰");
|
||||
assert.strictEqual(afterNewLine[7][78], "╯");
|
||||
|
||||
|
||||
// Verify that onSubmit was NOT called (since we pressed SHIFT+ENTER, not plain ENTER)
|
||||
assert.strictEqual(submittedText, "");
|
||||
|
||||
|
||||
ui.stop();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user