feat: rewrite stdin pipeline with input-parser, exitOnCtrlC via \x03
- Replace data event with readable + input-parser state machine - Intercept \x03 in input pipeline (not process SIGINT) - useInput now uses parse-keypress for semantic key mapping - 20ms pending escape flush timer matching Ink - Esc focus reset in input pipeline - Delete old key-parser.ts (replaced by input-parser + parse-keypress) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { defineComponent, shallowRef } from "vue";
|
||||
import { expect, test } from "vite-plus/test";
|
||||
import { expect, test, vi } from "vite-plus/test";
|
||||
import { render } from "@vue-tui/testing";
|
||||
import { Text, useInput, useStdout, type Key } from "@vue-tui/runtime";
|
||||
|
||||
@@ -363,3 +363,18 @@ test("useInput - handle page down", async () => {
|
||||
await stdin.write("\x1b[6~");
|
||||
expect(calls[0]?.key.pageDown).toBe(true);
|
||||
});
|
||||
|
||||
// --- exitOnCtrlC ---
|
||||
|
||||
test("exitOnCtrlC intercepts \\x03 in raw mode", async () => {
|
||||
const handler = vi.fn();
|
||||
const App = defineComponent(() => {
|
||||
useInput(handler);
|
||||
return () => <Text>running</Text>;
|
||||
});
|
||||
const { stdin, waitUntilExit } = await render(App, { exitOnCtrlC: true });
|
||||
await stdin.write("\x03");
|
||||
// Should exit without calling user handler
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
await expect(waitUntilExit()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
import { defineComponent } from "vue";
|
||||
import { expect, test } from "vite-plus/test";
|
||||
import { expect, test, vi } from "vite-plus/test";
|
||||
import { render } from "@vue-tui/testing";
|
||||
import { Text } from "@vue-tui/runtime";
|
||||
import { Text, useInput } from "@vue-tui/runtime";
|
||||
|
||||
test("exitOnCtrlC registers a SIGINT handler that unmount removes", async () => {
|
||||
const before = process.listenerCount("SIGINT");
|
||||
test("exitOnCtrlC intercepts \\x03 and exits the app", async () => {
|
||||
const handler = vi.fn();
|
||||
const App = defineComponent(() => {
|
||||
useInput(handler);
|
||||
return () => <Text>x</Text>;
|
||||
});
|
||||
const { stdin, waitUntilExit } = await render(App, { exitOnCtrlC: true });
|
||||
|
||||
const App = defineComponent(() => () => <Text>x</Text>);
|
||||
const { unmount } = await render(App, { exitOnCtrlC: true });
|
||||
|
||||
expect(process.listenerCount("SIGINT")).toBe(before + 1);
|
||||
unmount();
|
||||
expect(process.listenerCount("SIGINT")).toBe(before);
|
||||
await stdin.write("\x03");
|
||||
// \x03 is intercepted before reaching useInput handlers
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
await expect(waitUntilExit()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("exitOnCtrlC=false registers no SIGINT handler", async () => {
|
||||
const before = process.listenerCount("SIGINT");
|
||||
test("exitOnCtrlC=false does not intercept \\x03", async () => {
|
||||
const calls: Array<{ input: string }> = [];
|
||||
const App = defineComponent(() => {
|
||||
useInput((input) => calls.push({ input }));
|
||||
return () => <Text>x</Text>;
|
||||
});
|
||||
const { stdin, unmount } = await render(App, { exitOnCtrlC: false });
|
||||
|
||||
const App = defineComponent(() => () => <Text>x</Text>);
|
||||
const { unmount } = await render(App, { exitOnCtrlC: false });
|
||||
|
||||
expect(process.listenerCount("SIGINT")).toBe(before);
|
||||
await stdin.write("\x03");
|
||||
// With exitOnCtrlC=false, Ctrl+C reaches the useInput handler
|
||||
expect(calls.length).toBe(1);
|
||||
expect(calls[0]?.input).toBe("c");
|
||||
unmount();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user