|
| 1 | +import { PassThrough } from 'node:stream' |
| 2 | +import { afterEach, expect, it, vi } from 'vitest' |
| 3 | +import { confirmInTerminal } from '../src/terminal-confirm' |
| 4 | + |
| 5 | +const COMMAND = ['npm', 'i', '-D', '@nuxt/devtools@^4.0.0'] |
| 6 | + |
| 7 | +function terminal({ isTTY = true, isRaw = false } = {}) { |
| 8 | + const asked: string[] = [] |
| 9 | + const warned: string[] = [] |
| 10 | + return { |
| 11 | + input: Object.assign(new PassThrough(), { isTTY, isRaw }), |
| 12 | + asked, |
| 13 | + warned, |
| 14 | + ask: (message: string) => asked.push(message), |
| 15 | + warn: (message: string) => warned.push(message), |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +afterEach(() => { |
| 20 | + vi.useRealTimers() |
| 21 | +}) |
| 22 | + |
| 23 | +it.each([ |
| 24 | + // nuxi's TUI holds stdin in raw mode, so a single key answers |
| 25 | + [true, 'y', true], |
| 26 | + [true, 'Y', true], |
| 27 | + [true, 'w', false], |
| 28 | + [true, '\r', false], |
| 29 | + // nuxi's plain mode reads lines |
| 30 | + [false, 'yes\n', true], |
| 31 | + [false, 'y\n', true], |
| 32 | + [false, '\n', false], |
| 33 | + [false, 'no\n', false], |
| 34 | +])('raw mode %s: answers %j with %s', async (isRaw, key, allowed) => { |
| 35 | + const t = terminal({ isRaw }) |
| 36 | + const answer = confirmInTerminal(COMMAND, t) |
| 37 | + expect(t.asked[0]).toMatch(isRaw ? /^Press y / : /^Type yes and press Enter /) |
| 38 | + expect(t.asked[0]).toContain('npm i -D @nuxt/devtools@^4.0.0') |
| 39 | + |
| 40 | + t.input.write(key) |
| 41 | + expect(await answer).toBe(allowed) |
| 42 | + expect(t.input.readableFlowing).toBe(false) |
| 43 | +}) |
| 44 | + |
| 45 | +it('keeps the terminal flowing for the TUI that read it first', async () => { |
| 46 | + const t = terminal({ isRaw: true }) |
| 47 | + const keys: string[] = [] |
| 48 | + t.input.on('data', chunk => keys.push(String(chunk))) |
| 49 | + |
| 50 | + const answer = confirmInTerminal(COMMAND, t) |
| 51 | + t.input.write('y') |
| 52 | + expect(await answer).toBe(true) |
| 53 | + expect(t.input.readableFlowing).toBe(true) |
| 54 | + expect(keys).toEqual(['y']) |
| 55 | +}) |
| 56 | + |
| 57 | +it('refuses at once without a terminal', async () => { |
| 58 | + const t = terminal({ isTTY: false }) |
| 59 | + expect(await confirmInTerminal(COMMAND, t)).toBe(false) |
| 60 | + expect(t.asked).toEqual([]) |
| 61 | + expect(t.warned[0]).toContain('npm i -D @nuxt/devtools@^4.0.0') |
| 62 | +}) |
| 63 | + |
| 64 | +it('refuses when nobody answers within 5 minutes', async () => { |
| 65 | + vi.useFakeTimers() |
| 66 | + const t = terminal({ isRaw: true }) |
| 67 | + const answer = confirmInTerminal(COMMAND, t) |
| 68 | + |
| 69 | + vi.advanceTimersByTime(5 * 60_000) |
| 70 | + expect(await answer).toBe(false) |
| 71 | + expect(t.warned).toHaveLength(1) |
| 72 | + t.input.write('y') |
| 73 | + expect(t.input.readableFlowing).toBe(false) |
| 74 | +}) |
0 commit comments