Files
ansiblings/packages/nopy/tests/prompts.pty.test.ts
T

78 lines
2.8 KiB
TypeScript

/**
* The variable form, on a terminal that reports no size.
*
* This is the one case that cannot be tested from inside a vitest worker: there
* is no TTY there for enquirer to misread, so the mocked tests in
* `prompts.test.ts` prove only that `rows` is *passed*, never that passing it
* matters. Here a real pty is opened at 0x0 — `pty.fork()`'s own default, and
* what `script -q` and some CI terminals report — and a real form is answered
* through it.
*
* Measured both ways while writing this: with `terminalSize()` removed from
* `nopy.prompts.ts`, the form never renders and the driver times out with
* nothing on the wire.
*
* Needs `python3` for the pty; skipped, loudly, where there is none.
*/
import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
const EXPECT_PY = fileURLToPath(new URL('../../../scripts/expect.py', import.meta.url));
const TSX = fileURLToPath(new URL('../node_modules/.bin/tsx', import.meta.url));
const PROBE = fileURLToPath(new URL('./fixtures/form-probe.ts', import.meta.url));
const hasPython = spawnSync('python3', ['--version']).status === 0;
/** Down arrow — how the form moves from one field to the next. */
const DOWN = '\u001b[B';
const STEPS = [
{ expect: 'ALPHA', send: 'alpha-typed', settle: 0.6 },
{ send: DOWN, settle: 0.4 },
{ send: 'beta-typed', settle: 0.4 },
{ send: '\r', settle: 1.2 },
];
describe.skipIf(!hasPython)('variable form over a pty', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nopy-pty-'));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
/** Answers the probe form on a pty of the given size; returns what it assigned. */
const answerForm = (rows: number, cols: number) => {
const stepsPath = path.join(tmpDir, 'steps.json');
const logPath = path.join(tmpDir, 'session.log');
fs.writeFileSync(stepsPath, JSON.stringify(STEPS));
execFileSync('python3', [EXPECT_PY, stepsPath, '--', TSX, PROBE], {
env: {
...process.env,
PTY_ROWS: String(rows),
PTY_COLS: String(cols),
EXPECT_TIMEOUT: '60',
EXPECT_LOG: logPath,
},
stdio: 'pipe',
});
const transcript = fs.readFileSync(logPath, 'utf-8').replace(/\r/g, '');
const line = transcript.split('\n').find((l) => l.startsWith('NOPY_PROBE '));
return line ? JSON.parse(line.slice('NOPY_PROBE '.length)) : undefined;
};
it('collects every field on a terminal reporting 0x0', () => {
expect(answerForm(0, 0)).toEqual({ ALPHA: 'alpha-typed', BETA: 'beta-typed' });
}, 90_000);
});