78 lines
3.2 KiB
TypeScript
78 lines
3.2 KiB
TypeScript
|
|
// These tests exercise MultilineTextEditor behaviour when the editor width is
|
|||
|
|
// *not* provided via props so that it has to derive its width from the current
|
|||
|
|
// terminal size. We emulate a terminal‑resize by mutating
|
|||
|
|
// `process.stdout.columns` and emitting a synthetic `resize` event – the
|
|||
|
|
// `useTerminalSize` hook listens for that and causes the component to
|
|||
|
|
// re‑render. The test then asserts that
|
|||
|
|
// 1. The rendered line re‑wraps to the new width, *and*
|
|||
|
|
// 2. The caret (highlighted inverse character) is still kept in view after
|
|||
|
|
// the horizontal shrink so that editing remains possible.
|
|||
|
|
|
|||
|
|
import { renderTui } from "./ui-test-helpers.js";
|
|||
|
|
import MultilineTextEditor from "../src/components/chat/multiline-editor.js";
|
|||
|
|
import * as React from "react";
|
|||
|
|
import { describe, it, expect } from "vitest";
|
|||
|
|
|
|||
|
|
// Helper to synchronously type text then flush Ink's timers so that the next
|
|||
|
|
// `lastFrame()` call sees the updated UI.
|
|||
|
|
async function type(
|
|||
|
|
stdin: NodeJS.WritableStream,
|
|||
|
|
text: string,
|
|||
|
|
flush: () => Promise<void>,
|
|||
|
|
) {
|
|||
|
|
stdin.write(text);
|
|||
|
|
await flush();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
describe("MultilineTextEditor – dynamic width", () => {
|
|||
|
|
// The dynamic horizontal scroll logic is still flaky – mark as an expected
|
|||
|
|
// *failing* test so it doesn't break CI until the feature is aligned with
|
|||
|
|
// the Rust implementation.
|
|||
|
|
it("keeps the caret visible when the terminal width shrinks", async () => {
|
|||
|
|
// Fake an initial terminal width large enough that no horizontal
|
|||
|
|
// scrolling is required while we type the long alphabet sequence.
|
|||
|
|
process.stdout.columns = 40; // width seen by useTerminalSize (after padding)
|
|||
|
|
|
|||
|
|
const { stdin, lastFrame, flush, cleanup } = renderTui(
|
|||
|
|
React.createElement(MultilineTextEditor, {
|
|||
|
|
initialText: "",
|
|||
|
|
// width *omitted* – component should fall back to terminal columns
|
|||
|
|
height: 3,
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Ensure initial render completes.
|
|||
|
|
await flush();
|
|||
|
|
|
|||
|
|
// Type the alphabet – longer than the width we'll shrink to.
|
|||
|
|
const alphabet = "abcdefghijklmnopqrstuvwxyz";
|
|||
|
|
await type(stdin, alphabet, flush);
|
|||
|
|
|
|||
|
|
// The cursor (block) now sits on the far right after the 'z'. Verify that
|
|||
|
|
// the character 'z' is visible in the current frame.
|
|||
|
|
expect(lastFrame()?.includes("z")).toBe(true);
|
|||
|
|
|
|||
|
|
/* ----------------------- Simulate resize ----------------------- */
|
|||
|
|
|
|||
|
|
// Shrink the reported terminal width so that the previously visible slice
|
|||
|
|
// would no longer include the cursor *unless* the editor re‑computes
|
|||
|
|
// scroll offsets on re‑render.
|
|||
|
|
process.stdout.columns = 20; // shrink significantly (remember: padding‑8)
|
|||
|
|
process.stdout.emit("resize"); // notify listeners
|
|||
|
|
|
|||
|
|
// Allow Ink to schedule the state update and then perform the re‑render.
|
|||
|
|
await flush();
|
|||
|
|
await flush();
|
|||
|
|
|
|||
|
|
// After the resize the editor should have scrolled horizontally so that
|
|||
|
|
// the caret (and thus the 'z' character that is block‑highlighted) remains
|
|||
|
|
// visible in the rendered slice.
|
|||
|
|
const frameAfter = lastFrame() || "";
|
|||
|
|
// eslint-disable-next-line no-console
|
|||
|
|
console.log("FRAME AFTER RESIZE:\n" + frameAfter);
|
|||
|
|
expect(frameAfter.includes("z")).toBe(true);
|
|||
|
|
|
|||
|
|
cleanup();
|
|||
|
|
});
|
|||
|
|
});
|