1fd832d297
Align several user-observable runtime behaviors with the Ink v7.0.4 parity audit: live input/paste handler refs, duplicate focus id registration, string-only color props, noninteractive empty final newlines, cross-realm error headers, and contained zero-content box layout/paint. Document Vue-specific KEEP decisions and require Conventional Commits for commit messages and PR titles. Co-authored-by: Claude <noreply@anthropic.com>
106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import { defineComponent, nextTick, shallowRef, toRef, type PropType } from "vue";
|
|
import { describe, test, expect } from "vite-plus/test";
|
|
import { render } from "@vue-tui/testing";
|
|
import { Text, useInput, usePaste } from "@vue-tui/runtime";
|
|
|
|
describe("usePaste", () => {
|
|
test("receives pasted text from bracketed paste", async () => {
|
|
const pasted = shallowRef("");
|
|
const App = defineComponent(() => {
|
|
usePaste((text) => {
|
|
pasted.value = text;
|
|
});
|
|
return () => <Text>{pasted.value || "waiting"}</Text>;
|
|
});
|
|
const { stdin } = await render(App);
|
|
await stdin.write("\x1b[200~hello world\x1b[201~");
|
|
expect(pasted.value).toBe("hello world");
|
|
});
|
|
|
|
test("paste falls through to useInput when no paste listeners", async () => {
|
|
const received = shallowRef("");
|
|
const App = defineComponent(() => {
|
|
useInput((input) => {
|
|
received.value += input;
|
|
});
|
|
return () => <Text>{received.value || "waiting"}</Text>;
|
|
});
|
|
const { stdin } = await render(App);
|
|
// Without usePaste, paste events fall through as regular input
|
|
await stdin.write("\x1b[200~pasted\x1b[201~");
|
|
expect(received.value).toBe("pasted");
|
|
});
|
|
|
|
test("respects isActive option", async () => {
|
|
const pasted = shallowRef("");
|
|
const active = shallowRef(false);
|
|
const App = defineComponent(() => {
|
|
usePaste(
|
|
(text) => {
|
|
pasted.value = text;
|
|
},
|
|
{ isActive: active },
|
|
);
|
|
return () => <Text>{pasted.value || "waiting"}</Text>;
|
|
});
|
|
const { stdin } = await render(App);
|
|
await stdin.write("\x1b[200~ignored\x1b[201~");
|
|
expect(pasted.value).toBe("");
|
|
|
|
active.value = true;
|
|
await stdin.write("\x1b[200~captured\x1b[201~");
|
|
expect(pasted.value).toBe("captured");
|
|
});
|
|
|
|
test("accepts a handler ref and calls the latest function", async () => {
|
|
const calls: string[] = [];
|
|
const firstHandler = (text: string) => calls.push(`first:${text}`);
|
|
const secondHandler = (text: string) => calls.push(`second:${text}`);
|
|
const currentHandler = shallowRef(firstHandler);
|
|
|
|
const Child = defineComponent({
|
|
props: {
|
|
onPaste: {
|
|
type: Function as PropType<(text: string) => void>,
|
|
required: true,
|
|
},
|
|
},
|
|
setup(props) {
|
|
usePaste(toRef(props, "onPaste"));
|
|
return () => <Text>child</Text>;
|
|
},
|
|
});
|
|
|
|
const App = defineComponent(() => {
|
|
return () => <Child onPaste={currentHandler.value} />;
|
|
});
|
|
|
|
const { stdin } = await render(App);
|
|
await stdin.write("\x1b[200~alpha\x1b[201~");
|
|
expect(calls).toEqual(["first:alpha"]);
|
|
|
|
currentHandler.value = secondHandler;
|
|
await nextTick();
|
|
await stdin.write("\x1b[200~beta\x1b[201~");
|
|
expect(calls).toEqual(["first:alpha", "second:beta"]);
|
|
});
|
|
|
|
test("usePaste intercepts paste so useInput does not receive it", async () => {
|
|
const inputReceived: string[] = [];
|
|
const pasteReceived: string[] = [];
|
|
const App = defineComponent(() => {
|
|
useInput((input) => {
|
|
inputReceived.push(input);
|
|
});
|
|
usePaste((text) => {
|
|
pasteReceived.push(text);
|
|
});
|
|
return () => <Text>listening</Text>;
|
|
});
|
|
const { stdin } = await render(App);
|
|
await stdin.write("\x1b[200~pasted text\x1b[201~");
|
|
expect(pasteReceived).toEqual(["pasted text"]);
|
|
expect(inputReceived).toEqual([]);
|
|
});
|
|
});
|