UNPKG

@inquirer/testing

Version:
64 lines (63 loc) 2.48 kB
import { stripVTControlCharacters } from 'node:util'; import MuteStream from 'mute-stream'; import { BufferedStream } from "./buffered-stream.js"; import { interpretTerminalOutput } from "./terminal.js"; export async function render(prompt, config, options) { const input = new MuteStream(); input.unmute(); const output = new BufferedStream(); const firstRender = new Promise((resolve) => output.once('render', resolve)); const answer = prompt(config, { ...options, input, output }); // The first render is synchronous. If our BufferedStream received a write, we're ready. if (output.writeCount === 0) { // Our BufferedStream didn't receive a write yet. This happens when the prompt // errored before rendering. Race against the answer promise to handle that case. await Promise.race([firstRender, answer.catch(() => { })]); } const events = { keypress(key) { if (typeof key === 'string') { input.emit('keypress', null, { name: key }); } else { input.emit('keypress', null, key); } }, type(text) { input.write(text); for (const char of text) { input.emit('keypress', null, { name: char }); } }, }; let rendersConsumed = output.writeCount; function nextRender() { const waitForRender = output.writeCount > rendersConsumed ? Promise.resolve() : new Promise((resolve) => output.once('render', resolve)); // After detecting a render, let the event loop settle so that multiple // synchronous-ish renders (e.g. validation: loading → error) are coalesced. return waitForRender.then(() => new Promise((resolve) => { setImmediate(() => { rendersConsumed = output.writeCount; resolve(); }); })); } return { answer, input, events, getScreen: ({ raw } = {}) => { const lastScreen = output.getLastChunk({ raw: Boolean(raw) }); return raw ? lastScreen : stripVTControlCharacters(lastScreen).trim(); }, getFullOutput: async ({ raw } = {}) => { const fullOutput = output.getFullOutput(); if (raw) return fullOutput; return interpretTerminalOutput(fullOutput); }, nextRender, }; }