From fe20db05e1d65b59ddc0fc41ade5c75150667f3f Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 2 Apr 2026 19:17:40 -0400 Subject: [PATCH 1/3] test(core): colocate tests and migrate to test-utils --- packages/core/package.json | 2 +- .../prompts/autocomplete.test.ts | 97 +++-- .../{test => src}/prompts/confirm.test.ts | 47 +-- .../core/{test => src}/prompts/date.test.ts | 203 +++++----- packages/core/src/prompts/multi-line.test.ts | 348 +++++++++++++++++ .../prompts/multi-select.test.ts | 103 +++-- .../{test => src}/prompts/password.test.ts | 61 ++- .../core/{test => src}/prompts/prompt.test.ts | 121 +++--- .../core/{test => src}/prompts/select.test.ts | 69 ++-- .../core/{test => src}/prompts/text.test.ts | 77 ++-- .../core/{test => src}/utils/cursor.test.ts | 0 .../utils.test.ts => src/utils/index.test.ts} | 26 +- packages/core/test/mock-readable.ts | 26 -- packages/core/test/mock-writable.ts | 14 - packages/core/test/prompts/multi-line.test.ts | 355 ------------------ 15 files changed, 718 insertions(+), 831 deletions(-) rename packages/core/{test => src}/prompts/autocomplete.test.ts (78%) rename packages/core/{test => src}/prompts/confirm.test.ts (63%) rename packages/core/{test => src}/prompts/date.test.ts (69%) create mode 100644 packages/core/src/prompts/multi-line.test.ts rename packages/core/{test => src}/prompts/multi-select.test.ts (67%) rename packages/core/{test => src}/prompts/password.test.ts (57%) rename packages/core/{test => src}/prompts/prompt.test.ts (73%) rename packages/core/{test => src}/prompts/select.test.ts (68%) rename packages/core/{test => src}/prompts/text.test.ts (64%) rename packages/core/{test => src}/utils/cursor.test.ts (100%) rename packages/core/{test/utils.test.ts => src/utils/index.test.ts} (76%) delete mode 100644 packages/core/test/mock-readable.ts delete mode 100644 packages/core/test/mock-writable.ts delete mode 100644 packages/core/test/prompts/multi-line.test.ts diff --git a/packages/core/package.json b/packages/core/package.json index 7ae47af9..af363b5f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -48,7 +48,7 @@ "license": "MIT", "packageManager": "pnpm@9.14.2", "scripts": { - "build": "bsh build", + "build": "bsh build \"src/**/*.ts\" \"!src/**/*.test.ts\"", "prepack": "pnpm build", "test": "bsh test", "lint": "bsh lint" diff --git a/packages/core/test/prompts/autocomplete.test.ts b/packages/core/src/prompts/autocomplete.test.ts similarity index 78% rename from packages/core/test/prompts/autocomplete.test.ts rename to packages/core/src/prompts/autocomplete.test.ts index f1d98e24..9044c8f0 100644 --- a/packages/core/test/prompts/autocomplete.test.ts +++ b/packages/core/src/prompts/autocomplete.test.ts @@ -1,12 +1,10 @@ import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as AutocompletePrompt } from '../../src/prompts/autocomplete.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as AutocompletePrompt } from './autocomplete.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('AutocompletePrompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; const testOptions = [ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, @@ -16,29 +14,24 @@ describe('AutocompletePrompt', () => { ]; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, }); instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); test('initial options match provided options', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, }); @@ -52,8 +45,8 @@ describe('AutocompletePrompt', () => { test('cursor navigation with event emitter', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, }); @@ -78,8 +71,8 @@ describe('AutocompletePrompt', () => { test('initialValue selects correct option', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, initialValue: ['cherry'], @@ -95,8 +88,8 @@ describe('AutocompletePrompt', () => { test('initialValue defaults to first option when non-multiple', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, }); @@ -107,8 +100,8 @@ describe('AutocompletePrompt', () => { test('initialValue is empty when multiple', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, multiple: true, @@ -120,8 +113,8 @@ describe('AutocompletePrompt', () => { test('filtering through user input', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, }); @@ -132,7 +125,7 @@ describe('AutocompletePrompt', () => { expect(instance.filteredOptions.length).to.equal(testOptions.length); // Simulate typing 'a' by emitting keypress event - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); // Check that filtered options are updated to include options with 'a' expect(instance.filteredOptions.length).to.be.lessThan(testOptions.length); @@ -144,37 +137,37 @@ describe('AutocompletePrompt', () => { test('default filter function works correctly', () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, }); instance.prompt(); - input.emit('keypress', 'a', { name: 'a' }); - input.emit('keypress', 'p', { name: 'p' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'p', { name: 'p' }); expect(instance.filteredOptions).toEqual([ { value: 'apple', label: 'Apple' }, { value: 'grape', label: 'Grape' }, ]); - input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); expect(instance.filteredOptions).toEqual([]); }); test('submit without nav resolves to first option in non-multiple', async () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, }); const promise = instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await promise; expect(instance.selectedValues).to.deep.equal(['apple']); @@ -183,15 +176,15 @@ describe('AutocompletePrompt', () => { test('submit without nav resolves to [] in multiple', async () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, multiple: true, }); const promise = instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await promise; expect(instance.selectedValues).to.deep.equal([]); @@ -200,16 +193,16 @@ describe('AutocompletePrompt', () => { test('Tab with empty input and placeholder fills input and submit returns matching option', async () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, placeholder: 'apple', }); const promise = instance.prompt(); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await promise; expect(instance.userInput).to.equal('apple'); @@ -222,15 +215,15 @@ describe('AutocompletePrompt', () => { { value: 'banana', label: 'Banana' }, ]; const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: () => dynamicOptions, }); instance.prompt(); - input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); expect(instance.filteredOptions).toEqual(dynamicOptions); }); @@ -242,8 +235,8 @@ describe('AutocompletePrompt', () => { { value: 'cherry', label: 'Cherry' }, ]; const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: () => dynamicOptions, filter: (search, opt) => (opt.label ?? '').toLowerCase().endsWith(search.toLowerCase()), @@ -251,7 +244,7 @@ describe('AutocompletePrompt', () => { instance.prompt(); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); // 'endsWith' matches Banana but not Apple or Cherry expect(instance.filteredOptions).toEqual([{ value: 'banana', label: 'Banana' }]); @@ -259,15 +252,15 @@ describe('AutocompletePrompt', () => { test('Tab with non-matching placeholder does not fill input', async () => { const instance = new AutocompletePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: testOptions, placeholder: 'Type to search...', }); instance.prompt(); - input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); // Placeholder does not match any option, so input must not be filled with placeholder expect(instance.userInput).not.to.equal('Type to search...'); diff --git a/packages/core/test/prompts/confirm.test.ts b/packages/core/src/prompts/confirm.test.ts similarity index 63% rename from packages/core/test/prompts/confirm.test.ts rename to packages/core/src/prompts/confirm.test.ts index 8ad14c56..0f95bd87 100644 --- a/packages/core/test/prompts/confirm.test.ts +++ b/packages/core/src/prompts/confirm.test.ts @@ -1,38 +1,31 @@ import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as ConfirmPrompt } from '../../src/prompts/confirm.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as ConfirmPrompt } from './confirm.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('ConfirmPrompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new ConfirmPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', active: 'yes', inactive: 'no', }); instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); test('sets value and submits on confirm (y)', () => { const instance = new ConfirmPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', active: 'yes', inactive: 'no', @@ -40,7 +33,7 @@ describe('ConfirmPrompt', () => { }); instance.prompt(); - input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); expect(instance.value).to.equal(true); expect(instance.state).to.equal('submit'); @@ -48,8 +41,8 @@ describe('ConfirmPrompt', () => { test('sets value and submits on confirm (n)', () => { const instance = new ConfirmPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', active: 'yes', inactive: 'no', @@ -57,7 +50,7 @@ describe('ConfirmPrompt', () => { }); instance.prompt(); - input.emit('keypress', 'n', { name: 'n' }); + mocks.input.emit('keypress', 'n', { name: 'n' }); expect(instance.value).to.equal(false); expect(instance.state).to.equal('submit'); @@ -66,8 +59,8 @@ describe('ConfirmPrompt', () => { describe('cursor', () => { test('cursor is 1 when inactive', () => { const instance = new ConfirmPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', active: 'yes', inactive: 'no', @@ -75,14 +68,14 @@ describe('ConfirmPrompt', () => { }); instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.cursor).to.equal(1); }); test('cursor is 0 when active', () => { const instance = new ConfirmPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', active: 'yes', inactive: 'no', @@ -90,7 +83,7 @@ describe('ConfirmPrompt', () => { }); instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.cursor).to.equal(0); }); }); diff --git a/packages/core/test/prompts/date.test.ts b/packages/core/src/prompts/date.test.ts similarity index 69% rename from packages/core/test/prompts/date.test.ts rename to packages/core/src/prompts/date.test.ts index 93cf6055..8d6ddd3c 100644 --- a/packages/core/test/prompts/date.test.ts +++ b/packages/core/src/prompts/date.test.ts @@ -1,9 +1,8 @@ import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as DatePrompt } from '../../src/prompts/date.js'; -import { isCancel } from '../../src/utils/index.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as DatePrompt } from './date.js'; +import { isCancel } from '../utils/index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; const d = (iso: string) => { const [y, m, day] = iso.slice(0, 10).split('-').map(Number); @@ -11,33 +10,27 @@ const d = (iso: string) => { }; describe('DatePrompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', }); instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); test('initial value displays correctly', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), @@ -50,58 +43,58 @@ describe('DatePrompt', () => { test('left/right navigates between segments', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), }); instance.prompt(); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 0, positionInSegment: 0 }); - input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 1, positionInSegment: 0 }); - input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 2, positionInSegment: 0 }); - input.emit('keypress', undefined, { name: 'left' }); + mocks.input.emit('keypress', undefined, { name: 'left' }); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 1, positionInSegment: 0 }); }); test('up/down increments and decrements segment', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), }); instance.prompt(); - input.emit('keypress', undefined, { name: 'right' }); // move to month - input.emit('keypress', undefined, { name: 'up' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); // move to month + mocks.input.emit('keypress', undefined, { name: 'up' }); expect(instance.userInput).to.equal('2025/02/15'); - input.emit('keypress', undefined, { name: 'down' }); + mocks.input.emit('keypress', undefined, { name: 'down' }); expect(instance.userInput).to.equal('2025/01/15'); }); test('up/down on one segment leaves other segments blank', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', }); instance.prompt(); expect(instance.userInput).to.equal('____/__/__'); - input.emit('keypress', undefined, { name: 'up' }); // up on year (first segment) + mocks.input.emit('keypress', undefined, { name: 'up' }); // up on year (first segment) expect(instance.userInput).to.equal('0001/__/__'); - input.emit('keypress', undefined, { name: 'right' }); // move to month - input.emit('keypress', undefined, { name: 'up' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); // move to month + mocks.input.emit('keypress', undefined, { name: 'up' }); expect(instance.userInput).to.equal('0001/01/__'); }); test('with minDate/maxDate, up on blank segment starts at min', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', minDate: d('2025-03-10'), @@ -109,40 +102,40 @@ describe('DatePrompt', () => { }); instance.prompt(); expect(instance.userInput).to.equal('____/__/__'); - input.emit('keypress', undefined, { name: 'up' }); + mocks.input.emit('keypress', undefined, { name: 'up' }); expect(instance.userInput).to.equal('2025/__/__'); - input.emit('keypress', undefined, { name: 'right' }); - input.emit('keypress', undefined, { name: 'up' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'up' }); expect(instance.userInput).to.equal('2025/03/__'); - input.emit('keypress', undefined, { name: 'right' }); - input.emit('keypress', undefined, { name: 'up' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'up' }); expect(instance.userInput).to.equal('2025/03/10'); }); test('with minDate/maxDate, down on blank segment starts at max', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', minDate: d('2025-03-10'), maxDate: d('2025-11-20'), }); instance.prompt(); - input.emit('keypress', undefined, { name: 'down' }); + mocks.input.emit('keypress', undefined, { name: 'down' }); expect(instance.userInput).to.equal('2025/__/__'); - input.emit('keypress', undefined, { name: 'right' }); - input.emit('keypress', undefined, { name: 'down' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'down' }); expect(instance.userInput).to.equal('2025/11/__'); - input.emit('keypress', undefined, { name: 'right' }); - input.emit('keypress', undefined, { name: 'down' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'down' }); expect(instance.userInput).to.equal('2025/11/20'); }); test('digit-by-digit editing from left to right', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), @@ -150,56 +143,56 @@ describe('DatePrompt', () => { instance.prompt(); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 0, positionInSegment: 0 }); // Type 2,0,2,3 to change year to 2023 (right-to-left fill) - input.emit('keypress', '2', { name: undefined, sequence: '2' }); + mocks.input.emit('keypress', '2', { name: undefined, sequence: '2' }); expect(instance.userInput).to.equal('___2/01/15'); - input.emit('keypress', '0', { name: undefined, sequence: '0' }); + mocks.input.emit('keypress', '0', { name: undefined, sequence: '0' }); expect(instance.userInput).to.equal('__20/01/15'); - input.emit('keypress', '2', { name: undefined, sequence: '2' }); + mocks.input.emit('keypress', '2', { name: undefined, sequence: '2' }); expect(instance.userInput).to.equal('_202/01/15'); - input.emit('keypress', '3', { name: undefined, sequence: '3' }); + mocks.input.emit('keypress', '3', { name: undefined, sequence: '3' }); expect(instance.userInput).to.equal('2023/01/15'); }); test('backspace clears entire segment at any cursor position', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-12-21'), }); instance.prompt(); expect(instance.userInput).to.equal('2025/12/21'); - input.emit('keypress', undefined, { name: 'backspace', sequence: '\x7f' }); + mocks.input.emit('keypress', undefined, { name: 'backspace', sequence: '\x7f' }); expect(instance.userInput).to.equal('____/12/21'); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 0, positionInSegment: 0 }); }); test('backspace clears segment when cursor at first char (2___)', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', }); instance.prompt(); - input.emit('keypress', '2', { name: undefined, sequence: '2' }); + mocks.input.emit('keypress', '2', { name: undefined, sequence: '2' }); expect(instance.userInput).to.equal('___2/__/__'); - input.emit('keypress', '\x7f', { name: undefined, sequence: '\x7f' }); + mocks.input.emit('keypress', '\x7f', { name: undefined, sequence: '\x7f' }); expect(instance.userInput).to.equal('____/__/__'); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 0, positionInSegment: 0 }); }); test('digit input updates segment and jumps to next when complete', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', }); instance.prompt(); for (const c of '2025') { - input.emit('keypress', c, { name: undefined, sequence: c }); + mocks.input.emit('keypress', c, { name: undefined, sequence: c }); } expect(instance.userInput).to.equal('2025/__/__'); expect(instance.segmentCursor).to.deep.equal({ segmentIndex: 1, positionInSegment: 0 }); @@ -207,14 +200,14 @@ describe('DatePrompt', () => { test('submit returns Date for valid date', async () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-31'), }); const resultPromise = instance.prompt(); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); const result = await resultPromise; expect(result).toBeInstanceOf(Date); expect((result as Date).toISOString().slice(0, 10)).to.equal('2025-01-31'); @@ -222,28 +215,28 @@ describe('DatePrompt', () => { test('can cancel', async () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), }); const resultPromise = instance.prompt(); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const result = await resultPromise; expect(isCancel(result)).toBe(true); }); test('defaultValue used when invalid date submitted', async () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', defaultValue: d('2025-06-15'), }); const resultPromise = instance.prompt(); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); const result = await resultPromise; expect(result).toBeInstanceOf(Date); expect((result as Date).toISOString().slice(0, 10)).to.equal('2025-06-15'); @@ -251,8 +244,8 @@ describe('DatePrompt', () => { test('supports MDY format', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'MDY', initialValue: d('2025-01-15'), @@ -263,8 +256,8 @@ describe('DatePrompt', () => { test('supports DMY format', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'DMY', initialValue: d('2025-01-15'), @@ -275,44 +268,44 @@ describe('DatePrompt', () => { test('rejects invalid month via pending tens digit', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', }); instance.prompt(); // Navigate to month - input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); // Type '1' → '01' with pending tens digit (since 1 <= 1) - input.emit('keypress', '1', { name: undefined, sequence: '1' }); + mocks.input.emit('keypress', '1', { name: undefined, sequence: '1' }); expect(instance.segmentValues.month).to.equal('01'); // Type '3' → tries '13' which is > 12 → inline error - input.emit('keypress', '3', { name: undefined, sequence: '3' }); + mocks.input.emit('keypress', '3', { name: undefined, sequence: '3' }); expect(instance.inlineError).to.equal('There are only 12 months in a year'); }); test('rejects invalid day via pending tens digit', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', }); instance.prompt(); // Navigate to day - input.emit('keypress', undefined, { name: 'right' }); - input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); // Type '2' → '02' with pending (2 <= 2) - input.emit('keypress', '2', { name: undefined, sequence: '2' }); - input.emit('keypress', '0', { name: undefined, sequence: '0' }); + mocks.input.emit('keypress', '2', { name: undefined, sequence: '2' }); + mocks.input.emit('keypress', '0', { name: undefined, sequence: '0' }); expect(instance.inlineError).to.equal(''); }); describe('segmentValues and segmentCursor', () => { test('segmentValues reflects current input', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), @@ -326,14 +319,14 @@ describe('DatePrompt', () => { test('segmentCursor tracks cursor position', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), }); instance.prompt(); - input.emit('keypress', undefined, { name: 'right' }); // move to month + mocks.input.emit('keypress', undefined, { name: 'right' }); // move to month const cursor = instance.segmentCursor; expect(cursor.segmentIndex).to.equal(1); expect(cursor.positionInSegment).to.equal(0); @@ -341,14 +334,14 @@ describe('DatePrompt', () => { test('segmentValues updates on submit', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', initialValue: d('2025-01-15'), }); instance.prompt(); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); const segmentValues = instance.segmentValues; expect(segmentValues.year).to.equal('2025'); expect(segmentValues.month).to.equal('01'); @@ -359,8 +352,8 @@ describe('DatePrompt', () => { describe('formattedValue and segments', () => { test('formattedValue returns formatted string', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'MDY', initialValue: d('2025-03-15'), @@ -371,8 +364,8 @@ describe('DatePrompt', () => { test('segments exposes segment config', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'DMY', }); @@ -386,8 +379,8 @@ describe('DatePrompt', () => { test('separator defaults to / for explicit format', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', }); @@ -399,8 +392,8 @@ describe('DatePrompt', () => { describe('locale detection', () => { test('locale auto-detects format from Intl', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', locale: 'en-US', initialValue: d('2025-03-15'), @@ -414,8 +407,8 @@ describe('DatePrompt', () => { test('explicit format overrides locale', () => { const instance = new DatePrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', format: 'YMD', locale: 'en-US', // would be MDY, but format takes precedence diff --git a/packages/core/src/prompts/multi-line.test.ts b/packages/core/src/prompts/multi-line.test.ts new file mode 100644 index 00000000..375d2962 --- /dev/null +++ b/packages/core/src/prompts/multi-line.test.ts @@ -0,0 +1,348 @@ +import { styleText } from 'node:util'; +import { cursor } from 'sisteransi'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as MultiLinePrompt } from './multi-line.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; + +describe('MultiLinePrompt', () => { + let mocks: Mocks<{ input: true; output: true }>; + + beforeEach(() => { + mocks = createMocks({ input: true, output: true }); + }); + + test('renders render() result', () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + instance.prompt(); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); + }); + + test('sets default value on finalize if no value', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + defaultValue: 'bleep bloop', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('bleep bloop'); + }); + + test('keeps value on finalize', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + defaultValue: 'bleep bloop', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x'); + }); + + describe('cursor', () => { + test('can get cursor', () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + + expect(instance.cursor).to.equal(0); + }); + }); + + describe('userInputWithCursor', () => { + test('returns value on submit', () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + expect(instance.userInputWithCursor).to.equal('x'); + }); + + test('highlights cursor position', () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + instance.prompt(); + const keys = 'foo'; + for (let i = 0; i < keys.length; i++) { + mocks.input.emit('keypress', keys[i], { name: keys[i] }); + } + mocks.input.emit('keypress', undefined, { name: 'left' }); + expect(instance.userInputWithCursor).to.equal(`fo${styleText('inverse', 'o')}`); + }); + + test('shows cursor at end if beyond value', () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + instance.prompt(); + const keys = 'foo'; + for (let i = 0; i < keys.length; i++) { + mocks.input.emit('keypress', keys[i], { name: keys[i] }); + } + mocks.input.emit('keypress', undefined, { name: 'right' }); + expect(instance.userInputWithCursor).to.equal('foo█'); + }); + }); + + describe('key', () => { + test('return inserts newline', () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + expect(instance.userInput).to.equal('x\n'); + }); + + test('double return submits', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x'); + }); + + test('double return inserts when showSubmit is true', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + showSubmit: true, + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x\n\n'); + }); + + test('typing when submit selected jumps back to text', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + showSubmit: true, + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('xy'); + }); + + test('backspace deletes previous char', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'backspace' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x'); + }); + + test('delete deletes next char', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'delete' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x'); + }); + + test('delete does nothing at end', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'delete' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x'); + }); + + test('backspace does nothing at start', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'backspace' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x'); + }); + + test('left moves left until start', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('yx'); + }); + + test('right moves right until end', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'right' }); + mocks.input.emit('keypress', '', { name: 'right' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('xyz'); + }); + + test('left moves across lines', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('xz\ny'); + }); + + test('right moves across lines', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'right' }); + mocks.input.emit('keypress', '', { name: 'right' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x\nyz'); + }); + + test('up moves up a line', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'up' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('xz\ny'); + }); + + test('down moves down a line', async () => { + const instance = new MultiLinePrompt({ + input: mocks.input, + output: mocks.output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'up' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('x\nyz'); + }); + }); +}); diff --git a/packages/core/test/prompts/multi-select.test.ts b/packages/core/src/prompts/multi-select.test.ts similarity index 67% rename from packages/core/test/prompts/multi-select.test.ts rename to packages/core/src/prompts/multi-select.test.ts index 99695793..a44e01fd 100644 --- a/packages/core/test/prompts/multi-select.test.ts +++ b/packages/core/src/prompts/multi-select.test.ts @@ -1,38 +1,31 @@ import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as MultiSelectPrompt } from '../../src/prompts/multi-select.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as MultiSelectPrompt } from './multi-select.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('MultiSelectPrompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); describe('cursor', () => { test('cursor is index of selected item', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); @@ -40,14 +33,14 @@ describe('MultiSelectPrompt', () => { instance.prompt(); expect(instance.cursor).to.equal(0); - input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); expect(instance.cursor).to.equal(1); }); test('cursor loops around', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }, { value: 'baz' }], }); @@ -55,44 +48,44 @@ describe('MultiSelectPrompt', () => { instance.prompt(); expect(instance.cursor).to.equal(0); - input.emit('keypress', 'up', { name: 'up' }); + mocks.input.emit('keypress', 'up', { name: 'up' }); expect(instance.cursor).to.equal(2); - input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); expect(instance.cursor).to.equal(0); }); test('left behaves as up', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }, { value: 'baz' }], }); instance.prompt(); - input.emit('keypress', 'left', { name: 'left' }); + mocks.input.emit('keypress', 'left', { name: 'left' }); expect(instance.cursor).to.equal(2); }); test('right behaves as down', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); instance.prompt(); - input.emit('keypress', 'left', { name: 'left' }); + mocks.input.emit('keypress', 'left', { name: 'left' }); expect(instance.cursor).to.equal(1); }); test('initial values is selected', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], initialValues: ['bar'], @@ -103,54 +96,54 @@ describe('MultiSelectPrompt', () => { test('select all when press "a" key', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); instance.prompt(); - input.emit('keypress', 'down', { name: 'down' }); - input.emit('keypress', 'space', { name: 'space' }); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'space', { name: 'space' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); expect(instance.value).toEqual(['foo', 'bar']); }); test('select invert when press "i" key', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); instance.prompt(); - input.emit('keypress', 'down', { name: 'down' }); - input.emit('keypress', 'space', { name: 'space' }); - input.emit('keypress', 'i', { name: 'i' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'space', { name: 'space' }); + mocks.input.emit('keypress', 'i', { name: 'i' }); expect(instance.value).toEqual(['foo']); }); test('disabled options are skipped', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar', disabled: true }, { value: 'baz' }], }); instance.prompt(); expect(instance.cursor).to.equal(0); - input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); expect(instance.cursor).to.equal(2); - input.emit('keypress', 'up', { name: 'up' }); + mocks.input.emit('keypress', 'up', { name: 'up' }); expect(instance.cursor).to.equal(0); }); test('initial cursorAt on disabled option', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar', disabled: true }, { value: 'baz' }], cursorAt: 'bar', @@ -164,28 +157,28 @@ describe('MultiSelectPrompt', () => { describe('toggleAll', () => { test('selects all enabled options', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar', disabled: true }, { value: 'baz' }], }); instance.prompt(); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); expect(instance.value).toEqual(['foo', 'baz']); }); test('unselects all enabled options if all selected', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar', disabled: true }, { value: 'baz' }], initialValues: ['foo', 'baz'], }); instance.prompt(); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); expect(instance.value).toEqual([]); }); }); @@ -193,8 +186,8 @@ describe('MultiSelectPrompt', () => { describe('toggleInvert', () => { test('inverts selection of enabled options', () => { const instance = new MultiSelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [ { value: 'foo' }, @@ -206,7 +199,7 @@ describe('MultiSelectPrompt', () => { }); instance.prompt(); - input.emit('keypress', 'i', { name: 'i' }); + mocks.input.emit('keypress', 'i', { name: 'i' }); expect(instance.value).toEqual(['qux']); }); }); diff --git a/packages/core/test/prompts/password.test.ts b/packages/core/src/prompts/password.test.ts similarity index 57% rename from packages/core/test/prompts/password.test.ts rename to packages/core/src/prompts/password.test.ts index 3b37e9d3..ba5af6e3 100644 --- a/packages/core/test/prompts/password.test.ts +++ b/packages/core/src/prompts/password.test.ts @@ -1,39 +1,32 @@ import { styleText } from 'node:util'; import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as PasswordPrompt } from '../../src/prompts/password.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as PasswordPrompt } from './password.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('PasswordPrompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new PasswordPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); // leave the promise hanging since we don't want to submit in this test instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); describe('cursor', () => { test('can get cursor', () => { const instance = new PasswordPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); @@ -44,54 +37,54 @@ describe('PasswordPrompt', () => { describe('userInputWithCursor', () => { test('returns masked value on submit', () => { const instance = new PasswordPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); instance.prompt(); const keys = 'foo'; for (let i = 0; i < keys.length; i++) { - input.emit('keypress', keys[i], { name: keys[i] }); + mocks.input.emit('keypress', keys[i], { name: keys[i] }); } - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.userInputWithCursor).to.equal('•••'); }); test('renders marker at end', () => { const instance = new PasswordPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); expect(instance.userInputWithCursor).to.equal(`•${styleText(['inverse', 'hidden'], '_')}`); }); test('renders cursor inside value', () => { const instance = new PasswordPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', undefined, { name: 'left' }); - input.emit('keypress', undefined, { name: 'left' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', undefined, { name: 'left' }); + mocks.input.emit('keypress', undefined, { name: 'left' }); expect(instance.userInputWithCursor).to.equal(`•${styleText('inverse', '•')}•`); }); test('renders custom mask', () => { const instance = new PasswordPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', mask: 'X', }); instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); expect(instance.userInputWithCursor).to.equal(`X${styleText(['inverse', 'hidden'], '_')}`); }); }); diff --git a/packages/core/test/prompts/prompt.test.ts b/packages/core/src/prompts/prompt.test.ts similarity index 73% rename from packages/core/test/prompts/prompt.test.ts rename to packages/core/src/prompts/prompt.test.ts index bc4fa5e6..51ebd98c 100644 --- a/packages/core/test/prompts/prompt.test.ts +++ b/packages/core/src/prompts/prompt.test.ts @@ -1,68 +1,61 @@ import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as Prompt } from '../../src/prompts/prompt.js'; -import { isCancel } from '../../src/utils/index.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { default as Prompt } from './prompt.js'; +import { isCancel } from '../utils/index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('Prompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); // leave the promise hanging since we don't want to submit in this test instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); test('submits on return key', async () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); const resultPromise = instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await resultPromise; expect(result).to.equal(undefined); expect(isCancel(result)).to.equal(false); expect(instance.state).to.equal('submit'); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo', '\n', cursor.show]); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo', '\n', cursor.show]); }); test('cancels on ctrl-c', async () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); const resultPromise = instance.prompt(); - input.emit('keypress', '\x03', { name: 'c' }); + mocks.input.emit('keypress', '\x03', { name: 'c' }); const result = await resultPromise; expect(isCancel(result)).to.equal(true); expect(instance.state).to.equal('cancel'); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo', '\n', cursor.show]); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo', '\n', cursor.show]); }); test('does not write initialValue to value', () => { const eventSpy = vi.fn(); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', initialValue: 'bananas', }); @@ -75,23 +68,23 @@ describe('Prompt', () => { test('re-renders on resize', () => { const renderFn = vi.fn().mockImplementation(() => 'foo'); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: renderFn, }); instance.prompt(); expect(renderFn).toHaveBeenCalledTimes(1); - output.emit('resize'); + mocks.output.emit('resize'); expect(renderFn).toHaveBeenCalledTimes(2); }); test('state is active after first render', async () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); @@ -105,8 +98,8 @@ describe('Prompt', () => { test('emits truthy confirm on y press', () => { const eventFn = vi.fn(); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); @@ -114,7 +107,7 @@ describe('Prompt', () => { instance.prompt(); - input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); expect(eventFn).toBeCalledWith(true); }); @@ -122,8 +115,8 @@ describe('Prompt', () => { test('emits falsey confirm on n press', () => { const eventFn = vi.fn(); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); @@ -131,7 +124,7 @@ describe('Prompt', () => { instance.prompt(); - input.emit('keypress', 'n', { name: 'n' }); + mocks.input.emit('keypress', 'n', { name: 'n' }); expect(eventFn).toBeCalledWith(false); }); @@ -139,8 +132,8 @@ describe('Prompt', () => { test('emits key event for unknown chars', () => { const eventSpy = vi.fn(); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); @@ -148,7 +141,7 @@ describe('Prompt', () => { instance.prompt(); - input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); expect(eventSpy).toBeCalledWith('z', { name: 'z' }); }); @@ -157,8 +150,8 @@ describe('Prompt', () => { const keys = ['up', 'down', 'left', 'right']; const eventSpy = vi.fn(); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); @@ -167,7 +160,7 @@ describe('Prompt', () => { instance.prompt(); for (const key of keys) { - input.emit('keypress', key, { name: key }); + mocks.input.emit('keypress', key, { name: key }); expect(eventSpy).toBeCalledWith(key); } }); @@ -182,8 +175,8 @@ describe('Prompt', () => { const eventSpy = vi.fn(); const instance = new Prompt( { - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }, false @@ -194,7 +187,7 @@ describe('Prompt', () => { instance.prompt(); for (const [alias, key] of keys) { - input.emit('keypress', alias, { name: alias }); + mocks.input.emit('keypress', alias, { name: alias }); expect(eventSpy).toBeCalledWith(key); } }); @@ -203,8 +196,8 @@ describe('Prompt', () => { const abortController = new AbortController(); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', signal: abortController.signal, }); @@ -223,8 +216,8 @@ describe('Prompt', () => { abortController.abort(); const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', signal: abortController.signal, }); @@ -235,8 +228,8 @@ describe('Prompt', () => { test('accepts invalid initial value', () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', initialValue: 'invalid', validate: (value) => (value === 'valid' ? undefined : 'must be valid'), @@ -249,8 +242,8 @@ describe('Prompt', () => { test('validates value on return', () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', validate: (value) => (value === 'valid' ? undefined : 'must be valid'), }); @@ -258,7 +251,7 @@ describe('Prompt', () => { instance.value = 'invalid'; - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.state).to.equal('error'); expect(instance.error).to.equal('must be valid'); @@ -266,15 +259,15 @@ describe('Prompt', () => { test('validates value with Error object', () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', validate: (value) => (value === 'valid' ? undefined : new Error('must be valid')), }); instance.prompt(); instance.value = 'invalid'; - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.state).to.equal('error'); expect(instance.error).to.equal('must be valid'); @@ -282,15 +275,15 @@ describe('Prompt', () => { test('validates value with regex validation', () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', validate: (value) => (/^[A-Z]+$/.test(value ?? '') ? undefined : 'Invalid value'), }); instance.prompt(); instance.value = 'Invalid Value $$$'; - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.state).to.equal('error'); expect(instance.error).to.equal('Invalid value'); @@ -298,15 +291,15 @@ describe('Prompt', () => { test('accepts valid value with regex validation', () => { const instance = new Prompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', validate: (value) => (/^[A-Z]+$/.test(value ?? '') ? undefined : 'Invalid value'), }); instance.prompt(); instance.value = 'VALID'; - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.state).to.equal('submit'); expect(instance.error).to.equal(''); diff --git a/packages/core/test/prompts/select.test.ts b/packages/core/src/prompts/select.test.ts similarity index 68% rename from packages/core/test/prompts/select.test.ts rename to packages/core/src/prompts/select.test.ts index a3583061..f8883aa1 100644 --- a/packages/core/test/prompts/select.test.ts +++ b/packages/core/src/prompts/select.test.ts @@ -1,38 +1,31 @@ import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as SelectPrompt } from '../../src/prompts/select.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as SelectPrompt } from './select.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('SelectPrompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); describe('cursor', () => { test('cursor is index of selected item', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); @@ -40,14 +33,14 @@ describe('SelectPrompt', () => { instance.prompt(); expect(instance.cursor).to.equal(0); - input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); expect(instance.cursor).to.equal(1); }); test('cursor loops around', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }, { value: 'baz' }], }); @@ -55,44 +48,44 @@ describe('SelectPrompt', () => { instance.prompt(); expect(instance.cursor).to.equal(0); - input.emit('keypress', 'up', { name: 'up' }); + mocks.input.emit('keypress', 'up', { name: 'up' }); expect(instance.cursor).to.equal(2); - input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); expect(instance.cursor).to.equal(0); }); test('left behaves as up', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }, { value: 'baz' }], }); instance.prompt(); - input.emit('keypress', 'left', { name: 'left' }); + mocks.input.emit('keypress', 'left', { name: 'left' }); expect(instance.cursor).to.equal(2); }); test('right behaves as down', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], }); instance.prompt(); - input.emit('keypress', 'left', { name: 'left' }); + mocks.input.emit('keypress', 'left', { name: 'left' }); expect(instance.cursor).to.equal(1); }); test('initial value is selected', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar' }], initialValue: 'bar', @@ -103,35 +96,35 @@ describe('SelectPrompt', () => { test('cursor skips disabled options (down)', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo' }, { value: 'bar', disabled: true }, { value: 'baz' }], }); instance.prompt(); expect(instance.cursor).to.equal(0); - input.emit('keypress', 'down', { name: 'down' }); + mocks.input.emit('keypress', 'down', { name: 'down' }); expect(instance.cursor).to.equal(2); }); test('cursor skips disabled options (up)', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', initialValue: 'baz', options: [{ value: 'foo' }, { value: 'bar', disabled: true }, { value: 'baz' }], }); instance.prompt(); expect(instance.cursor).to.equal(2); - input.emit('keypress', 'up', { name: 'up' }); + mocks.input.emit('keypress', 'up', { name: 'up' }); expect(instance.cursor).to.equal(0); }); test('cursor skips initial disabled option', () => { const instance = new SelectPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', options: [{ value: 'foo', disabled: true }, { value: 'bar' }, { value: 'baz' }], }); diff --git a/packages/core/test/prompts/text.test.ts b/packages/core/src/prompts/text.test.ts similarity index 64% rename from packages/core/test/prompts/text.test.ts rename to packages/core/src/prompts/text.test.ts index 91338a9b..bfd7888d 100644 --- a/packages/core/test/prompts/text.test.ts +++ b/packages/core/src/prompts/text.test.ts @@ -1,57 +1,50 @@ import { styleText } from 'node:util'; import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as TextPrompt } from '../../src/prompts/text.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { default as TextPrompt } from './text.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('TextPrompt', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders render() result', () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); // leave the promise hanging since we don't want to submit in this test instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); + expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']); }); test('sets default value on finalize if no value', async () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', defaultValue: 'bleep bloop', }); const resultPromise = instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await resultPromise; expect(result).to.equal('bleep bloop'); }); test('keeps value on finalize', async () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', defaultValue: 'bleep bloop', }); const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await resultPromise; expect(result).to.equal('x'); }); @@ -59,8 +52,8 @@ describe('TextPrompt', () => { describe('cursor', () => { test('can get cursor', () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); @@ -71,69 +64,69 @@ describe('TextPrompt', () => { describe('userInputWithCursor', () => { test('returns value on submit', () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); expect(instance.userInputWithCursor).to.equal('x'); }); test('highlights cursor position', () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); instance.prompt(); const keys = 'foo'; for (let i = 0; i < keys.length; i++) { - input.emit('keypress', keys[i], { name: keys[i] }); + mocks.input.emit('keypress', keys[i], { name: keys[i] }); } - input.emit('keypress', undefined, { name: 'left' }); + mocks.input.emit('keypress', undefined, { name: 'left' }); expect(instance.userInputWithCursor).to.equal(`fo${styleText('inverse', 'o')}`); }); test('shows cursor at end if beyond value', () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', }); instance.prompt(); const keys = 'foo'; for (let i = 0; i < keys.length; i++) { - input.emit('keypress', keys[i], { name: keys[i] }); + mocks.input.emit('keypress', keys[i], { name: keys[i] }); } - input.emit('keypress', undefined, { name: 'right' }); + mocks.input.emit('keypress', undefined, { name: 'right' }); expect(instance.userInputWithCursor).to.equal('foo█'); }); test('does not use placeholder as value when pressing enter', async () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', placeholder: ' (hit Enter to use default)', defaultValue: 'default-value', }); const resultPromise = instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await resultPromise; expect(result).to.equal('default-value'); }); test('returns empty string when no value and no default', async () => { const instance = new TextPrompt({ - input, - output, + input: mocks.input, + output: mocks.output, render: () => 'foo', placeholder: ' (hit Enter to use default)', }); const resultPromise = instance.prompt(); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const result = await resultPromise; expect(result).to.equal(''); }); diff --git a/packages/core/test/utils/cursor.test.ts b/packages/core/src/utils/cursor.test.ts similarity index 100% rename from packages/core/test/utils/cursor.test.ts rename to packages/core/src/utils/cursor.test.ts diff --git a/packages/core/test/utils.test.ts b/packages/core/src/utils/index.test.ts similarity index 76% rename from packages/core/test/utils.test.ts rename to packages/core/src/utils/index.test.ts index 7909f4f0..7696524e 100644 --- a/packages/core/test/utils.test.ts +++ b/packages/core/src/utils/index.test.ts @@ -1,19 +1,13 @@ import type { Key } from 'node:readline'; import { cursor } from 'sisteransi'; -import { afterEach, describe, expect, test, vi } from 'vitest'; -import { block } from '../src/utils/index.js'; -import { MockReadable } from './mock-readable.js'; -import { MockWritable } from './mock-writable.js'; +import { describe, expect, test, vi } from 'vitest'; +import { block } from './index.js'; +import { createMocks } from '@bomb.sh/tools/test-utils'; describe('utils', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - describe('block', () => { test('clears output on keypress', () => { - const input = new MockReadable(); - const output = new MockWritable(); + const { input, output } = createMocks({ input: true, output: true }); const callback = block({ input, output }); const event: Key = { @@ -26,8 +20,7 @@ describe('utils', () => { }); test('clears output vertically when return pressed', () => { - const input = new MockReadable(); - const output = new MockWritable(); + const { input, output } = createMocks({ input: true, output: true }); const callback = block({ input, output }); const event: Key = { @@ -40,8 +33,7 @@ describe('utils', () => { }); test('ignores additional keypresses after dispose', () => { - const input = new MockReadable(); - const output = new MockWritable(); + const { input, output } = createMocks({ input: true, output: true }); const callback = block({ input, output }); const event: Key = { @@ -55,8 +47,7 @@ describe('utils', () => { }); test('exits on ctrl-c', () => { - const input = new MockReadable(); - const output = new MockWritable(); + const { input, output } = createMocks({ input: true, output: true }); // purposely don't keep the callback since we would exit the process block({ input, output }); const spy = vi.spyOn(process, 'exit').mockImplementation((() => { @@ -73,8 +64,7 @@ describe('utils', () => { }); test('does not clear if overwrite=false', () => { - const input = new MockReadable(); - const output = new MockWritable(); + const { input, output } = createMocks({ input: true, output: true }); const callback = block({ input, output, overwrite: false }); const event: Key = { diff --git a/packages/core/test/mock-readable.ts b/packages/core/test/mock-readable.ts deleted file mode 100644 index b08e4879..00000000 --- a/packages/core/test/mock-readable.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Readable } from 'node:stream'; - -export class MockReadable extends Readable { - protected _buffer: unknown[] | null = []; - - _read() { - if (this._buffer === null) { - this.push(null); - return; - } - - for (const val of this._buffer) { - this.push(val); - } - - this._buffer = []; - } - - pushValue(val: unknown): void { - this._buffer?.push(val); - } - - close(): void { - this._buffer = null; - } -} diff --git a/packages/core/test/mock-writable.ts b/packages/core/test/mock-writable.ts deleted file mode 100644 index 746b0a0d..00000000 --- a/packages/core/test/mock-writable.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Writable } from 'node:stream'; - -export class MockWritable extends Writable { - public buffer: string[] = []; - - _write( - chunk: any, - _encoding: BufferEncoding, - callback: (error?: Error | null | undefined) => void - ): void { - this.buffer.push(chunk.toString()); - callback(); - } -} diff --git a/packages/core/test/prompts/multi-line.test.ts b/packages/core/test/prompts/multi-line.test.ts deleted file mode 100644 index 53f86a5d..00000000 --- a/packages/core/test/prompts/multi-line.test.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { styleText } from 'node:util'; -import { cursor } from 'sisteransi'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { default as MultiLinePrompt } from '../../src/prompts/multi-line.js'; -import { MockReadable } from '../mock-readable.js'; -import { MockWritable } from '../mock-writable.js'; - -describe('MultiLinePrompt', () => { - let input: MockReadable; - let output: MockWritable; - - beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - test('renders render() result', () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - instance.prompt(); - expect(output.buffer).to.deep.equal([cursor.hide, 'foo']); - }); - - test('sets default value on finalize if no value', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - defaultValue: 'bleep bloop', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('bleep bloop'); - }); - - test('keeps value on finalize', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - defaultValue: 'bleep bloop', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x'); - }); - - describe('cursor', () => { - test('can get cursor', () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - - expect(instance.cursor).to.equal(0); - }); - }); - - describe('userInputWithCursor', () => { - test('returns value on submit', () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - expect(instance.userInputWithCursor).to.equal('x'); - }); - - test('highlights cursor position', () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - instance.prompt(); - const keys = 'foo'; - for (let i = 0; i < keys.length; i++) { - input.emit('keypress', keys[i], { name: keys[i] }); - } - input.emit('keypress', undefined, { name: 'left' }); - expect(instance.userInputWithCursor).to.equal(`fo${styleText('inverse', 'o')}`); - }); - - test('shows cursor at end if beyond value', () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - instance.prompt(); - const keys = 'foo'; - for (let i = 0; i < keys.length; i++) { - input.emit('keypress', keys[i], { name: keys[i] }); - } - input.emit('keypress', undefined, { name: 'right' }); - expect(instance.userInputWithCursor).to.equal('foo█'); - }); - }); - - describe('key', () => { - test('return inserts newline', () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - expect(instance.userInput).to.equal('x\n'); - }); - - test('double return submits', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x'); - }); - - test('double return inserts when showSubmit is true', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - showSubmit: true, - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x\n\n'); - }); - - test('typing when submit selected jumps back to text', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - showSubmit: true, - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('xy'); - }); - - test('backspace deletes previous char', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'backspace' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x'); - }); - - test('delete deletes next char', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'delete' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x'); - }); - - test('delete does nothing at end', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'delete' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x'); - }); - - test('backspace does nothing at start', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'backspace' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x'); - }); - - test('left moves left until start', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('yx'); - }); - - test('right moves right until end', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'right' }); - input.emit('keypress', '', { name: 'right' }); - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('xyz'); - }); - - test('left moves across lines', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('xz\ny'); - }); - - test('right moves across lines', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'right' }); - input.emit('keypress', '', { name: 'right' }); - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x\nyz'); - }); - - test('up moves up a line', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'up' }); - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('xz\ny'); - }); - - test('down moves down a line', async () => { - const instance = new MultiLinePrompt({ - input, - output, - render: () => 'foo', - }); - const resultPromise = instance.prompt(); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'up' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x\nyz'); - }); - }); -}); From eef2e42b73455c673e7ce162f3a329781cd9cb9b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 2 Apr 2026 19:17:50 -0400 Subject: [PATCH 2/3] test(prompts): colocate tests and migrate to test-utils --- packages/prompts/package.json | 2 +- .../{test => src}/autocomplete.test.ts | 307 ++- packages/prompts/{test => src}/box.test.ts | 161 +- .../prompts/{test => src}/confirm.test.ts | 123 +- packages/prompts/{test => src}/date.test.ts | 89 +- .../{test => src}/group-multi-select.test.ts | 225 +-- .../{test => src}/limit-options.test.ts | 24 +- packages/prompts/{test => src}/log.test.ts | 88 +- packages/prompts/src/multi-line.test.ts | 259 +++ .../{test => src}/multi-select.test.ts | 249 ++- packages/prompts/{test => src}/note.test.ts | 88 +- packages/prompts/src/password.test.ts | 172 ++ packages/prompts/{test => src}/path.test.ts | 166 +- .../{test => src}/progress-bar.test.ts | 101 +- .../prompts/{test => src}/select-key.test.ts | 127 +- packages/prompts/{test => src}/select.test.ts | 195 +- .../prompts/{test => src}/spinner.test.ts | 139 +- .../prompts/{test => src}/task-log.test.ts | 190 +- packages/prompts/{test => src}/text.test.ts | 145 +- .../__snapshots__/autocomplete.test.ts.snap | 895 --------- .../test/__snapshots__/box.test.ts.snap | 551 ------ .../test/__snapshots__/confirm.test.ts.snap | 481 ----- .../test/__snapshots__/date.test.ts.snap | 263 --- .../group-multi-select.test.ts.snap | 1205 ------------ .../test/__snapshots__/log.test.ts.snap | 283 --- .../__snapshots__/multi-line.test.ts.snap | 831 -------- .../__snapshots__/multi-select.test.ts.snap | 1561 --------------- .../test/__snapshots__/note.test.ts.snap | 385 ---- .../test/__snapshots__/password.test.ts.snap | 463 ----- .../test/__snapshots__/path.test.ts.snap | 643 ------ .../__snapshots__/progress-bar.test.ts.snap | 586 ------ .../__snapshots__/select-key.test.ts.snap | 613 ------ .../test/__snapshots__/select.test.ts.snap | 961 --------- .../test/__snapshots__/spinner.test.ts.snap | 1008 ---------- .../test/__snapshots__/task-log.test.ts.snap | 1738 ----------------- .../test/__snapshots__/text.test.ts.snap | 595 ------ packages/prompts/test/multi-line.test.ts | 272 --- packages/prompts/test/password.test.ts | 185 -- packages/prompts/test/test-utils.ts | 42 - 39 files changed, 1539 insertions(+), 14872 deletions(-) rename packages/prompts/{test => src}/autocomplete.test.ts (60%) rename packages/prompts/{test => src}/box.test.ts (58%) rename packages/prompts/{test => src}/confirm.test.ts (52%) rename packages/prompts/{test => src}/date.test.ts (62%) rename packages/prompts/{test => src}/group-multi-select.test.ts (57%) rename packages/prompts/{test => src}/limit-options.test.ts (94%) rename packages/prompts/{test => src}/log.test.ts (59%) create mode 100644 packages/prompts/src/multi-line.test.ts rename packages/prompts/{test => src}/multi-select.test.ts (56%) rename packages/prompts/{test => src}/note.test.ts (55%) create mode 100644 packages/prompts/src/password.test.ts rename packages/prompts/{test => src}/path.test.ts (52%) rename packages/prompts/{test => src}/progress-bar.test.ts (69%) rename packages/prompts/{test => src}/select-key.test.ts (63%) rename packages/prompts/{test => src}/select.test.ts (58%) rename packages/prompts/{test => src}/spinner.test.ts (67%) rename packages/prompts/{test => src}/task-log.test.ts (69%) rename packages/prompts/{test => src}/text.test.ts (51%) delete mode 100644 packages/prompts/test/__snapshots__/autocomplete.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/box.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/confirm.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/date.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/log.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/multi-line.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/multi-select.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/note.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/password.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/path.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/progress-bar.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/select-key.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/select.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/spinner.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/task-log.test.ts.snap delete mode 100644 packages/prompts/test/__snapshots__/text.test.ts.snap delete mode 100644 packages/prompts/test/multi-line.test.ts delete mode 100644 packages/prompts/test/password.test.ts delete mode 100644 packages/prompts/test/test-utils.ts diff --git a/packages/prompts/package.json b/packages/prompts/package.json index fa9dfcf7..2a841b19 100644 --- a/packages/prompts/package.json +++ b/packages/prompts/package.json @@ -48,7 +48,7 @@ ], "packageManager": "pnpm@9.14.2", "scripts": { - "build": "bsh build", + "build": "bsh build \"src/**/*.ts\" \"!src/**/*.test.ts\"", "prepack": "pnpm build", "test": "bsh test", "lint": "bsh lint" diff --git a/packages/prompts/test/autocomplete.test.ts b/packages/prompts/src/autocomplete.test.ts similarity index 60% rename from packages/prompts/test/autocomplete.test.ts rename to packages/prompts/src/autocomplete.test.ts index fe55444c..2424da37 100644 --- a/packages/prompts/test/autocomplete.test.ts +++ b/packages/prompts/src/autocomplete.test.ts @@ -1,11 +1,10 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { autocomplete, autocompleteMultiselect } from '../src/autocomplete.js'; -import { isCancel, updateSettings } from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { autocomplete, autocompleteMultiselect } from './autocomplete.js'; +import { isCancel, updateSettings } from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('autocomplete', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; const testOptions = [ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, @@ -15,12 +14,10 @@ describe('autocomplete', () => { ]; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); + mocks = createMocks({ input: true, output: true }); }); afterEach(() => { - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); @@ -28,13 +25,13 @@ describe('autocomplete', () => { const result = autocomplete({ message: 'Select a fruit', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('limits displayed options when maxItems is set', async () => { @@ -47,80 +44,80 @@ describe('autocomplete', () => { message: 'Select an option', options, maxItems: 6, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('shows no matches message when search has no results', async () => { const result = autocomplete({ message: 'Select a fruit', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); // Type something that won't match - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('shows hint when option has hint and is focused', async () => { const result = autocomplete({ message: 'Select a fruit', options: [...testOptions, { value: 'kiwi', label: 'Kiwi', hint: 'New Zealand' }], - input, - output, + input: mocks.input, + output: mocks.output, }); // Navigate to the option with hint - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('shows selected value in submit state', async () => { const result = autocomplete({ message: 'Select a fruit', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); // Select an option and submit - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('banana'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('shows strikethrough in cancel state', async () => { const result = autocomplete({ message: 'Select a fruit', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); // Cancel with Ctrl+C - input.emit('keypress', '\x03', { name: 'c', ctrl: true }); + mocks.input.emit('keypress', '\x03', { name: 'c', ctrl: true }); const value = await result; expect(typeof value === 'symbol').toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders placeholder if set', async () => { @@ -128,13 +125,13 @@ describe('autocomplete', () => { message: 'Select a fruit', placeholder: 'Type to search...', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('apple'); }); @@ -143,12 +140,12 @@ describe('autocomplete', () => { message: 'Select a fruit', placeholder: 'apple', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('apple'); }); @@ -158,12 +155,12 @@ describe('autocomplete', () => { message: 'Select a fruit', placeholder: 'Type to search...', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; // Tab did not fill input with placeholder (no option matches), so Enter submits first option expect(value).toBe('apple'); @@ -174,15 +171,15 @@ describe('autocomplete', () => { message: 'Select a fruit', options: testOptions, initialValue: 'cherry', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('cherry'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can be aborted by a signal', async () => { @@ -190,15 +187,15 @@ describe('autocomplete', () => { const result = autocomplete({ message: 'foo', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, signal: controller.signal, }); controller.abort(); const value = await result; expect(isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('autocompleteMultiselect respects withGuide: false', async () => { @@ -206,18 +203,18 @@ describe('autocomplete', () => { message: 'Select fruits', options: testOptions, withGuide: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['banana']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('autocompleteMultiselect respects global withGuide: false', async () => { @@ -226,22 +223,22 @@ describe('autocomplete', () => { const result = autocompleteMultiselect({ message: 'Select fruits', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['banana']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders bottom ellipsis when items do not fit', async () => { - output.rows = 5; + mocks.output.rows = 5; const options = [ { @@ -258,17 +255,17 @@ describe('autocomplete', () => { message: 'Select an option', options, maxItems: 5, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders top ellipsis when scrolled down and its do not fit', async () => { - output.rows = 5; + mocks.output.rows = 5; const options = [ { @@ -288,13 +285,13 @@ describe('autocomplete', () => { options, initialValue: 'option2', maxItems: 5, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('placeholder is shown if set', async () => { @@ -302,14 +299,14 @@ describe('autocomplete', () => { message: 'Select a fruit', placeholder: 'Type to search...', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'g', { name: 'g' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'g', { name: 'g' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('grape'); }); @@ -318,18 +315,18 @@ describe('autocomplete', () => { const result = autocomplete({ message: 'Select a fruit', options: optionsWithDisabled, - input, - output, + input: mocks.input, + output: mocks.output, }); for (let i = 0; i < 5; i++) { - input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); } - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('apple'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('cannot select disabled options when only one left', async () => { @@ -337,22 +334,21 @@ describe('autocomplete', () => { const result = autocomplete({ message: 'Select a fruit', options: optionsWithDisabled, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'k', { name: 'k' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'k', { name: 'k' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(undefined); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('autocompleteMultiselect', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; const testOptions = [ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, @@ -362,12 +358,7 @@ describe('autocompleteMultiselect', () => { ]; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('renders error when empty selection & required is true', async () => { @@ -375,15 +366,15 @@ describe('autocompleteMultiselect', () => { message: 'Select a fruit', options: testOptions, required: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can be aborted by a signal', async () => { @@ -391,42 +382,42 @@ describe('autocompleteMultiselect', () => { const result = autocompleteMultiselect({ message: 'foo', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, signal: controller.signal, }); controller.abort(); const value = await result; expect(isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can use navigation keys to select options', async () => { const result = autocompleteMultiselect({ message: 'Select fruits', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['banana', 'cherry']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('supports custom filter function', async () => { const result = autocompleteMultiselect({ message: 'Select fruits', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, // Custom filter that only matches exact prefix filter: (search, option) => { const label = option.label ?? String(option.value ?? ''); @@ -435,13 +426,13 @@ describe('autocompleteMultiselect', () => { }); // Type 'a' - should match 'Apple' only (not 'Banana' which contains 'a') - input.emit('keypress', 'a', { name: 'a' }); - input.emit('keypress', '', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', '', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['apple']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('displays disabled options correctly', async () => { @@ -449,19 +440,19 @@ describe('autocompleteMultiselect', () => { const result = autocompleteMultiselect({ message: 'Select a fruit', options: optionsWithDisabled, - input, - output, + input: mocks.input, + output: mocks.output, }); for (let i = 0; i < testOptions.length; i++) { - input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); } - input.emit('keypress', '', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['apple']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('cannot select disabled options when only one left', async () => { @@ -469,17 +460,17 @@ describe('autocompleteMultiselect', () => { const result = autocompleteMultiselect({ message: 'Select a fruit', options: optionsWithDisabled, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'k', { name: 'k' }); - input.emit('keypress', '', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'k', { name: 'k' }); + mocks.input.emit('keypress', '', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual([]); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('Tab with placeholder fills input; Enter submits current selection', async () => { @@ -487,20 +478,19 @@ describe('autocompleteMultiselect', () => { message: 'Select fruits', placeholder: 'apple', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual([]); }); }); describe('autocomplete with custom filter', () => { - let input: MockReadable; - let output: MockWritable; + let mocks: Mocks<{ input: true; output: true }>; const testOptions = [ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, @@ -508,20 +498,15 @@ describe('autocomplete with custom filter', () => { ]; beforeEach(() => { - input = new MockReadable(); - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true }); }); test('uses custom filter function when provided', async () => { const result = autocomplete({ message: 'Select a fruit', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, // Custom filter that only matches exact prefix filter: (search, option) => { const label = option.label ?? String(option.value ?? ''); @@ -530,29 +515,29 @@ describe('autocomplete with custom filter', () => { }); // Type 'a' - should match 'Apple' only (not 'Banana' which contains 'a') - input.emit('keypress', 'a', { name: 'a' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('apple'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('falls back to default filter when not provided', async () => { const result = autocomplete({ message: 'Select a fruit', options: testOptions, - input, - output, + input: mocks.input, + output: mocks.output, }); // Type 'a' - default filter should match both 'Apple' and 'Banana' - input.emit('keypress', 'a', { name: 'a' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; // First match should be selected expect(value).toBe('apple'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/box.test.ts b/packages/prompts/src/box.test.ts similarity index 58% rename from packages/prompts/test/box.test.ts rename to packages/prompts/src/box.test.ts index 54378210..7fb043e4 100644 --- a/packages/prompts/test/box.test.ts +++ b/packages/prompts/src/box.test.ts @@ -1,273 +1,260 @@ import { styleText } from 'node:util'; import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); test('renders message', () => { prompts.box('message', undefined, { - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message with title', () => { prompts.box('message', 'some title', { - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders as wide as longest line with width: auto', () => { prompts.box('short\nsomewhat questionably long line', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders as specified width', () => { prompts.box('short\nsomewhat questionably long line', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, width: 0.5, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('wraps content to fit within specified width', () => { prompts.box('foo bar'.repeat(20), 'title', { - input, - output, + input: mocks.input, + output: mocks.output, width: 0.5, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders specified titlePadding', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, titlePadding: 6, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders specified contentPadding', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, contentPadding: 6, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders without guide when withGuide is false', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, withGuide: false, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders without guide when global withGuide is false', () => { updateSettings({ withGuide: false }); prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders truncated long titles', () => { prompts.box('message', 'foo'.repeat(20), { - input, - output, + input: mocks.input, + output: mocks.output, width: 0.2, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('cannot have width larger than 100%', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, width: 1.1, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders rounded corners when rounded is true', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, rounded: true, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders auto width with content longer than title', () => { prompts.box('message'.repeat(4), 'title', { - input, - output, + input: mocks.input, + output: mocks.output, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders auto width with title longer than content', () => { prompts.box('message', 'title'.repeat(4), { - input, - output, + input: mocks.input, + output: mocks.output, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders left aligned title', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, titleAlign: 'left', width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders right aligned title', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, titleAlign: 'right', width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders center aligned title', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, titleAlign: 'center', width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders left aligned content', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, contentAlign: 'left', width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders right aligned content', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, contentAlign: 'right', width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders center aligned content', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, contentAlign: 'center', width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders with formatBorder formatting', () => { prompts.box('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, width: 'auto', formatBorder: (text: string) => styleText('red', text), }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders wide characters with auto width', () => { const messages = ['이게 첫 번째 줄이에요', 'これは次の行です']; prompts.box(messages.join('\n'), '这是标题', { - input, - output, + input: mocks.input, + output: mocks.output, width: 'auto', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders wide characters with specified width', () => { const messages = ['이게 첫 번째 줄이에요', 'これは次の行です']; prompts.box(messages.join('\n'), '这是标题', { - input, - output, + input: mocks.input, + output: mocks.output, width: 0.2, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/confirm.test.ts b/packages/prompts/src/confirm.test.ts similarity index 52% rename from packages/prompts/test/confirm.test.ts rename to packages/prompts/src/confirm.test.ts index f79d22bb..28dcd5c5 100644 --- a/packages/prompts/test/confirm.test.ts +++ b/packages/prompts/src/confirm.test.ts @@ -1,187 +1,174 @@ import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('confirm (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); test('renders message with choices', async () => { const result = prompts.confirm({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders custom active choice', async () => { const result = prompts.confirm({ message: 'foo', active: 'bleep', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders custom inactive choice', async () => { const result = prompts.confirm({ message: 'foo', inactive: 'bleep', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders options in vertical alignment', async () => { const result = prompts.confirm({ message: 'foo', vertical: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('right arrow moves to next choice', async () => { const result = prompts.confirm({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'right', { name: 'right' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'right', { name: 'right' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(false); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('left arrow moves to previous choice', async () => { const result = prompts.confirm({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'right', { name: 'right' }); - input.emit('keypress', 'left', { name: 'left' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'right', { name: 'right' }); + mocks.input.emit('keypress', 'left', { name: 'left' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can cancel', async () => { const result = prompts.confirm({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can set initialValue', async () => { const result = prompts.confirm({ message: 'foo', initialValue: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(false); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can be aborted by a signal', async () => { const controller = new AbortController(); const result = prompts.confirm({ message: 'yes?', - input, - output, + input: mocks.input, + output: mocks.output, signal: controller.signal, }); controller.abort(); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', async () => { const result = prompts.confirm({ message: 'foo', withGuide: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('global withGuide: false removes guide', async () => { @@ -189,28 +176,28 @@ describe.each(['true', 'false'])('confirm (isCI = %s)', (isCI) => { const result = prompts.confirm({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders multi-line messages correctly', async () => { const result = prompts.confirm({ message: 'foo\nbar\nbaz', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/date.test.ts b/packages/prompts/src/date.test.ts similarity index 62% rename from packages/prompts/test/date.test.ts rename to packages/prompts/src/date.test.ts index 59490052..bd7e9cbc 100644 --- a/packages/prompts/test/date.test.ts +++ b/packages/prompts/src/date.test.ts @@ -1,7 +1,7 @@ import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; const d = (iso: string) => { const [y, m, day] = iso.slice(0, 10).split('-').map(Number); @@ -9,26 +9,13 @@ const d = (iso: string) => { }; describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); @@ -37,15 +24,15 @@ describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { message: 'Pick a date', locale: 'en-US', initialValue: d('2025-01-15'), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders initial value', async () => { @@ -53,33 +40,33 @@ describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { message: 'Pick a date', locale: 'en-US', initialValue: d('2025-01-15'), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); const value = await result; expect(value).toBeInstanceOf(Date); expect((value as Date).toISOString().slice(0, 10)).toBe('2025-01-15'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can cancel', async () => { const result = prompts.date({ message: 'Pick a date', locale: 'en-US', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders submitted value', async () => { @@ -87,17 +74,17 @@ describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { message: 'Pick a date', locale: 'en-US', initialValue: d('2025-06-15'), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); const value = await result; expect(value).toBeInstanceOf(Date); expect((value as Date).toISOString().slice(0, 10)).toBe('2025-06-15'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('defaultValue used when empty submit', async () => { @@ -105,17 +92,17 @@ describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { message: 'Pick a date', locale: 'en-US', defaultValue: d('2025-12-25'), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); const value = await result; expect(value).toBeInstanceOf(Date); expect((value as Date).toISOString().slice(0, 10)).toBe('2025-12-25'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', async () => { @@ -124,15 +111,15 @@ describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { locale: 'en-US', withGuide: false, initialValue: d('2025-01-15'), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('supports MDY format', async () => { @@ -140,17 +127,17 @@ describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { message: 'Pick a date', format: 'MDY', initialValue: d('2025-01-15'), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); const value = await result; expect(value).toBeInstanceOf(Date); expect((value as Date).toISOString().slice(0, 10)).toBe('2025-01-15'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('minDate shows error when date before min and submit', async () => { @@ -159,19 +146,19 @@ describe.each(['true', 'false'])('date (isCI = %s)', (isCI) => { locale: 'en-US', initialValue: d('2025-01-10'), minDate: d('2025-01-15'), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', undefined, { name: 'return' }); + mocks.input.emit('keypress', undefined, { name: 'return' }); await new Promise((r) => setImmediate(r)); - const hasError = output.buffer.some( + const hasError = mocks.output.buffer.some( (s) => typeof s === 'string' && s.includes('Date must be on or after') ); expect(hasError).toBe(true); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); }); diff --git a/packages/prompts/test/group-multi-select.test.ts b/packages/prompts/src/group-multi-select.test.ts similarity index 57% rename from packages/prompts/test/group-multi-select.test.ts rename to packages/prompts/src/group-multi-select.test.ts index 7d24dc18..352778b5 100644 --- a/packages/prompts/test/group-multi-select.test.ts +++ b/packages/prompts/src/group-multi-select.test.ts @@ -1,36 +1,23 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); prompts.updateSettings({ withGuide: true }); }); test('renders message with options', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], group2: [{ value: 'group2value0' }], @@ -38,149 +25,149 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { }); // Select the first non-group option - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // submit - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can select multiple options', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }, { value: 'group1value2' }], }, }); // Select the first non-group option - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // Select the second non-group option - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // submit - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0', 'group1value1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can select a group', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, }); // Select the group as a whole - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'space' }); // submit - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0', 'group1value1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can select a group by selecting all members', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, }); // Select the first group option - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // Select the second group option - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // submit - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0', 'group1value1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can deselect an option', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, }); // Select the first group option - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // Select the second group option - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // Deselect it - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'space' }); // submit - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders error when nothing selected', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, }); // try submit - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); // now select something and submit - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); describe('selectableGroups = false', () => { test('cannot select groups', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, @@ -188,20 +175,20 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { }); // first selectable item should be group's child - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('selecting all members of group does not select group', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, @@ -209,76 +196,76 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { }); // first selectable item should be group's child - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'space' }); // select second item - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); // submit - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0', 'group1value1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); test('can submit empty selection when require = false', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, required: false, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual([]); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('cursorAt sets initial selection', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, cursorAt: 'group1value1', }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('initial values can be set', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, initialValues: ['group1value1'], }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('values can be non-primitive', async () => { @@ -286,8 +273,8 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { const value1 = Symbol(); const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [ { value: value0, label: 'value0' }, @@ -296,22 +283,22 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { }, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual([value0]); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); describe('groupSpacing', () => { test('renders spaced groups', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }], group2: [{ value: 'group2value0' }], @@ -319,20 +306,20 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { groupSpacing: 2, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('negative spacing is ignored', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }], group2: [{ value: 'group2value0' }], @@ -340,13 +327,13 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { groupSpacing: -2, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); @@ -358,36 +345,36 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { group1: [{ value: 'group1value0' }], group2: [{ value: 'group2value0' }], }, - input, - output, + input: mocks.input, + output: mocks.output, signal: controller.signal, }); controller.abort(); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', async () => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, withGuide: false, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('global withGuide: false removes guide', async () => { @@ -395,20 +382,20 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => { const result = prompts.groupMultiselect({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, options: { group1: [{ value: 'group1value0' }, { value: 'group1value1' }], }, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['group1value0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/limit-options.test.ts b/packages/prompts/src/limit-options.test.ts similarity index 94% rename from packages/prompts/test/limit-options.test.ts rename to packages/prompts/src/limit-options.test.ts index 91fd8268..9524c40b 100644 --- a/packages/prompts/test/limit-options.test.ts +++ b/packages/prompts/src/limit-options.test.ts @@ -1,16 +1,16 @@ import { styleText } from 'node:util'; import { beforeEach, describe, expect, test } from 'vitest'; -import { type LimitOptionsParams, limitOptions } from '../src/index.js'; -import { MockWritable } from './test-utils.js'; +import { type LimitOptionsParams, limitOptions } from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe('limitOptions', () => { - let output: MockWritable; + let mocks: Mocks<{ output: true }>; let options: LimitOptionsParams<{ value: string }>; beforeEach(() => { - output = new MockWritable(); + mocks = createMocks({ output: true }); options = { - output, + output: mocks.output, options: [], maxItems: undefined, cursor: 0, @@ -55,7 +55,7 @@ describe('limitOptions', () => { { value: 'Item 9' }, { value: 'Item 10' }, ]; - output.rows = 20; + mocks.output.rows = 20; options.maxItems = 5; options.cursor = 6; const result = limitOptions(options); @@ -106,7 +106,7 @@ describe('limitOptions', () => { { value: 'Item 9' }, { value: 'Item 10' }, ]; - output.rows = 7; + mocks.output.rows = 7; options.maxItems = 10; const result = limitOptions(options); expect(result).toEqual(['Item 1', 'Item 2', styleText('dim', '...')]); @@ -129,7 +129,7 @@ describe('limitOptions', () => { { value: 'Item 9' }, { value: 'Item 10' }, ]; - output.rows = 14; + mocks.output.rows = 14; options.maxItems = 10; const result = limitOptions(options); expect(result).toEqual([ @@ -165,7 +165,7 @@ describe('limitOptions', () => { { value: 'Item 9' }, { value: 'Item 10' }, ]; - output.rows = 14; + mocks.output.rows = 14; options.maxItems = 10; options.cursor = 7; const result = limitOptions(options); @@ -202,7 +202,7 @@ describe('limitOptions', () => { { value: 'Item 9' }, { value: 'Item 10' }, ]; - output.rows = 14; + mocks.output.rows = 14; options.maxItems = 10; options.cursor = 9; const result = limitOptions(options); @@ -261,7 +261,7 @@ describe('limitOptions', () => { { value: 'Item 9' }, { value: 'Item 10' }, ]; - output.rows = 12; + mocks.output.rows = 12; options.rowPadding = 6; // Available rows for options = 12 - 6 = 6 const result = limitOptions(options); @@ -288,7 +288,7 @@ describe('limitOptions', () => { { value: 'Item 9' }, { value: 'Item 10' }, ]; - output.rows = 12; + mocks.output.rows = 12; // Simulate a multiline message that takes 6 lines options.rowPadding = 6; // Move cursor to middle of list diff --git a/packages/prompts/test/log.test.ts b/packages/prompts/src/log.test.ts similarity index 59% rename from packages/prompts/test/log.test.ts rename to packages/prompts/src/log.test.ts index 5bf01e5b..c93965fc 100644 --- a/packages/prompts/test/log.test.ts +++ b/packages/prompts/src/log.test.ts @@ -1,173 +1,159 @@ import { styleText } from 'node:util'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockWritable } from './test-utils.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('log (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ output: true }>; beforeEach(() => { - output = new MockWritable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ output: true, env: { CI: isCI } }); }); describe('message', () => { test('renders message', () => { prompts.log.message('message', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders multiline message', () => { prompts.log.message('line 1\nline 2\nline 3', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message from array', () => { prompts.log.message(['line 1', 'line 2', 'line 3'], { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message with custom symbols and spacing', () => { prompts.log.message('custom\nsymbols', { symbol: styleText('red', '>>'), secondarySymbol: styleText('yellow', '--'), - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message with guide disabled', () => { prompts.log.message('standalone message', { withGuide: false, - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders multiline message with guide disabled', () => { prompts.log.message('line 1\nline 2\nline 3', { withGuide: false, - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message with custom spacing', () => { prompts.log.message('spaced message', { spacing: 3, - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders empty message correctly', () => { prompts.log.message('', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders empty message with guide disabled', () => { prompts.log.message('', { withGuide: false, - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders empty lines correctly', () => { prompts.log.message('foo\n\nbar', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders empty lines with guide disabled', () => { prompts.log.message('foo\n\nbar', { withGuide: false, - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('info', () => { test('renders info message', () => { prompts.log.info('info message', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('success', () => { test('renders success message', () => { prompts.log.success('success message', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('step', () => { test('renders step message', () => { prompts.log.step('step message', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('warn', () => { test('renders warn message', () => { prompts.log.warn('warn message', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('error', () => { test('renders error message', () => { prompts.log.error('error message', { - output, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); }); diff --git a/packages/prompts/src/multi-line.test.ts b/packages/prompts/src/multi-line.test.ts new file mode 100644 index 00000000..8a1b0127 --- /dev/null +++ b/packages/prompts/src/multi-line.test.ts @@ -0,0 +1,259 @@ +import { updateSettings } from '@clack/core'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; + +describe.each(['true', 'false'])('multiline (isCI = %s)', (isCI) => { + let mocks: Mocks<{ input: true; output: true }>; + + beforeEach(() => { + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); + }); + + afterEach(() => { + updateSettings({ withGuide: true }); + }); + + test('renders message', async () => { + const result = prompts.multiline({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders placeholder if set', async () => { + const result = prompts.multiline({ + message: 'foo', + placeholder: 'bar', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + expect(value).toBe(''); + }); + + test('can cancel', async () => { + const result = prompts.multiline({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'escape', { name: 'escape' }); + + const value = await result; + + expect(prompts.isCancel(value)).toBe(true); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders cancelled value if one set', async () => { + const result = prompts.multiline({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'escape' }); + + const value = await result; + + expect(prompts.isCancel(value)).toBe(true); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders submitted value', async () => { + const result = prompts.multiline({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('xy'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('defaultValue sets the value but does not render', async () => { + const result = prompts.multiline({ + message: 'foo', + defaultValue: 'bar', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('bar'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('validation errors render and clear', async () => { + const result = prompts.multiline({ + message: 'foo', + validate: (val) => (val !== 'xy' ? 'should be xy' : undefined), + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('xy'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('validation errors render and clear (using Error)', async () => { + const result = prompts.multiline({ + message: 'foo', + validate: (val) => (val !== 'xy' ? new Error('should be xy') : undefined), + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('xy'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('placeholder is not used as value when pressing enter', async () => { + const result = prompts.multiline({ + message: 'foo', + placeholder: ' (submit to use default)', + defaultValue: 'default-value', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('default-value'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('empty string when no value and no default', async () => { + const result = prompts.multiline({ + message: 'foo', + placeholder: ' (submit to use default)', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe(''); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('can be aborted by a signal', async () => { + const controller = new AbortController(); + const result = prompts.multiline({ + message: 'foo', + input: mocks.input, + output: mocks.output, + signal: controller.signal, + }); + + controller.abort(); + const value = await result; + expect(prompts.isCancel(value)).toBe(true); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('withGuide: false removes guide', async () => { + const result = prompts.multiline({ + message: 'foo', + withGuide: false, + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('global withGuide: false removes guide', async () => { + updateSettings({ withGuide: false }); + + const result = prompts.multiline({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders submit button', async () => { + const result = prompts.multiline({ + message: 'foo', + input: mocks.input, + output: mocks.output, + showSubmit: true, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '\t', { name: 'tab' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('xy'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); +}); diff --git a/packages/prompts/test/multi-select.test.ts b/packages/prompts/src/multi-select.test.ts similarity index 56% rename from packages/prompts/test/multi-select.test.ts rename to packages/prompts/src/multi-select.test.ts index 3f79555b..20b41916 100644 --- a/packages/prompts/test/multi-select.test.ts +++ b/packages/prompts/src/multi-select.test.ts @@ -1,28 +1,15 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); prompts.updateSettings({ withGuide: true }); }); @@ -30,73 +17,73 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { const result = prompts.multiselect({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders multiple selected options', async () => { const result = prompts.multiselect({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }, { value: 'opt2' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0', 'opt1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can cancel', async () => { const result = prompts.multiselect({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders validation errors', async () => { const result = prompts.multiselect({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); // try submit with nothing selected - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); // select and submit - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can submit without selection when required = false', async () => { @@ -104,16 +91,16 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], required: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual([]); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can set cursorAt to preselect an option', async () => { @@ -121,17 +108,17 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], cursorAt: 'opt1', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can set initial values', async () => { @@ -139,16 +126,16 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], initialValues: ['opt1'], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('maxItems renders a sliding window', async () => { @@ -158,20 +145,20 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { value: `opt${k}`, })), maxItems: 6, - input, - output, + input: mocks.input, + output: mocks.output, }); for (let i = 0; i < 6; i++) { - input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); } - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt6']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('sliding window loops upwards', async () => { @@ -181,18 +168,18 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { value: `opt${k}`, })), maxItems: 6, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'up' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'up' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt11']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('sliding window loops downwards', async () => { @@ -202,20 +189,20 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { value: `opt${k}`, })), maxItems: 6, - input, - output, + input: mocks.input, + output: mocks.output, }); for (let i = 0; i < 12; i++) { - input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); } - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can set custom labels', async () => { @@ -225,17 +212,17 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { { value: 'opt0', label: 'Option 0' }, { value: 'opt1', label: 'Option 1' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can render option hints', async () => { @@ -245,17 +232,17 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { { value: 'opt0', hint: 'Hint 0' }, { value: 'opt1', hint: 'Hint 1' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('shows hints for all selected options', async () => { @@ -267,38 +254,38 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { { value: 'opt2', hint: 'Hint 2' }, ], initialValues: ['opt0', 'opt1'], - input, - output, + input: mocks.input, + output: mocks.output, }); // Check that both selected options show their hints - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0', 'opt1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders multiple cancelled values', async () => { const result = prompts.multiselect({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }, { value: 'opt2' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'escape' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can be aborted by a signal', async () => { @@ -306,15 +293,15 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { const result = prompts.multiselect({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, signal: controller.signal, }); controller.abort(); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders disabled options', async () => { @@ -325,40 +312,40 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { { value: 'opt1' }, { value: 'opt2', disabled: true, hint: 'Hint 2' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt1']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('wraps long messages', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.multiselect({ message: 'foo '.repeat(20).trim(), options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('wraps cancelled state with long options', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.multiselect({ message: 'foo', @@ -366,21 +353,21 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { { value: 'opt0', label: 'Option 0 '.repeat(10).trim() }, { value: 'opt1', label: 'Option 1 '.repeat(10).trim() }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('wraps success state with long options', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.multiselect({ message: 'foo', @@ -388,17 +375,17 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { { value: 'opt0', label: 'Option 0 '.repeat(10).trim() }, { value: 'opt1', label: 'Option 1 '.repeat(10).trim() }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', async () => { @@ -406,17 +393,17 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], withGuide: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('global withGuide: false removes guide', async () => { @@ -425,16 +412,16 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => { const result = prompts.multiselect({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'space' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual(['opt0']); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/note.test.ts b/packages/prompts/src/note.test.ts similarity index 55% rename from packages/prompts/test/note.test.ts rename to packages/prompts/src/note.test.ts index 2b037c83..026c58ef 100644 --- a/packages/prompts/test/note.test.ts +++ b/packages/prompts/src/note.test.ts @@ -1,122 +1,106 @@ import { styleText } from 'node:util'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('note (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); test('renders message with title', () => { prompts.note('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders as wide as longest line', () => { prompts.note('short\nsomewhat questionably long line', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('formatter which adds length works', () => { prompts.note('line 0\nline 1\nline 2', 'title', { format: (line) => `* ${line} *`, - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('formatter which adds colors works', () => { prompts.note('line 0\nline 1\nline 2', 'title', { format: (line) => styleText('red', line), - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test("don't overflow", () => { const message = `${'test string '.repeat(32)}\n`.repeat(4).trim(); - output.columns = 75; + mocks.output.columns = 75; prompts.note(message, 'title', { - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test("don't overflow with formatter", () => { const message = `${'test string '.repeat(32)}\n`.repeat(4).trim(); - output.columns = 75; + mocks.output.columns = 75; prompts.note(message, 'title', { format: (line) => styleText('red', `* ${styleText('cyan', line)} *`), - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('handle wide characters', () => { const messages = ['이게 첫 번째 줄이에요', 'これは次の行です']; - output.columns = 10; + mocks.output.columns = 10; prompts.note(messages.join('\n'), '这是标题', { - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('handle wide characters with formatter', () => { const messages = ['이게 첫 번째 줄이에요', 'これは次の行です']; - output.columns = 10; + mocks.output.columns = 10; prompts.note(messages.join('\n'), '这是标题', { format: (line) => styleText('red', `* ${styleText('cyan', line)} *`), - input, - output, + input: mocks.input, + output: mocks.output, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('without guide', () => { prompts.note('message', 'title', { - input, - output, + input: mocks.input, + output: mocks.output, withGuide: false, }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/src/password.test.ts b/packages/prompts/src/password.test.ts new file mode 100644 index 00000000..9aba10a2 --- /dev/null +++ b/packages/prompts/src/password.test.ts @@ -0,0 +1,172 @@ +import { updateSettings } from '@clack/core'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; + +describe.each(['true', 'false'])('password (isCI = %s)', (isCI) => { + let mocks: Mocks<{ input: true; output: true }>; + + beforeEach(() => { + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); + }); + + afterEach(() => { + updateSettings({ withGuide: true }); + }); + + test('renders message', async () => { + const result = prompts.password({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders masked value', async () => { + const result = prompts.password({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('xy'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders custom mask', async () => { + const result = prompts.password({ + message: 'foo', + mask: '*', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders and clears validation errors', async () => { + const result = prompts.password({ + message: 'foo', + validate: (value) => { + if (!value || value.length < 2) { + return 'Password must be at least 2 characters'; + } + + return undefined; + }, + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('xy'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('renders cancelled value', async () => { + const result = prompts.password({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'escape' }); + + const value = await result; + + expect(prompts.isCancel(value)).toBe(true); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('can be aborted by a signal', async () => { + const controller = new AbortController(); + const result = prompts.password({ + message: 'foo', + input: mocks.input, + output: mocks.output, + signal: controller.signal, + }); + + controller.abort(); + const value = await result; + expect(prompts.isCancel(value)).toBe(true); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('clears input on error when clearOnError is true', async () => { + const result = prompts.password({ + message: 'foo', + input: mocks.input, + output: mocks.output, + validate: (v) => (v === 'yz' ? undefined : 'Error'), + clearOnError: true, + }); + + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', 'z', { name: 'z' }); + mocks.input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('yz'); + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('withGuide: false removes guide', async () => { + const result = prompts.password({ + message: 'foo', + withGuide: false, + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + }); + + test('global withGuide: false removes guide', async () => { + updateSettings({ withGuide: false }); + + const result = prompts.password({ + message: 'foo', + input: mocks.input, + output: mocks.output, + }); + + mocks.input.emit('keypress', '', { name: 'return' }); + + await result; + + expect(mocks.output.buffer).toMatchSnapshot(); + }); +}); diff --git a/packages/prompts/test/path.test.ts b/packages/prompts/src/path.test.ts similarity index 52% rename from packages/prompts/test/path.test.ts rename to packages/prompts/src/path.test.ts index 5c1fe1f9..3ce94d26 100644 --- a/packages/prompts/test/path.test.ts +++ b/packages/prompts/src/path.test.ts @@ -1,27 +1,15 @@ import { vol } from 'memfs'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; vi.mock('node:fs'); describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); vol.reset(); vol.fromJSON( { @@ -37,23 +25,19 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { ); }); - afterEach(() => { - vi.restoreAllMocks(); - }); - test('renders message', async () => { const result = prompts.path({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, root: '/tmp/', }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('/tmp/bar'); }); @@ -61,72 +45,72 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { const result = prompts.path({ message: 'foo', root: '/tmp/', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders cancelled value if one set', async () => { const result = prompts.path({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, root: '/tmp/', }); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'escape' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders submitted value', async () => { const result = prompts.path({ message: 'foo', root: '/tmp/', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'b', { name: 'b' }); - input.emit('keypress', 'a', { name: 'a' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'b', { name: 'b' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('/tmp/bar'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('cannot submit unknown value', async () => { const result = prompts.path({ message: 'foo', root: '/tmp/', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '_', { name: '_' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'h', ctrl: true }); - input.emit('keypress', 'b', { name: 'b' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '_', { name: '_' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'h', ctrl: true }); + mocks.input.emit('keypress', 'b', { name: 'b' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('/tmp/bar'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('initialValue sets the value', async () => { @@ -134,16 +118,16 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { message: 'foo', initialValue: '/tmp/bar', root: '/tmp/', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('/tmp/bar'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('directory mode only allows selecting directories', async () => { @@ -151,12 +135,12 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { message: 'foo', root: '/tmp/', directory: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'f', { name: 'f' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'f', { name: 'f' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; @@ -169,11 +153,11 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { root: '/tmp/', initialValue: '/tmp', directory: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; @@ -186,12 +170,12 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { root: '/tmp/', initialValue: '/tmp', directory: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '/', { name: '/' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '/', { name: '/' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; @@ -204,12 +188,12 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { root: '/tmp/', initialValue: '/tmp/', directory: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'f', { name: 'f' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'f', { name: 'f' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; @@ -220,15 +204,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { const result = prompts.path({ message: 'foo', root: '/tmp/', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'r', { name: 'r' }); - input.emit('keypress', 'o', { name: 'o' }); - input.emit('keypress', 'o', { name: 'o' }); - input.emit('keypress', 't', { name: 't' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'r', { name: 'r' }); + mocks.input.emit('keypress', 'o', { name: 'o' }); + mocks.input.emit('keypress', 'o', { name: 'o' }); + mocks.input.emit('keypress', 't', { name: 't' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; @@ -240,22 +224,22 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { message: 'foo', root: '/tmp/', validate: (val) => (val !== '/tmp/bar' ? 'should be /tmp/bar' : undefined), - input, - output, + input: mocks.input, + output: mocks.output, }); // to match `root.zip` - input.emit('keypress', 'r', { name: 'r' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'r', { name: 'r' }); + mocks.input.emit('keypress', '', { name: 'return' }); // delete what we had - input.emit('keypress', '', { name: 'h', ctrl: true }); - input.emit('keypress', 'b', { name: 'b' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'h', ctrl: true }); + mocks.input.emit('keypress', 'b', { name: 'b' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('/tmp/bar'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('validation errors render and clear (using Error)', async () => { @@ -263,21 +247,21 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { message: 'foo', root: '/tmp/', validate: (val) => (val !== '/tmp/bar' ? new Error('should be /tmp/bar') : undefined), - input, - output, + input: mocks.input, + output: mocks.output, }); // to match `root.zip` - input.emit('keypress', 'r', { name: 'r' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'r', { name: 'r' }); + mocks.input.emit('keypress', '', { name: 'return' }); // delete what we had - input.emit('keypress', '', { name: 'h', ctrl: true }); - input.emit('keypress', 'b', { name: 'b' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'h', ctrl: true }); + mocks.input.emit('keypress', 'b', { name: 'b' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('/tmp/bar'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/progress-bar.test.ts b/packages/prompts/src/progress-bar.test.ts similarity index 69% rename from packages/prompts/test/progress-bar.test.ts rename to packages/prompts/src/progress-bar.test.ts index a64061c8..8e72fa02 100644 --- a/packages/prompts/test/progress-bar.test.ts +++ b/packages/prompts/src/progress-bar.test.ts @@ -1,35 +1,24 @@ import process from 'node:process'; import { EventEmitter } from 'node:stream'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import type { ProgressOptions } from '../src/index.js'; -import * as prompts from '../src/index.js'; -import { MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import type { ProgressOptions } from './index.js'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ output: true }>; beforeEach(() => { - output = new MockWritable(); + mocks = createMocks({ output: true, env: { CI: isCI } }); vi.useFakeTimers(); }); afterEach(() => { - vi.restoreAllMocks(); vi.useRealTimers(); }); test('returns progress API', () => { - const api = prompts.progress({ output }); + const api = prompts.progress({ output: mocks.output }); expect(api.stop).toBeTypeOf('function'); expect(api.start).toBeTypeOf('function'); @@ -39,7 +28,7 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { describe('start', () => { test('renders frames at interval', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -48,33 +37,33 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { vi.advanceTimersByTime(80); } - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start('foo'); vi.advanceTimersByTime(80); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders timer when indicator is "timer"', () => { - const result = prompts.progress({ output, indicator: 'timer' }); + const result = prompts.progress({ output: mocks.output, indicator: 'timer' }); result.start(); vi.advanceTimersByTime(80); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('stop', () => { test('renders submit symbol and stops progress', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -84,11 +73,11 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { vi.advanceTimersByTime(80); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders cancel symbol when calling cancel()', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -96,11 +85,11 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { result.cancel(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders error symbol when calling error()', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -108,11 +97,11 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { result.error(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -120,11 +109,11 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { result.stop('foo'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message without removing dots', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -132,11 +121,11 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { result.stop('foo.'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message when cancelling', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -144,11 +133,11 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { result.cancel('cancelled :-('); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message when erroring', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -156,13 +145,13 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { result.error('FATAL ERROR!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('message', () => { test('sets message for next frame', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start(); @@ -172,7 +161,7 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { vi.advanceTimersByTime(80); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); @@ -198,36 +187,36 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { }); test('uses default cancel message', () => { - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('uses custom cancel message when provided directly', () => { const result = prompts.progress({ - output, + output: mocks.output, cancelMessage: 'Custom cancel message', }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('uses custom error message when provided directly', () => { const result = prompts.progress({ - output, + output: mocks.output, errorMessage: 'Custom error message', }); result.start('Test operation'); processEmitter.emit('exit', 2); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('uses global custom cancel message from settings', () => { @@ -237,12 +226,12 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { // Set custom message prompts.settings.messages.cancel = 'Global cancel message'; - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original prompts.settings.messages.cancel = originalCancelMessage; @@ -256,12 +245,12 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { // Set custom message prompts.settings.messages.error = 'Global error message'; - const result = prompts.progress({ output }); + const result = prompts.progress({ output: mocks.output }); result.start('Test operation'); processEmitter.emit('exit', 2); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original prompts.settings.messages.error = originalErrorMessage; @@ -277,13 +266,13 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { prompts.settings.messages.error = 'Global error message'; const result = prompts.progress({ - output, + output: mocks.output, errorMessage: 'Progress error message', }); result.start('Test operation'); processEmitter.emit('exit', 2); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original values prompts.settings.messages.error = originalErrorMessage; @@ -299,13 +288,13 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { prompts.settings.messages.cancel = 'Global cancel message'; const result = prompts.progress({ - output, + output: mocks.output, cancelMessage: 'Progress cancel message', }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original values prompts.settings.messages.cancel = originalCancelMessage; @@ -317,14 +306,14 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => { test.each(['block', 'heavy', 'light'] satisfies Array)( 'renders %s progressbar', (style) => { - const result = prompts.progress({ output, style, max: 2, size: 10 }); + const result = prompts.progress({ output: mocks.output, style, max: 2, size: 10 }); result.start(); vi.advanceTimersByTime(160); result.advance(); vi.advanceTimersByTime(160); result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } ); }); diff --git a/packages/prompts/test/select-key.test.ts b/packages/prompts/src/select-key.test.ts similarity index 63% rename from packages/prompts/test/select-key.test.ts rename to packages/prompts/src/select-key.test.ts index 2e143a88..abe92505 100644 --- a/packages/prompts/test/select-key.test.ts +++ b/packages/prompts/src/select-key.test.ts @@ -1,29 +1,16 @@ import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); @@ -34,15 +21,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe(undefined); }); @@ -53,15 +40,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'b', { name: 'b' }); + mocks.input.emit('keypress', 'b', { name: 'b' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('b'); }); @@ -72,15 +59,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(prompts.isCancel(value)).toBe(true); }); @@ -91,15 +78,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option A', value: 'A' }, { label: 'Option B', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('A'); }); @@ -110,15 +97,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a', shift: true }); + mocks.input.emit('keypress', 'a', { name: 'a', shift: true }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('a'); }); @@ -130,16 +117,16 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option B', value: 'b' }, ], caseSensitive: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a' }); - input.emit('keypress', '', { name: 'escape' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', '', { name: 'escape' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(prompts.isCancel(value)).toBe(true); }); @@ -152,20 +139,20 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option B', value: 'b' }, ], caseSensitive: true, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a', shift: true }); + mocks.input.emit('keypress', 'a', { name: 'a', shift: true }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('A'); }); test('long option labels are wrapped correctly', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.selectKey({ message: 'Select an option:', @@ -176,20 +163,20 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { }, { label: 'Short label', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe('a'); }); test('long cancelled labels are wrapped correctly', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.selectKey({ message: 'Select an option:', @@ -200,15 +187,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { }, { label: 'Short label', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'escape' }); + mocks.input.emit('keypress', '', { name: 'escape' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', async () => { @@ -219,15 +206,15 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option B', value: 'b' }, ], withGuide: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('global withGuide: false removes guide', async () => { @@ -239,19 +226,19 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { { label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('long submitted labels are wrapped correctly', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.selectKey({ message: 'Select an option:', @@ -262,14 +249,14 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { }, { label: 'Short label', value: 'b' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'a', { name: 'a' }); + mocks.input.emit('keypress', 'a', { name: 'a' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/select.test.ts b/packages/prompts/src/select.test.ts similarity index 58% rename from packages/prompts/test/select.test.ts rename to packages/prompts/src/select.test.ts index 447a0e1b..b2a56b49 100644 --- a/packages/prompts/test/select.test.ts +++ b/packages/prompts/src/select.test.ts @@ -1,29 +1,16 @@ import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); @@ -31,67 +18,67 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { const result = prompts.select({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt0'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('down arrow selects next option', async () => { const result = prompts.select({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt1'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('up arrow selects previous option', async () => { const result = prompts.select({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'up' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'up' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt0'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can cancel', async () => { const result = prompts.select({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders option labels', async () => { @@ -101,16 +88,16 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { { value: 'opt0', label: 'Option 0' }, { value: 'opt1', label: 'Option 1' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt0'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders option hints', async () => { @@ -120,16 +107,16 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { { value: 'opt0', hint: 'Hint 0' }, { value: 'opt1', hint: 'Hint 1' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt0'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can be aborted by a signal', async () => { @@ -137,15 +124,15 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { const result = prompts.select({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, signal: controller.signal, }); controller.abort(); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders disabled options', async () => { @@ -156,20 +143,20 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { { value: 'opt1', label: 'Option 1' }, { value: 'opt2', label: 'Option 2', disabled: true, hint: 'Hint 2' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt1'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('wraps long results', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.select({ message: 'foo', @@ -180,19 +167,19 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { }, { value: 'opt1', label: 'Option 1' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('wraps long cancelled message', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.select({ message: 'foo', @@ -203,33 +190,33 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { }, { value: 'opt1', label: 'Option 1' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('wraps long messages', async () => { - output.columns = 40; + mocks.output.columns = 40; const result = prompts.select({ message: 'foo '.repeat(20).trim(), options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toEqual('opt0'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders multi-line option labels', async () => { @@ -239,20 +226,20 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { { value: 'opt0', label: 'Option 0\nwith multiple lines' }, { value: 'opt1', label: 'Option 1' }, ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('handles mixed size re-renders', async () => { - output.rows = 10; + mocks.output.rows = 10; const result = prompts.select({ message: 'Whatever', @@ -266,22 +253,22 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { label: `Option ${i}`, })), ], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'up' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'up' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('correctly limits options when message wraps to multiple lines', async () => { // Simulate a narrow terminal that forces the message to wrap - output.columns = 30; - output.rows = 12; + mocks.output.columns = 30; + mocks.output.rows = 12; const result = prompts.select({ // Long message that will wrap to multiple lines in a 30-column terminal @@ -290,21 +277,21 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { value: `opt${i}`, label: `Option ${i}`, })), - input, - output, + input: mocks.input, + output: mocks.output, }); // Scroll down through options to trigger the bug scenario - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt4'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', async () => { @@ -312,15 +299,15 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], withGuide: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('global withGuide: false removes guide', async () => { @@ -329,19 +316,19 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { const result = prompts.select({ message: 'foo', options: [{ value: 'opt0' }, { value: 'opt1' }], - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('correctly limits options with explicit multiline message', async () => { - output.rows = 12; + mocks.output.rows = 12; const result = prompts.select({ // Explicit multiline message @@ -350,19 +337,19 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => { value: `opt${i}`, label: `Option ${i}`, })), - input, - output, + input: mocks.input, + output: mocks.output, }); // Scroll down to test that options don't overflow - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'down' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'down' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('opt3'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/spinner.test.ts b/packages/prompts/src/spinner.test.ts similarity index 67% rename from packages/prompts/test/spinner.test.ts rename to packages/prompts/src/spinner.test.ts index 18896121..1e787fd7 100644 --- a/packages/prompts/test/spinner.test.ts +++ b/packages/prompts/src/spinner.test.ts @@ -1,36 +1,25 @@ import { EventEmitter } from 'node:stream'; import { styleText } from 'node:util'; import { getColumns, updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockWritable } from './test-utils.js'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ output: true }>; beforeEach(() => { - output = new MockWritable(); + mocks = createMocks({ output: true, env: { CI: isCI } }); vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); test('returns spinner API', () => { - const api = prompts.spinner({ output }); + const api = prompts.spinner({ output: mocks.output }); expect(api.stop).toBeTypeOf('function'); expect(api.start).toBeTypeOf('function'); @@ -39,7 +28,7 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { describe('start', () => { test('renders frames at interval', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -50,11 +39,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start('foo'); @@ -62,11 +51,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders timer when indicator is "timer"', () => { - const result = prompts.spinner({ output, indicator: 'timer' }); + const result = prompts.spinner({ output: mocks.output, indicator: 'timer' }); result.start(); @@ -74,12 +63,12 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('handles wrapping', () => { - const columns = getColumns(output); - const result = prompts.spinner({ output }); + const columns = getColumns(mocks.output); + const result = prompts.spinner({ output: mocks.output }); result.start('x'.repeat(columns + 10)); @@ -87,11 +76,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop('stopped'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('handles multi-line messages', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start('foo\nbar\nbaz'); @@ -99,13 +88,13 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('stop', () => { test('renders submit symbol and stops spinner', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -115,11 +104,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { vi.advanceTimersByTime(80); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders cancel symbol when calling cancel()', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -127,11 +116,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.cancel(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders error symbol when calling error()', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -139,11 +128,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.error(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -151,11 +140,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop('foo'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message without removing dots', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -163,11 +152,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop('foo.'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message when cancelling', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -175,11 +164,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.cancel('too dizzy — spinning cancelled'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders message when erroring', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -187,11 +176,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.error('error: spun too fast!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('does not throw if called before start', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); expect(() => result.stop()).not.toThrow(); }); @@ -199,7 +188,7 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { describe('message', () => { test('sets message for next frame', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start(); @@ -211,13 +200,13 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('indicator customization', () => { test('custom frames', () => { - const result = prompts.spinner({ output, frames: ['🐴', '🦋', '🐙', '🐶'] }); + const result = prompts.spinner({ output: mocks.output, frames: ['🐴', '🦋', '🐙', '🐶'] }); result.start(); @@ -228,11 +217,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('custom frames with lots of frame have consistent ellipsis display', () => { - const result = prompts.spinner({ output, frames: Object.keys(Array(10).fill(0)) }); + const result = prompts.spinner({ output: mocks.output, frames: Object.keys(Array(10).fill(0)) }); result.start(); @@ -242,11 +231,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('custom delay', () => { - const result = prompts.spinner({ output, delay: 200 }); + const result = prompts.spinner({ output: mocks.output, delay: 200 }); result.start(); @@ -257,11 +246,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('custom frame style', () => { - const result = prompts.spinner({ output, styleFrame: (text) => styleText('red', text) }); + const result = prompts.spinner({ output: mocks.output, styleFrame: (text) => styleText('red', text) }); result.start(); @@ -271,7 +260,7 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); @@ -297,36 +286,36 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { }); test('uses default cancel message', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('uses custom cancel message when provided directly', () => { const result = prompts.spinner({ - output, + output: mocks.output, cancelMessage: 'Custom cancel message', }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('uses custom error message when provided directly', () => { const result = prompts.spinner({ - output, + output: mocks.output, errorMessage: 'Custom error message', }); result.start('Test operation'); processEmitter.emit('exit', 2); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('uses global custom cancel message from settings', () => { @@ -336,12 +325,12 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { // Set custom message prompts.settings.messages.cancel = 'Global cancel message'; - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original prompts.settings.messages.cancel = originalCancelMessage; @@ -356,12 +345,12 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { // Set custom message prompts.settings.messages.error = 'Global error message'; - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start('Test operation'); processEmitter.emit('exit', 2); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original prompts.settings.messages.error = originalErrorMessage; @@ -377,13 +366,13 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { prompts.settings.messages.error = 'Global error message'; const result = prompts.spinner({ - output, + output: mocks.output, errorMessage: 'Spinner error message', }); result.start('Test operation'); processEmitter.emit('exit', 2); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original values prompts.settings.messages.error = originalErrorMessage; @@ -399,13 +388,13 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { prompts.settings.messages.cancel = 'Global cancel message'; const result = prompts.spinner({ - output, + output: mocks.output, cancelMessage: 'Spinner cancel message', }); result.start('Test operation'); processEmitter.emit('SIGINT'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); } finally { // Reset to original values prompts.settings.messages.cancel = originalCancelMessage; @@ -416,7 +405,7 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { test('can be aborted by a signal', async () => { const controller = new AbortController(); const result = prompts.spinner({ - output, + output: mocks.output, signal: controller.signal, }); @@ -424,11 +413,11 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { controller.abort(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', () => { - const result = prompts.spinner({ output, withGuide: false }); + const result = prompts.spinner({ output: mocks.output, withGuide: false }); result.start('foo'); @@ -436,13 +425,13 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('global withGuide: false removes guide', () => { updateSettings({ withGuide: false }); - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start('foo'); @@ -450,12 +439,12 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.stop(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); describe('clear', () => { test('stops and clears the spinner from the output', () => { - const result = prompts.spinner({ output }); + const result = prompts.spinner({ output: mocks.output }); result.start('Loading'); @@ -463,7 +452,7 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { result.clear(); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); }); diff --git a/packages/prompts/test/task-log.test.ts b/packages/prompts/src/task-log.test.ts similarity index 69% rename from packages/prompts/test/task-log.test.ts rename to packages/prompts/src/task-log.test.ts index dbbc31a6..24977091 100644 --- a/packages/prompts/test/task-log.test.ts +++ b/packages/prompts/src/task-log.test.ts @@ -1,71 +1,55 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - output.isTTY = isCI === 'false'; - input = new MockReadable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); + mocks.output.isTTY = isCI === 'false'; }); test('writes message header', () => { prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); describe('message', () => { test('can write line by line', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); log.message('line 0'); log.message('line 1'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can write multiple lines', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); log.message('line 0\nline 1'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('enforces limit if set', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', limit: 2, }); @@ -74,13 +58,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.message('line 1'); log.message('line 2'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('raw = true appends message text until newline', async () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -88,13 +72,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.message('still line 0', { raw: true }); log.message('\nline 1', { raw: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('raw = true works when mixed with non-raw messages', async () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -102,13 +86,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.message('still line 0', { raw: true }); log.message('line 1'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('raw = true works when started with non-raw messages', async () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -116,13 +100,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.message('line 1', { raw: true }); log.message('still line 1', { raw: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('prints empty lines', async () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -130,13 +114,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.message(''); log.message('line 3'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('destructive ansi codes are stripped', async () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -144,15 +128,15 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.message('line 2\x1b[2K bad ansi!'); log.message('line 3'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('error', () => { test('renders output with message', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -161,13 +145,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.error('some error!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('clears output if showLog = false', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -176,15 +160,15 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.error('some error!', { showLog: false }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); describe('success', () => { test('clears output and renders message', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -193,13 +177,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.success('success!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders output if showLog = true', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', }); @@ -208,7 +192,7 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.success('success!', { showLog: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); @@ -216,8 +200,8 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { describe.each(['error', 'success'] as const)('%s', (method) => { test('retainLog = true outputs full log', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', retainLog: true, }); @@ -228,13 +212,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log[method]('woo!', { showLog: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('retainLog = true outputs full log with limit', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', retainLog: true, limit: 2, @@ -246,13 +230,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log[method]('woo!', { showLog: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('retainLog = false outputs full log without limit', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', retainLog: false, }); @@ -263,13 +247,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log[method]('woo!', { showLog: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('retainLog = false outputs limited log with limit', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', retainLog: false, limit: 2, @@ -281,13 +265,13 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log[method]('woo!', { showLog: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('outputs limited log with limit by default', () => { const log = prompts.taskLog({ - input, - output, + input: mocks.input, + output: mocks.output, title: 'foo', limit: 2, }); @@ -298,7 +282,7 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log[method]('woo!', { showLog: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); }); @@ -307,8 +291,8 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { test('can render multiple groups of equal size', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group0 = log.group('Group 0'); const group1 = log.group('Group 1'); @@ -318,14 +302,14 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { group1.message(`Group 1 line ${i}`); } - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can render multiple groups of different sizes', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group0 = log.group('Group 0'); const group1 = log.group('Group 1'); @@ -337,40 +321,40 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { group1.message(`Group 1 line ${i}`); } - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders success state', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group = log.group('Group 0'); group.message('Group 0 line 0'); group.success('Group success!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders error state', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group = log.group('Group 0'); group.message('Group 0 line 0'); group.error('Group error!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('applies limit per group', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, limit: 2, }); const group0 = log.group('Group 0'); @@ -381,40 +365,40 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { group1.message(`Group 1 line ${i}`); } - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders group success state', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group = log.group('Group 0'); group.message('Group 0 line 0'); group.success('Group success!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders group error state', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group = log.group('Group 0'); group.message('Group 0 line 0'); group.error('Group error!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('showLog shows all groups in order', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group0 = log.group('Group 0'); const group1 = log.group('Group 1'); @@ -431,20 +415,20 @@ describe.each(['true', 'false'])('taskLog (isCI = %s)', (isCI) => { log.error('overall error', { showLog: true }); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('handles empty groups', async () => { const log = prompts.taskLog({ title: 'Some log', - input, - output, + input: mocks.input, + output: mocks.output, }); const group = log.group('Group 0'); group.success('Group success!'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); }); diff --git a/packages/prompts/test/text.test.ts b/packages/prompts/src/text.test.ts similarity index 51% rename from packages/prompts/test/text.test.ts rename to packages/prompts/src/text.test.ts index 62de9067..d149e121 100644 --- a/packages/prompts/test/text.test.ts +++ b/packages/prompts/src/text.test.ts @@ -1,163 +1,150 @@ import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; +import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import * as prompts from './index.js'; describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); + let mocks: Mocks<{ input: true; output: true }>; beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); + mocks = createMocks({ input: true, output: true, env: { CI: isCI } }); }); afterEach(() => { - vi.restoreAllMocks(); updateSettings({ withGuide: true }); }); test('renders message', async () => { const result = prompts.text({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders placeholder if set', async () => { const result = prompts.text({ message: 'foo', placeholder: 'bar', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); expect(value).toBe(''); }); test('can cancel', async () => { const result = prompts.text({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'escape', { name: 'escape' }); + mocks.input.emit('keypress', 'escape', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders cancelled value if one set', async () => { const result = prompts.text({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'escape' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'escape' }); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('renders submitted value', async () => { const result = prompts.text({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('defaultValue sets the value but does not render', async () => { const result = prompts.text({ message: 'foo', defaultValue: 'bar', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('bar'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('validation errors render and clear', async () => { const result = prompts.text({ message: 'foo', validate: (val) => (val !== 'xy' ? 'should be xy' : undefined), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('validation errors render and clear (using Error)', async () => { const result = prompts.text({ message: 'foo', validate: (val) => (val !== 'xy' ? new Error('should be xy') : undefined), - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'x', { name: 'x' }); + mocks.input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', 'y', { name: 'y' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('placeholder is not used as value when pressing enter', async () => { @@ -165,62 +152,62 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { message: 'foo', placeholder: ' (hit Enter to use default)', defaultValue: 'default-value', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe('default-value'); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('empty string when no value and no default', async () => { const result = prompts.text({ message: 'foo', placeholder: ' (hit Enter to use default)', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); const value = await result; expect(value).toBe(''); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('can be aborted by a signal', async () => { const controller = new AbortController(); const result = prompts.text({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, signal: controller.signal, }); controller.abort(); const value = await result; expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('withGuide: false removes guide', async () => { const result = prompts.text({ message: 'foo', withGuide: false, - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); test('global withGuide: false removes guide', async () => { @@ -228,14 +215,14 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { const result = prompts.text({ message: 'foo', - input, - output, + input: mocks.input, + output: mocks.output, }); - input.emit('keypress', '', { name: 'return' }); + mocks.input.emit('keypress', '', { name: 'return' }); await result; - expect(output.buffer).toMatchSnapshot(); + expect(mocks.output.buffer).toMatchSnapshot(); }); }); diff --git a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap deleted file mode 100644 index efb12990..00000000 --- a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap +++ /dev/null @@ -1,895 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`autocomplete > autocompleteMultiselect respects global withGuide: false 1`] = ` -[ - "", - "◆ Select fruits - -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -", - "", - "", - "", - "Search: -Apple -◻ Banana -Cherry -Grape -Orange -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -", - "", - "", - "", - "◼ Banana", - "", - "", - "", - "◇ Select fruits -1 items selected", - " -", - "", -] -`; - -exports[`autocomplete > autocompleteMultiselect respects withGuide: false 1`] = ` -[ - "", - "◆ Select fruits - -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -", - "", - "", - "", - "Search: -Apple -◻ Banana -Cherry -Grape -Orange -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -", - "", - "", - "", - "◼ Banana", - "", - "", - "", - "◇ Select fruits -1 items selected", - " -", - "", -] -`; - -exports[`autocomplete > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - " -", - "", -] -`; - -exports[`autocomplete > cannot select disabled options when only one left 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: k█ (1 match) -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -│", - " -", - "", -] -`; - -exports[`autocomplete > displays disabled options correctly 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: -Apple -● Banana -Cherry -Grape -Orange -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Banana -● Cherry -Grape -Orange -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Cherry -● Grape -Orange -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Grape -● Orange -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "● Apple -Banana -Cherry -Grape -Orange -○ Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Apple", - " -", - "", -] -`; - -exports[`autocomplete > limits displayed options when maxItems is set 1`] = ` -[ - "", - "│ -◆ Select an option -│ -Search: _ -● Option 0 -Option 1 -Option 2 -Option 3 -Option 4 -... -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select an option -Option 0", - " -", - "", -] -`; - -exports[`autocomplete > placeholder is shown if set 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: Type to search... -● Apple -Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: g█ (2 matches) -● Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Grape", - " -", - "", -] -`; - -exports[`autocomplete > renders bottom ellipsis when items do not fit 1`] = ` -[ - "", - "│ -◆ Select an option -│ -Search: _ -● Line 0 -│ Line 1 -│ Line 2 -│ Line 3 -... -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "◇ Select an option -Line 0 -Line 1 -Line 2 -Line 3", - " -", - "", -] -`; - -exports[`autocomplete > renders initial UI with message and instructions 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Apple", - " -", - "", -] -`; - -exports[`autocomplete > renders placeholder if set 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: Type to search... -● Apple -Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Apple", - " -", - "", -] -`; - -exports[`autocomplete > renders top ellipsis when scrolled down and its do not fit 1`] = ` -[ - "", - "│ -◆ Select an option -│ -Search: _ -... -● Option 2 -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "│ -◇ Select an option -Option 2", - " -", - "", -] -`; - -exports[`autocomplete > shows hint when option has hint and is focused 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: -Apple -● Banana -Cherry -Grape -Orange -Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Banana -● Cherry -Grape -Orange -Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Cherry -● Grape -Orange -Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Grape -● Orange -Kiwi -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Orange -● Kiwi (New Zealand) -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Kiwi", - " -", - "", -] -`; - -exports[`autocomplete > shows no matches message when search has no results 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: z█ (0 matches) -No matches found -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -│", - " -", - "", -] -`; - -exports[`autocomplete > shows selected value in submit state 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: -Apple -● Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Banana", - " -", - "", -] -`; - -exports[`autocomplete > shows strikethrough in cancel state 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "■ Select a fruit -│", - " -", - "", -] -`; - -exports[`autocomplete > supports initialValue 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -Apple -Banana -● Cherry -Grape -Orange -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Cherry", - " -", - "", -] -`; - -exports[`autocomplete with custom filter > falls back to default filter when not provided 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: a█ (2 matches) -● Apple -Banana -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Apple", - " -", - "", -] -`; - -exports[`autocomplete with custom filter > uses custom filter function when provided 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -● Apple -Banana -Cherry -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: a█ (1 match) -● Apple -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -Apple", - " -", - "", -] -`; - -exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - " -", - "", -] -`; - -exports[`autocompleteMultiselect > can use navigation keys to select options 1`] = ` -[ - "", - "│ -◆ Select fruits -│ -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: -Apple -◻ Banana -Cherry -Grape -Orange -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◼ Banana", - "", - "", - "", - "", - "Banana -◻ Cherry -Grape -Orange -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◼ Cherry", - "", - "", - "", - "", - "◇ Select fruits -2 items selected", - " -", - "", -] -`; - -exports[`autocompleteMultiselect > cannot select disabled options when only one left 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -◻ Kiwi -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: k█ (1 match) -◻ Kiwi -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -0 items selected", - " -", - "", -] -`; - -exports[`autocompleteMultiselect > displays disabled options correctly 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -◻ Kiwi -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: -Apple -◻ Banana -Cherry -Grape -Orange -◻ Kiwi -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Banana -◻ Cherry -Grape -Orange -◻ Kiwi -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Cherry -◻ Grape -Orange -◻ Kiwi -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Grape -◻ Orange -◻ Kiwi -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◻ Apple -Banana -Cherry -Grape -Orange -◻ Kiwi -↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◼ Apple", - "", - "", - "", - "", - "◇ Select a fruit -1 items selected", - " -", - "", -] -`; - -exports[`autocompleteMultiselect > renders error when empty selection & required is true 1`] = ` -[ - "", - "│ -◆ Select a fruit -│ -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ Select a fruit -│ -Search: _ -Please select at least one item -◻ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ Select a fruit -│ -Search: _ -◼ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ Select a fruit -1 items selected", - " -", - "", -] -`; - -exports[`autocompleteMultiselect > supports custom filter function 1`] = ` -[ - "", - "│ -◆ Select fruits -│ -Search: _ -◻ Apple -Banana -Cherry -Grape -Orange -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: a█ (1 match) -◻ Apple -↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◼ Apple", - "", - "", - "", - "", - "◇ Select fruits -1 items selected", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/box.test.ts.snap b/packages/prompts/test/__snapshots__/box.test.ts.snap deleted file mode 100644 index cb745226..00000000 --- a/packages/prompts/test/__snapshots__/box.test.ts.snap +++ /dev/null @@ -1,551 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`box (isCI = false) > cannot have width larger than 100% 1`] = ` -[ - "│ ┌─title──────────────────────────────────────────────────────────────────────┐ -", - "│ │ message │ -", - "│ └────────────────────────────────────────────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders as specified width 1`] = ` -[ - "│ ┌─title──────────────────────────────┐ -", - "│ │ short │ -", - "│ │ somewhat questionably long line │ -", - "│ └────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders as wide as longest line with width: auto 1`] = ` -[ - "│ ┌─title──────────────────────────────┐ -", - "│ │ short │ -", - "│ │ somewhat questionably long line │ -", - "│ └────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders auto width with content longer than title 1`] = ` -[ - "│ ┌─title──────────────────────────┐ -", - "│ │ messagemessagemessagemessage │ -", - "│ └────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders auto width with title longer than content 1`] = ` -[ - "│ ┌─titletitletitletitle─┐ -", - "│ │ message │ -", - "│ └──────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders center aligned content 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders center aligned title 1`] = ` -[ - "│ ┌───title────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders left aligned content 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders left aligned title 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders message 1`] = ` -[ - "│ ┌────────────────────────────────────────────────────────────────────────────┐ -", - "│ │ message │ -", - "│ └────────────────────────────────────────────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders message with title 1`] = ` -[ - "│ ┌─some title─────────────────────────────────────────────────────────────────┐ -", - "│ │ message │ -", - "│ └────────────────────────────────────────────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders right aligned content 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders right aligned title 1`] = ` -[ - "│ ┌──────title─┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders rounded corners when rounded is true 1`] = ` -[ - "│ ╭─title──────╮ -", - "│ │ message │ -", - "│ ╰────────────╯ -", -] -`; - -exports[`box (isCI = false) > renders specified contentPadding 1`] = ` -[ - "│ ┌─title──────────────┐ -", - "│ │ message │ -", - "│ └────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders specified titlePadding 1`] = ` -[ - "│ ┌──────title───────┐ -", - "│ │ message │ -", - "│ └──────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders truncated long titles 1`] = ` -[ - "│ ┌─foofoof...─┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders wide characters with auto width 1`] = ` -[ - "│ ┌─这是标题─────────────────┐ -", - "│ │ 이게 첫 번째 줄이에요 │ -", - "│ │ これは次の行です │ -", - "│ └──────────────────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders wide characters with specified width 1`] = ` -[ - "│ ┌─这是标题───┐ -", - "│ │ 이게 첫 │ -", - "│ │ 번째 │ -", - "│ │ 줄이에요 │ -", - "│ │ これは次 │ -", - "│ │ の行です │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders with formatBorder formatting 1`] = ` -[ - "│ ─title┐ -", - "│ │ message │ -", - "│ ┘ -", -] -`; - -exports[`box (isCI = false) > renders without guide when global withGuide is false 1`] = ` -[ - "┌─title──────┐ -", - "│ message │ -", - "└────────────┘ -", -] -`; - -exports[`box (isCI = false) > renders without guide when withGuide is false 1`] = ` -[ - "┌─title──────┐ -", - "│ message │ -", - "└────────────┘ -", -] -`; - -exports[`box (isCI = false) > wraps content to fit within specified width 1`] = ` -[ - "│ ┌─title──────────────────────────────┐ -", - "│ │ foo barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo bar │ -", - "│ └────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > cannot have width larger than 100% 1`] = ` -[ - "│ ┌─title──────────────────────────────────────────────────────────────────────┐ -", - "│ │ message │ -", - "│ └────────────────────────────────────────────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders as specified width 1`] = ` -[ - "│ ┌─title──────────────────────────────┐ -", - "│ │ short │ -", - "│ │ somewhat questionably long line │ -", - "│ └────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders as wide as longest line with width: auto 1`] = ` -[ - "│ ┌─title──────────────────────────────┐ -", - "│ │ short │ -", - "│ │ somewhat questionably long line │ -", - "│ └────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders auto width with content longer than title 1`] = ` -[ - "│ ┌─title──────────────────────────┐ -", - "│ │ messagemessagemessagemessage │ -", - "│ └────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders auto width with title longer than content 1`] = ` -[ - "│ ┌─titletitletitletitle─┐ -", - "│ │ message │ -", - "│ └──────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders center aligned content 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders center aligned title 1`] = ` -[ - "│ ┌───title────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders left aligned content 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders left aligned title 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders message 1`] = ` -[ - "│ ┌────────────────────────────────────────────────────────────────────────────┐ -", - "│ │ message │ -", - "│ └────────────────────────────────────────────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders message with title 1`] = ` -[ - "│ ┌─some title─────────────────────────────────────────────────────────────────┐ -", - "│ │ message │ -", - "│ └────────────────────────────────────────────────────────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders right aligned content 1`] = ` -[ - "│ ┌─title──────┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders right aligned title 1`] = ` -[ - "│ ┌──────title─┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders rounded corners when rounded is true 1`] = ` -[ - "│ ╭─title──────╮ -", - "│ │ message │ -", - "│ ╰────────────╯ -", -] -`; - -exports[`box (isCI = true) > renders specified contentPadding 1`] = ` -[ - "│ ┌─title──────────────┐ -", - "│ │ message │ -", - "│ └────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders specified titlePadding 1`] = ` -[ - "│ ┌──────title───────┐ -", - "│ │ message │ -", - "│ └──────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders truncated long titles 1`] = ` -[ - "│ ┌─foofoof...─┐ -", - "│ │ message │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders wide characters with auto width 1`] = ` -[ - "│ ┌─这是标题─────────────────┐ -", - "│ │ 이게 첫 번째 줄이에요 │ -", - "│ │ これは次の行です │ -", - "│ └──────────────────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders wide characters with specified width 1`] = ` -[ - "│ ┌─这是标题───┐ -", - "│ │ 이게 첫 │ -", - "│ │ 번째 │ -", - "│ │ 줄이에요 │ -", - "│ │ これは次 │ -", - "│ │ の行です │ -", - "│ └────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders with formatBorder formatting 1`] = ` -[ - "│ ─title┐ -", - "│ │ message │ -", - "│ ┘ -", -] -`; - -exports[`box (isCI = true) > renders without guide when global withGuide is false 1`] = ` -[ - "┌─title──────┐ -", - "│ message │ -", - "└────────────┘ -", -] -`; - -exports[`box (isCI = true) > renders without guide when withGuide is false 1`] = ` -[ - "┌─title──────┐ -", - "│ message │ -", - "└────────────┘ -", -] -`; - -exports[`box (isCI = true) > wraps content to fit within specified width 1`] = ` -[ - "│ ┌─title──────────────────────────────┐ -", - "│ │ foo barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo barfoo │ -", - "│ │ barfoo barfoo barfoo bar │ -", - "│ └────────────────────────────────────┘ -", -] -`; diff --git a/packages/prompts/test/__snapshots__/confirm.test.ts.snap b/packages/prompts/test/__snapshots__/confirm.test.ts.snap deleted file mode 100644 index d7bafcda..00000000 --- a/packages/prompts/test/__snapshots__/confirm.test.ts.snap +++ /dev/null @@ -1,481 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`confirm (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ yes? -● Yes / No -└ -", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "■ foo -│ No -│", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > can set initialValue 1`] = ` -[ - "", - "│ -◆ foo -Yes / ● No -└ -", - "", - "", - "", - "◇ foo -No", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● Yes / No - -", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > left arrow moves to previous choice 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "Yes / ● No", - "", - "", - "", - "", - "● Yes / No", - "", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > renders custom active choice 1`] = ` -[ - "", - "│ -◆ foo -● bleep / No -└ -", - "", - "", - "", - "◇ foo -bleep", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > renders custom inactive choice 1`] = ` -[ - "", - "│ -◆ foo -● Yes / bleep -└ -", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > renders message with choices 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > renders multi-line messages correctly 1`] = ` -[ - "", - "│ -◆ foo -│ bar -│ baz -● Yes / No -└ -", - "", - "", - "", - "◇ foo -│ bar -│ baz -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > renders options in vertical alignment 1`] = ` -[ - "", - "│ -◆ foo -● Yes -No -└ -", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > right arrow moves to next choice 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "Yes / ● No", - "", - "", - "", - "", - "◇ foo -No", - " -", - "", -] -`; - -exports[`confirm (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● Yes / No - -", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ yes? -● Yes / No -└ -", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "■ foo -│ No -│", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > can set initialValue 1`] = ` -[ - "", - "│ -◆ foo -Yes / ● No -└ -", - "", - "", - "", - "◇ foo -No", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● Yes / No - -", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > left arrow moves to previous choice 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "Yes / ● No", - "", - "", - "", - "", - "● Yes / No", - "", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > renders custom active choice 1`] = ` -[ - "", - "│ -◆ foo -● bleep / No -└ -", - "", - "", - "", - "◇ foo -bleep", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > renders custom inactive choice 1`] = ` -[ - "", - "│ -◆ foo -● Yes / bleep -└ -", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > renders message with choices 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > renders multi-line messages correctly 1`] = ` -[ - "", - "│ -◆ foo -│ bar -│ baz -● Yes / No -└ -", - "", - "", - "", - "◇ foo -│ bar -│ baz -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > renders options in vertical alignment 1`] = ` -[ - "", - "│ -◆ foo -● Yes -No -└ -", - "", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > right arrow moves to next choice 1`] = ` -[ - "", - "│ -◆ foo -● Yes / No -└ -", - "", - "", - "", - "Yes / ● No", - "", - "", - "", - "", - "◇ foo -No", - " -", - "", -] -`; - -exports[`confirm (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● Yes / No - -", - "", - "", - "◇ foo -Yes", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/date.test.ts.snap b/packages/prompts/test/__snapshots__/date.test.ts.snap deleted file mode 100644 index 559b1be9..00000000 --- a/packages/prompts/test/__snapshots__/date.test.ts.snap +++ /dev/null @@ -1,263 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`date (isCI = false) > can cancel 1`] = ` -[ - "", - "│ -◆ Pick a date -│ mm/dd/yyyy -└ -", - "", - "", - "", - "■ Pick a date -│", - " -", - "", -] -`; - -exports[`date (isCI = false) > defaultValue used when empty submit 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 12/25/2025 -└ -", - "", - "", - "", - "◇ Pick a date -12/25/2025", - " -", - "", -] -`; - -exports[`date (isCI = false) > renders initial value 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 01/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -01/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = false) > renders message 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 01/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -01/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = false) > renders submitted value 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 06/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -06/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = false) > supports MDY format 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 01/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -01/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ Pick a date -01/15/2025 - -", - "", - "", - "◇ Pick a date - 01/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = true) > can cancel 1`] = ` -[ - "", - "│ -◆ Pick a date -│ mm/dd/yyyy -└ -", - "", - "", - "", - "■ Pick a date -│", - " -", - "", -] -`; - -exports[`date (isCI = true) > defaultValue used when empty submit 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 12/25/2025 -└ -", - "", - "", - "", - "◇ Pick a date -12/25/2025", - " -", - "", -] -`; - -exports[`date (isCI = true) > renders initial value 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 01/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -01/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = true) > renders message 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 01/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -01/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = true) > renders submitted value 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 06/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -06/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = true) > supports MDY format 1`] = ` -[ - "", - "│ -◆ Pick a date -│ 01/15/2025 -└ -", - "", - "", - "", - "◇ Pick a date -01/15/2025", - " -", - "", -] -`; - -exports[`date (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ Pick a date -01/15/2025 - -", - "", - "", - "◇ Pick a date - 01/15/2025", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap b/packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap deleted file mode 100644 index 7db02072..00000000 --- a/packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap +++ /dev/null @@ -1,1205 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`groupMultiselect (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ Select a fruit -◻ group1 -│ └ group1value0 -group2 -group2value0 -└ -", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > can deselect an option 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "group1 -group1value0 -◼ group1value1 -└ -", - "", - "", - "", - "group1 -group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > can select a group 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◼ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > can select a group by selecting all members 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "group1 -group1value0 -◼ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > can select multiple options 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ │ group1value1 -│ └ group1value2 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -group1value2 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "group1value0 -◻ group1value1 -group1value2 -└ -", - "", - "", - "", - "◼ group1value1", - "", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > can submit empty selection when require = false 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > cursorAt sets initial selection 1`] = ` -[ - "", - "│ -◆ foo -group1 -group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "◼ group1value1", - "", - "", - "", - "", - "◇ foo -group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - ◻ group1 - │ group1value0 - └ group1value1 - -", - "", - "", - "", - " group1 - ◻ group1value0 - group1value1 - -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "◇ foo - group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > groupSpacing > negative spacing is ignored 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ └ group1value0 -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◼ group1value0 -group2 -group2value0 -└ -", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > groupSpacing > renders spaced groups 1`] = ` -[ - "", - "│ -◆ foo -│ -│ -◻ group1 -│ └ group1value0 -│ -│ -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -│ -│ -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◼ group1value0 -│ -│ -group2 -group2value0 -└ -", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > initial values can be set 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > renders error when nothing selected 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "▲ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -Please select at least one option. - Press  space  to select,  enter  to submit -", - "", - "", - "", - "◆ foo -group1 -◻ group1value0 -group1value1 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > renders message with options 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -group2 -group2value0 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > selectableGroups = false > cannot select groups 1`] = ` -[ - "", - "│ -◆ foo - group1 - ◻ group1value0 - group1value1 -└ -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > selectableGroups = false > selecting all members of group does not select group 1`] = ` -[ - "", - "│ -◆ foo - group1 - ◻ group1value0 - group1value1 -└ -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "", - " group1value0 - ◻ group1value1 -└ -", - "", - "", - "", - " ◼ group1value1", - "", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > values can be non-primitive 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ value0 -│ └ value1 -└ -", - "", - "", - "", - "group1 -◻ value0 -value1 -└ -", - "", - "", - "", - "◼ value0", - "", - "", - "", - "", - "◇ foo -value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - ◻ group1 - │ group1value0 - └ group1value1 - -", - "", - "", - "", - " group1 - ◻ group1value0 - group1value1 - -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "◇ foo - group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ Select a fruit -◻ group1 -│ └ group1value0 -group2 -group2value0 -└ -", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > can deselect an option 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "group1 -group1value0 -◼ group1value1 -└ -", - "", - "", - "", - "group1 -group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > can select a group 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◼ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > can select a group by selecting all members 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "group1 -group1value0 -◼ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > can select multiple options 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ │ group1value1 -│ └ group1value2 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -group1value2 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "group1value0 -◻ group1value1 -group1value2 -└ -", - "", - "", - "", - "◼ group1value1", - "", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > can submit empty selection when require = false 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > cursorAt sets initial selection 1`] = ` -[ - "", - "│ -◆ foo -group1 -group1value0 -◻ group1value1 -└ -", - "", - "", - "", - "◼ group1value1", - "", - "", - "", - "", - "◇ foo -group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - ◻ group1 - │ group1value0 - └ group1value1 - -", - "", - "", - "", - " group1 - ◻ group1value0 - group1value1 - -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "◇ foo - group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > groupSpacing > negative spacing is ignored 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ └ group1value0 -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◼ group1value0 -group2 -group2value0 -└ -", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > groupSpacing > renders spaced groups 1`] = ` -[ - "", - "│ -◆ foo -│ -│ -◻ group1 -│ └ group1value0 -│ -│ -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -│ -│ -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◼ group1value0 -│ -│ -group2 -group2value0 -└ -", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > initial values can be set 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "◇ foo -group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > renders error when nothing selected 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -└ -", - "", - "", - "", - "▲ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -Please select at least one option. - Press  space  to select,  enter  to submit -", - "", - "", - "", - "◆ foo -group1 -◻ group1value0 -group1value1 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > renders message with options 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ group1value0 -│ └ group1value1 -group2 -group2value0 -└ -", - "", - "", - "", - "group1 -◻ group1value0 -group1value1 -group2 -group2value0 -└ -", - "", - "", - "", - "◼ group1value0", - "", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > selectableGroups = false > cannot select groups 1`] = ` -[ - "", - "│ -◆ foo - group1 - ◻ group1value0 - group1value1 -└ -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "", - "◇ foo -group1value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > selectableGroups = false > selecting all members of group does not select group 1`] = ` -[ - "", - "│ -◆ foo - group1 - ◻ group1value0 - group1value1 -└ -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "", - " group1value0 - ◻ group1value1 -└ -", - "", - "", - "", - " ◼ group1value1", - "", - "", - "", - "", - "◇ foo -group1value0, group1value1", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > values can be non-primitive 1`] = ` -[ - "", - "│ -◆ foo -◻ group1 -│ │ value0 -│ └ value1 -└ -", - "", - "", - "", - "group1 -◻ value0 -value1 -└ -", - "", - "", - "", - "◼ value0", - "", - "", - "", - "", - "◇ foo -value0", - " -", - "", -] -`; - -exports[`groupMultiselect (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - ◻ group1 - │ group1value0 - └ group1value1 - -", - "", - "", - "", - " group1 - ◻ group1value0 - group1value1 - -", - "", - "", - "", - " ◼ group1value0", - "", - "", - "", - "◇ foo - group1value0", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/log.test.ts.snap b/packages/prompts/test/__snapshots__/log.test.ts.snap deleted file mode 100644 index af5e5615..00000000 --- a/packages/prompts/test/__snapshots__/log.test.ts.snap +++ /dev/null @@ -1,283 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`log (isCI = false) > error > renders error message 1`] = ` -[ - "│ -■ error message -", -] -`; - -exports[`log (isCI = false) > info > renders info message 1`] = ` -[ - "│ -● info message -", -] -`; - -exports[`log (isCI = false) > message > renders empty lines correctly 1`] = ` -[ - "│ -│ foo -│ -│ bar -", -] -`; - -exports[`log (isCI = false) > message > renders empty lines with guide disabled 1`] = ` -[ - " -foo - -bar -", -] -`; - -exports[`log (isCI = false) > message > renders empty message correctly 1`] = ` -[ - "│ -│ -", -] -`; - -exports[`log (isCI = false) > message > renders empty message with guide disabled 1`] = ` -[ - " - -", -] -`; - -exports[`log (isCI = false) > message > renders message 1`] = ` -[ - "│ -│ message -", -] -`; - -exports[`log (isCI = false) > message > renders message from array 1`] = ` -[ - "│ -│ line 1 -│ line 2 -│ line 3 -", -] -`; - -exports[`log (isCI = false) > message > renders message with custom spacing 1`] = ` -[ - "│ -│ -│ -│ spaced message -", -] -`; - -exports[`log (isCI = false) > message > renders message with custom symbols and spacing 1`] = ` -[ - "-- ->> custom --- symbols -", -] -`; - -exports[`log (isCI = false) > message > renders message with guide disabled 1`] = ` -[ - " -standalone message -", -] -`; - -exports[`log (isCI = false) > message > renders multiline message 1`] = ` -[ - "│ -│ line 1 -│ line 2 -│ line 3 -", -] -`; - -exports[`log (isCI = false) > message > renders multiline message with guide disabled 1`] = ` -[ - " -line 1 -line 2 -line 3 -", -] -`; - -exports[`log (isCI = false) > step > renders step message 1`] = ` -[ - "│ -◇ step message -", -] -`; - -exports[`log (isCI = false) > success > renders success message 1`] = ` -[ - "│ -◆ success message -", -] -`; - -exports[`log (isCI = false) > warn > renders warn message 1`] = ` -[ - "│ -▲ warn message -", -] -`; - -exports[`log (isCI = true) > error > renders error message 1`] = ` -[ - "│ -■ error message -", -] -`; - -exports[`log (isCI = true) > info > renders info message 1`] = ` -[ - "│ -● info message -", -] -`; - -exports[`log (isCI = true) > message > renders empty lines correctly 1`] = ` -[ - "│ -│ foo -│ -│ bar -", -] -`; - -exports[`log (isCI = true) > message > renders empty lines with guide disabled 1`] = ` -[ - " -foo - -bar -", -] -`; - -exports[`log (isCI = true) > message > renders empty message correctly 1`] = ` -[ - "│ -│ -", -] -`; - -exports[`log (isCI = true) > message > renders empty message with guide disabled 1`] = ` -[ - " - -", -] -`; - -exports[`log (isCI = true) > message > renders message 1`] = ` -[ - "│ -│ message -", -] -`; - -exports[`log (isCI = true) > message > renders message from array 1`] = ` -[ - "│ -│ line 1 -│ line 2 -│ line 3 -", -] -`; - -exports[`log (isCI = true) > message > renders message with custom spacing 1`] = ` -[ - "│ -│ -│ -│ spaced message -", -] -`; - -exports[`log (isCI = true) > message > renders message with custom symbols and spacing 1`] = ` -[ - "-- ->> custom --- symbols -", -] -`; - -exports[`log (isCI = true) > message > renders message with guide disabled 1`] = ` -[ - " -standalone message -", -] -`; - -exports[`log (isCI = true) > message > renders multiline message 1`] = ` -[ - "│ -│ line 1 -│ line 2 -│ line 3 -", -] -`; - -exports[`log (isCI = true) > message > renders multiline message with guide disabled 1`] = ` -[ - " -line 1 -line 2 -line 3 -", -] -`; - -exports[`log (isCI = true) > step > renders step message 1`] = ` -[ - "│ -◇ step message -", -] -`; - -exports[`log (isCI = true) > success > renders success message 1`] = ` -[ - "│ -◆ success message -", -] -`; - -exports[`log (isCI = true) > warn > renders warn message 1`] = ` -[ - "│ -▲ warn message -", -] -`; diff --git a/packages/prompts/test/__snapshots__/multi-line.test.ts.snap b/packages/prompts/test/__snapshots__/multi-line.test.ts.snap deleted file mode 100644 index 8bdb615f..00000000 --- a/packages/prompts/test/__snapshots__/multi-line.test.ts.snap +++ /dev/null @@ -1,831 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`multiline (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "■ foo -│ escape", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > defaultValue sets the value but does not render 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -bar", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > empty string when no value and no default 1`] = ` -[ - "", - "│ -◆ foo -│   (submit to use default) -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "", - " -█ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > placeholder is not used as value when pressing enter 1`] = ` -[ - "", - "│ -◆ foo -│   (submit to use default) -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -default-value", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "■ foo -│ xy", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > renders placeholder if set 1`] = ` -[ - "", - "│ -◆ foo -│ bar -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > renders submit button 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ - [ submit ] -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - " [ submit ]", - "", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "│ xy -│ █ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ x -│ █ -└ -", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "│ xy -│ █ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ x -│ █ -└ -", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "│ xy -│ █ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "", - " -█ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "■ foo -│ escape", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > defaultValue sets the value but does not render 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -bar", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > empty string when no value and no default 1`] = ` -[ - "", - "│ -◆ foo -│   (submit to use default) -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "", - " -█ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > placeholder is not used as value when pressing enter 1`] = ` -[ - "", - "│ -◆ foo -│   (submit to use default) -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -default-value", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "■ foo -│ xy", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > renders placeholder if set 1`] = ` -[ - "", - "│ -◆ foo -│ bar -└ -", - "", - "", - "", - "│ -│ █ -└ -", - "", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > renders submit button 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ - [ submit ] -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - " [ submit ]", - "", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "│ xy -│ █ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ x -│ █ -└ -", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "│ xy -│ █ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ x -│ █ -└ -", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "│ xy -│ █ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`multiline (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "", - " -█ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap b/packages/prompts/test/__snapshots__/multi-select.test.ts.snap deleted file mode 100644 index a2126a38..00000000 --- a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap +++ /dev/null @@ -1,1561 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`multiselect (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "■ foo -│", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > can render option hints 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 (Hint 0) -opt1 -└ -", - "", - "", - "", - "◼ opt0 (Hint 0)", - "", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > can set cursorAt to preselect an option 1`] = ` -[ - "", - "│ -◆ foo -opt0 -◻ opt1 -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > can set custom labels 1`] = ` -[ - "", - "│ -◆ foo -◻ Option 0 -Option 1 -└ -", - "", - "", - "", - "◼ Option 0", - "", - "", - "", - "", - "◇ foo -Option 0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > can set initial values 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > can submit without selection when required = false 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -none", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -◻ opt0 -opt1 - -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > maxItems renders a sliding window 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt1 -◻ opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt2 -◻ opt3 -opt4 -... -└ -", - "", - "", - "", - "... -opt2 -opt3 -◻ opt4 -opt5 -... -└ -", - "", - "", - "", - "opt3 -opt4 -◻ opt5 -opt6 -... -└ -", - "", - "", - "", - "opt4 -opt5 -◻ opt6 -opt7 -... -└ -", - "", - "", - "", - "◼ opt6", - "", - "", - "", - "", - "◇ foo -opt6", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > renders disabled options 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -◻ opt1 -◻ opt2 (Hint 2) -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > renders message 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > renders multiple cancelled values 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "■ foo -│ opt0, opt1 -│", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > renders multiple selected options 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "opt1 -◻ opt2 -└ -", - "", - "", - "", - "◇ foo -opt0, opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > renders validation errors 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "▲ foo -◻ opt0 -opt1 -Please select at least one option. - Press  space  to select,  enter  to submit -", - "", - "", - "", - "◆ foo -◼ opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > shows hints for all selected options 1`] = ` -[ - "", - "│ -◆ foo -◼ opt0 (Hint 0) -opt1 (Hint 1) -opt2 -└ -", - "", - "", - "", - "opt0 (Hint 0) -◼ opt1 (Hint 1) -opt2 -└ -", - "", - "", - "", - "opt1 (Hint 1) -◻ opt2 (Hint 2) -└ -", - "", - "", - "", - "◇ foo -opt0, opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > sliding window loops downwards 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt1 -◻ opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt2 -◻ opt3 -opt4 -... -└ -", - "", - "", - "", - "... -opt2 -opt3 -◻ opt4 -opt5 -... -└ -", - "", - "", - "", - "opt3 -opt4 -◻ opt5 -opt6 -... -└ -", - "", - "", - "", - "opt4 -opt5 -◻ opt6 -opt7 -... -└ -", - "", - "", - "", - "opt5 -opt6 -◻ opt7 -opt8 -... -└ -", - "", - "", - "", - "opt6 -opt7 -◻ opt8 -opt9 -... -└ -", - "", - "", - "", - "opt7 -opt8 -◻ opt9 -opt10 -opt11 -└ -", - "", - "", - "", - "opt9 -◻ opt10 -opt11 -└ -", - "", - "", - "", - "opt10 -◻ opt11 -└ -", - "", - "", - "", - "◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > sliding window loops upwards 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "... -opt7 -opt8 -opt9 -opt10 -◻ opt11 -└ -", - "", - "", - "", - "◼ opt11", - "", - "", - "", - "", - "◇ foo -opt11", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -◻ opt0 -opt1 - -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > wraps cancelled state with long options 1`] = ` -[ - "", - "│ -◆ foo -◻ Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 -Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 -└ -", - "", - "", - "", - "◼ Option 0 Option 0 Option ", - "", - "", - "", - "", - "■ foo -│ Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0 -│", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > wraps long messages 1`] = ` -[ - "", - "│ -◆ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = false) > wraps success state with long options 1`] = ` -[ - "", - "│ -◆ foo -◻ Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 -Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 -└ -", - "", - "", - "", - "◼ Option 0 Option 0 Option ", - "", - "", - "", - "", - "◇ foo -Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "■ foo -│", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > can render option hints 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 (Hint 0) -opt1 -└ -", - "", - "", - "", - "◼ opt0 (Hint 0)", - "", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > can set cursorAt to preselect an option 1`] = ` -[ - "", - "│ -◆ foo -opt0 -◻ opt1 -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > can set custom labels 1`] = ` -[ - "", - "│ -◆ foo -◻ Option 0 -Option 1 -└ -", - "", - "", - "", - "◼ Option 0", - "", - "", - "", - "", - "◇ foo -Option 0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > can set initial values 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > can submit without selection when required = false 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -none", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -◻ opt0 -opt1 - -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > maxItems renders a sliding window 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt1 -◻ opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt2 -◻ opt3 -opt4 -... -└ -", - "", - "", - "", - "... -opt2 -opt3 -◻ opt4 -opt5 -... -└ -", - "", - "", - "", - "opt3 -opt4 -◻ opt5 -opt6 -... -└ -", - "", - "", - "", - "opt4 -opt5 -◻ opt6 -opt7 -... -└ -", - "", - "", - "", - "◼ opt6", - "", - "", - "", - "", - "◇ foo -opt6", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > renders disabled options 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -◻ opt1 -◻ opt2 (Hint 2) -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > renders message 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > renders multiple cancelled values 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "■ foo -│ opt0, opt1 -│", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > renders multiple selected options 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -└ -", - "", - "", - "", - "◼ opt1", - "", - "", - "", - "", - "opt1 -◻ opt2 -└ -", - "", - "", - "", - "◇ foo -opt0, opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > renders validation errors 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "▲ foo -◻ opt0 -opt1 -Please select at least one option. - Press  space  to select,  enter  to submit -", - "", - "", - "", - "◆ foo -◼ opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > shows hints for all selected options 1`] = ` -[ - "", - "│ -◆ foo -◼ opt0 (Hint 0) -opt1 (Hint 1) -opt2 -└ -", - "", - "", - "", - "opt0 (Hint 0) -◼ opt1 (Hint 1) -opt2 -└ -", - "", - "", - "", - "opt1 (Hint 1) -◻ opt2 (Hint 2) -└ -", - "", - "", - "", - "◇ foo -opt0, opt1", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > sliding window loops downwards 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt0 -◻ opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt1 -◻ opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "opt2 -◻ opt3 -opt4 -... -└ -", - "", - "", - "", - "... -opt2 -opt3 -◻ opt4 -opt5 -... -└ -", - "", - "", - "", - "opt3 -opt4 -◻ opt5 -opt6 -... -└ -", - "", - "", - "", - "opt4 -opt5 -◻ opt6 -opt7 -... -└ -", - "", - "", - "", - "opt5 -opt6 -◻ opt7 -opt8 -... -└ -", - "", - "", - "", - "opt6 -opt7 -◻ opt8 -opt9 -... -└ -", - "", - "", - "", - "opt7 -opt8 -◻ opt9 -opt10 -opt11 -└ -", - "", - "", - "", - "opt9 -◻ opt10 -opt11 -└ -", - "", - "", - "", - "opt10 -◻ opt11 -└ -", - "", - "", - "", - "◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > sliding window loops upwards 1`] = ` -[ - "", - "│ -◆ foo -◻ opt0 -opt1 -opt2 -opt3 -opt4 -... -└ -", - "", - "", - "", - "... -opt7 -opt8 -opt9 -opt10 -◻ opt11 -└ -", - "", - "", - "", - "◼ opt11", - "", - "", - "", - "", - "◇ foo -opt11", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -◻ opt0 -opt1 - -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > wraps cancelled state with long options 1`] = ` -[ - "", - "│ -◆ foo -◻ Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 -Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 -└ -", - "", - "", - "", - "◼ Option 0 Option 0 Option ", - "", - "", - "", - "", - "■ foo -│ Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0 -│", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > wraps long messages 1`] = ` -[ - "", - "│ -◆ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -◻ opt0 -opt1 -└ -", - "", - "", - "", - "◼ opt0", - "", - "", - "", - "", - "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -opt0", - " -", - "", -] -`; - -exports[`multiselect (isCI = true) > wraps success state with long options 1`] = ` -[ - "", - "│ -◆ foo -◻ Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 Option 0 Option -│ 0 Option 0 -Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 Option 1 Option -1 Option 1 -└ -", - "", - "", - "", - "◼ Option 0 Option 0 Option ", - "", - "", - "", - "", - "◇ foo -Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0 Option 0 Option 0 -Option 0", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/note.test.ts.snap b/packages/prompts/test/__snapshots__/note.test.ts.snap deleted file mode 100644 index eb09ad67..00000000 --- a/packages/prompts/test/__snapshots__/note.test.ts.snap +++ /dev/null @@ -1,385 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`note (isCI = false) > don't overflow 1`] = ` -[ - "│ -◇ title ───────────────────────────────────────────────────────────────╮ -│ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -│ -├───────────────────────────────────────────────────────────────────────╯ -", -] -`; - -exports[`note (isCI = false) > don't overflow with formatter 1`] = ` -[ - "│ -◇ title ─────────────────────────────────────────────────────────────────╮ -│ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -│ -├─────────────────────────────────────────────────────────────────────────╯ -", -] -`; - -exports[`note (isCI = false) > formatter which adds colors works 1`] = ` -[ - "│ -◇ title ──╮ -│ -line 0 │ -line 1 │ -line 2 │ -│ -├──────────╯ -", -] -`; - -exports[`note (isCI = false) > formatter which adds length works 1`] = ` -[ - "│ -◇ title ──────╮ -│ -│ * line 0 * │ -│ * line 1 * │ -│ * line 2 * │ -│ -├──────────────╯ -", -] -`; - -exports[`note (isCI = false) > handle wide characters 1`] = ` -[ - "│ -◇ 这是标题 ─╮ -│ -이게 │ -│ -번째 │ - │ -줄이 │ -에요 │ -これ │ -は次 │ -の行 │ -です │ -│ -├────────────╯ -", -] -`; - -exports[`note (isCI = false) > handle wide characters with formatter 1`] = ` -[ - "│ -◇ 这是标题 ─╮ -│ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -│ -├────────────╯ -", -] -`; - -exports[`note (isCI = false) > renders as wide as longest line 1`] = ` -[ - "│ -◇ title ───────────────────────────╮ -│ -short │ -somewhat questionably long line │ -│ -├───────────────────────────────────╯ -", -] -`; - -exports[`note (isCI = false) > renders message with title 1`] = ` -[ - "│ -◇ title ───╮ -│ -message │ -│ -├───────────╯ -", -] -`; - -exports[`note (isCI = false) > without guide 1`] = ` -[ - "◇ title ───╮ -│ -message │ -│ -╰───────────╯ -", -] -`; - -exports[`note (isCI = true) > don't overflow 1`] = ` -[ - "│ -◇ title ───────────────────────────────────────────────────────────────╮ -│ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string test string │ -test string test string test string test string test string test │ -string test string test string test string test string │ -│ -├───────────────────────────────────────────────────────────────────────╯ -", -] -`; - -exports[`note (isCI = true) > don't overflow with formatter 1`] = ` -[ - "│ -◇ title ─────────────────────────────────────────────────────────────────╮ -│ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -* test string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string test string test string test string test * │ -* string test string * │ -│ -├─────────────────────────────────────────────────────────────────────────╯ -", -] -`; - -exports[`note (isCI = true) > formatter which adds colors works 1`] = ` -[ - "│ -◇ title ──╮ -│ -line 0 │ -line 1 │ -line 2 │ -│ -├──────────╯ -", -] -`; - -exports[`note (isCI = true) > formatter which adds length works 1`] = ` -[ - "│ -◇ title ──────╮ -│ -│ * line 0 * │ -│ * line 1 * │ -│ * line 2 * │ -│ -├──────────────╯ -", -] -`; - -exports[`note (isCI = true) > handle wide characters 1`] = ` -[ - "│ -◇ 这是标题 ─╮ -│ -이게 │ -│ -번째 │ - │ -줄이 │ -에요 │ -これ │ -は次 │ -の行 │ -です │ -│ -├────────────╯ -", -] -`; - -exports[`note (isCI = true) > handle wide characters with formatter 1`] = ` -[ - "│ -◇ 这是标题 ─╮ -│ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -* * │ -│ -├────────────╯ -", -] -`; - -exports[`note (isCI = true) > renders as wide as longest line 1`] = ` -[ - "│ -◇ title ───────────────────────────╮ -│ -short │ -somewhat questionably long line │ -│ -├───────────────────────────────────╯ -", -] -`; - -exports[`note (isCI = true) > renders message with title 1`] = ` -[ - "│ -◇ title ───╮ -│ -message │ -│ -├───────────╯ -", -] -`; - -exports[`note (isCI = true) > without guide 1`] = ` -[ - "◇ title ───╮ -│ -message │ -│ -╰───────────╯ -", -] -`; diff --git a/packages/prompts/test/__snapshots__/password.test.ts.snap b/packages/prompts/test/__snapshots__/password.test.ts.snap deleted file mode 100644 index 99b5a8d3..00000000 --- a/packages/prompts/test/__snapshots__/password.test.ts.snap +++ /dev/null @@ -1,463 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`password (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - " -", - "", -] -`; - -exports[`password (isCI = false) > clears input on error when clearOnError is true 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "▲ foo -│ ▪ -Error -", - "", - "", - "", - "◆ foo -│ ▪_ -└ -", - "", - "", - "", - "│ ▪▪_", - "", - "", - "", - "", - "◇ foo -▪▪", - " -", - "", -] -`; - -exports[`password (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`password (isCI = false) > renders and clears validation errors 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "▲ foo -│ ▪ -Password must be at least 2 characters -", - "", - "", - "", - "◆ foo -│ ▪▪_ -└ -", - "", - "", - "", - "◇ foo -▪▪", - " -", - "", -] -`; - -exports[`password (isCI = false) > renders cancelled value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "■ foo -│ ▪ -│", - " -", - "", -] -`; - -exports[`password (isCI = false) > renders custom mask 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ *_", - "", - "", - "", - "", - "│ **_", - "", - "", - "", - "", - "◇ foo -**", - " -", - "", -] -`; - -exports[`password (isCI = false) > renders masked value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "│ ▪▪_", - "", - "", - "", - "", - "◇ foo -▪▪", - " -", - "", -] -`; - -exports[`password (isCI = false) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "◇ foo -│ ", - " -", - "", -] -`; - -exports[`password (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`password (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - " -", - "", -] -`; - -exports[`password (isCI = true) > clears input on error when clearOnError is true 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "▲ foo -│ ▪ -Error -", - "", - "", - "", - "◆ foo -│ ▪_ -└ -", - "", - "", - "", - "│ ▪▪_", - "", - "", - "", - "", - "◇ foo -▪▪", - " -", - "", -] -`; - -exports[`password (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`password (isCI = true) > renders and clears validation errors 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "▲ foo -│ ▪ -Password must be at least 2 characters -", - "", - "", - "", - "◆ foo -│ ▪▪_ -└ -", - "", - "", - "", - "◇ foo -▪▪", - " -", - "", -] -`; - -exports[`password (isCI = true) > renders cancelled value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "■ foo -│ ▪ -│", - " -", - "", -] -`; - -exports[`password (isCI = true) > renders custom mask 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ *_", - "", - "", - "", - "", - "│ **_", - "", - "", - "", - "", - "◇ foo -**", - " -", - "", -] -`; - -exports[`password (isCI = true) > renders masked value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ ▪_", - "", - "", - "", - "", - "│ ▪▪_", - "", - "", - "", - "", - "◇ foo -▪▪", - " -", - "", -] -`; - -exports[`password (isCI = true) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "◇ foo -│ ", - " -", - "", -] -`; - -exports[`password (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/path.test.ts.snap b/packages/prompts/test/__snapshots__/path.test.ts.snap deleted file mode 100644 index e9af08d5..00000000 --- a/packages/prompts/test/__snapshots__/path.test.ts.snap +++ /dev/null @@ -1,643 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`text (isCI = false) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "■ foo -│ /tmp/", - " -", - "", -] -`; - -exports[`text (isCI = false) > cannot submit unknown value 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/_█ -No matches found -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -Search: /tmp/_█ -No matches found -Please select a path -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > initialValue sets the value 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/bar█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/x█ -No matches found -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/xy█", - "", - "", - "", - "", - "■ foo -│ /tmp/xy", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/ba█", - "", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/r█ -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -Search: /tmp/r█ -should be /tmp/bar -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -Search: /tmp/█ -/tmp/bar -/tmp/foo -/tmp/hello -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/r█ -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -Search: /tmp/r█ -should be /tmp/bar -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -Search: /tmp/█ -/tmp/bar -/tmp/foo -/tmp/hello -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "■ foo -│ /tmp/", - " -", - "", -] -`; - -exports[`text (isCI = true) > cannot submit unknown value 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/_█ -No matches found -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -Search: /tmp/_█ -No matches found -Please select a path -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > initialValue sets the value 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/bar█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/x█ -No matches found -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/xy█", - "", - "", - "", - "", - "■ foo -│ /tmp/xy", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/ba█", - "", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/r█ -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -Search: /tmp/r█ -should be /tmp/bar -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -Search: /tmp/█ -/tmp/bar -/tmp/foo -/tmp/hello -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ -Search: /tmp/█ -● /tmp/bar -/tmp/foo -/tmp/hello -/tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/r█ -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -Search: /tmp/r█ -should be /tmp/bar -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -Search: /tmp/█ -/tmp/bar -/tmp/foo -/tmp/hello -● /tmp/root.zip -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "Search: /tmp/b█ -● /tmp/bar -↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -/tmp/bar", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/progress-bar.test.ts.snap b/packages/prompts/test/__snapshots__/progress-bar.test.ts.snap deleted file mode 100644 index c1239daa..00000000 --- a/packages/prompts/test/__snapshots__/progress-bar.test.ts.snap +++ /dev/null @@ -1,586 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`prompts - progress (isCI = false) > message > sets message for next frame 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo", -] -`; - -exports[`prompts - progress (isCI = false) > process exit handling > prioritizes cancel option over global setting 1`] = ` -[ - "", - "│ -", - "■ Progress cancel message -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > process exit handling > prioritizes error option over global setting 1`] = ` -[ - "", - "│ -", - "▲ Progress error message -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > process exit handling > uses custom cancel message when provided directly 1`] = ` -[ - "", - "│ -", - "■ Custom cancel message -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > process exit handling > uses custom error message when provided directly 1`] = ` -[ - "", - "│ -", - "▲ Custom error message -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > process exit handling > uses default cancel message 1`] = ` -[ - "", - "│ -", - "■ Canceled -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > process exit handling > uses global custom cancel message from settings 1`] = ` -[ - "", - "│ -", - "■ Global cancel message -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > process exit handling > uses global custom error message from settings 1`] = ` -[ - "", - "│ -", - "▲ Global error message -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > start > renders frames at interval 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", -] -`; - -exports[`prompts - progress (isCI = false) > start > renders message 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo", -] -`; - -exports[`prompts - progress (isCI = false) > start > renders timer when indicator is "timer" 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [0s]", -] -`; - -exports[`prompts - progress (isCI = false) > stop > renders cancel symbol when calling cancel() 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "■ -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > stop > renders error symbol when calling error() 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "▲ -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > stop > renders message 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "◇ foo -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > stop > renders message when cancelling 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "■ cancelled :-( -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > stop > renders message when erroring 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "▲ FATAL ERROR! -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > stop > renders message without removing dots 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "◇ foo. -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > stop > renders submit symbol and stops progress 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", - "", - "", - "◇ -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > style > renders block progressbar 1`] = ` -[ - "", - "│ -", - "██████████ ", - "", - "", - "██████████ ", - "", - "", - "██████████ ", - "", - "", - "██████████ ", - "", - "", - "◇ -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > style > renders heavy progressbar 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━ ", - "", - "", - "━━━━━━━━━━ ", - "", - "", - "━━━━━━━━━━ ", - "", - "", - "━━━━━━━━━━ ", - "", - "", - "◇ -", - "", -] -`; - -exports[`prompts - progress (isCI = false) > style > renders light progressbar 1`] = ` -[ - "", - "│ -", - "────────── ", - "", - "", - "────────── ", - "", - "", - "────────── ", - "", - "", - "────────── ", - "", - "", - "◇ -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > message > sets message for next frame 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo...", -] -`; - -exports[`prompts - progress (isCI = true) > process exit handling > prioritizes cancel option over global setting 1`] = ` -[ - "", - "│ -", - "■ Progress cancel message -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > process exit handling > prioritizes error option over global setting 1`] = ` -[ - "", - "│ -", - "▲ Progress error message -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > process exit handling > uses custom cancel message when provided directly 1`] = ` -[ - "", - "│ -", - "■ Custom cancel message -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > process exit handling > uses custom error message when provided directly 1`] = ` -[ - "", - "│ -", - "▲ Custom error message -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > process exit handling > uses default cancel message 1`] = ` -[ - "", - "│ -", - "■ Canceled -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > process exit handling > uses global custom cancel message from settings 1`] = ` -[ - "", - "│ -", - "■ Global cancel message -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > process exit handling > uses global custom error message from settings 1`] = ` -[ - "", - "│ -", - "▲ Global error message -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > start > renders frames at interval 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", -] -`; - -exports[`prompts - progress (isCI = true) > start > renders message 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo...", -] -`; - -exports[`prompts - progress (isCI = true) > start > renders timer when indicator is "timer" 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", -] -`; - -exports[`prompts - progress (isCI = true) > stop > renders cancel symbol when calling cancel() 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "■ -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > stop > renders error symbol when calling error() 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "▲ -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > stop > renders message 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "◇ foo -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > stop > renders message when cancelling 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "■ cancelled :-( -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > stop > renders message when erroring 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "▲ FATAL ERROR! -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > stop > renders message without removing dots 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "◇ foo. -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > stop > renders submit symbol and stops progress 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > style > renders block progressbar 1`] = ` -[ - "", - "│ -", - "██████████ ...", - " -", - "", - "", - "██████████ ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > style > renders heavy progressbar 1`] = ` -[ - "", - "│ -", - "━━━━━━━━━━ ...", - " -", - "", - "", - "━━━━━━━━━━ ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`prompts - progress (isCI = true) > style > renders light progressbar 1`] = ` -[ - "", - "│ -", - "────────── ...", - " -", - "", - "", - "────────── ...", - " -", - "", - "", - "◇ -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/select-key.test.ts.snap b/packages/prompts/test/__snapshots__/select-key.test.ts.snap deleted file mode 100644 index 6922d6c6..00000000 --- a/packages/prompts/test/__snapshots__/select-key.test.ts.snap +++ /dev/null @@ -1,613 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`text (isCI = false) > can cancel by pressing escape 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "■ foo -│ Option A -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > caseSensitive: true makes input case-sensitive 1`] = ` -[ - "", - "│ -◆ foo - a Option a - A  Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = false) > caseSensitive: true makes options case-sensitive 1`] = ` -[ - "", - "│ -◆ foo - A Option A - b  Option B -└ -", - "", - "", - "", - "■ foo -│ Option A -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - a Option A - b  Option B - -", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = false) > input is case-insensitive by default 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = false) > long cancelled labels are wrapped correctly 1`] = ` -[ - "", - "│ -◆ Select an option: - a This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label -│ This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label - b  Short label -└ -", - "", - "", - "", - "■ Select an option: -│ This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > long option labels are wrapped correctly 1`] = ` -[ - "", - "│ -◆ Select an option: - a This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label -│ This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label - b  Short label -└ -", - "", - "", - "", - "", - "◇ Select an option: -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label", - " -", -] -`; - -exports[`text (isCI = false) > long submitted labels are wrapped correctly 1`] = ` -[ - "", - "│ -◆ Select an option: - a This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label -│ This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label - b  Short label -└ -", - "", - "", - "", - "", - "◇ Select an option: -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label", - " -", -] -`; - -exports[`text (isCI = false) > options are case-insensitive by default 1`] = ` -[ - "", - "│ -◆ foo - A Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = false) > renders message with options 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "◇ foo -Option A", - " -", - "", -] -`; - -exports[`text (isCI = false) > selects option by keypress 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option B", - " -", -] -`; - -exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - a Option A - b  Option B - -", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = true) > can cancel by pressing escape 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "■ foo -│ Option A -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > caseSensitive: true makes input case-sensitive 1`] = ` -[ - "", - "│ -◆ foo - a Option a - A  Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = true) > caseSensitive: true makes options case-sensitive 1`] = ` -[ - "", - "│ -◆ foo - A Option A - b  Option B -└ -", - "", - "", - "", - "■ foo -│ Option A -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - a Option A - b  Option B - -", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = true) > input is case-insensitive by default 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = true) > long cancelled labels are wrapped correctly 1`] = ` -[ - "", - "│ -◆ Select an option: - a This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label -│ This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label - b  Short label -└ -", - "", - "", - "", - "■ Select an option: -│ This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > long option labels are wrapped correctly 1`] = ` -[ - "", - "│ -◆ Select an option: - a This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label -│ This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label - b  Short label -└ -", - "", - "", - "", - "", - "◇ Select an option: -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label", - " -", -] -`; - -exports[`text (isCI = true) > long submitted labels are wrapped correctly 1`] = ` -[ - "", - "│ -◆ Select an option: - a This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label -│ This is a somewhat long -│ label This is a somewhat -│ long label This is a -│ somewhat long label This is -│ a somewhat long label This -│ is a somewhat long label - b  Short label -└ -", - "", - "", - "", - "", - "◇ Select an option: -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label -This is a somewhat long -label This is a somewhat -long label This is a -somewhat long label This is - a somewhat long label This - is a somewhat long label", - " -", -] -`; - -exports[`text (isCI = true) > options are case-insensitive by default 1`] = ` -[ - "", - "│ -◆ foo - A Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; - -exports[`text (isCI = true) > renders message with options 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "◇ foo -Option A", - " -", - "", -] -`; - -exports[`text (isCI = true) > selects option by keypress 1`] = ` -[ - "", - "│ -◆ foo - a Option A - b  Option B -└ -", - "", - "", - "", - "", - "◇ foo -Option B", - " -", -] -`; - -exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo - a Option A - b  Option B - -", - "", - "", - "", - "◇ foo -Option A", - " -", -] -`; diff --git a/packages/prompts/test/__snapshots__/select.test.ts.snap b/packages/prompts/test/__snapshots__/select.test.ts.snap deleted file mode 100644 index 636bb748..00000000 --- a/packages/prompts/test/__snapshots__/select.test.ts.snap +++ /dev/null @@ -1,961 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`select (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - " -", - "", -] -`; - -exports[`select (isCI = false) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "■ foo -│ opt0 -│", - " -", - "", -] -`; - -exports[`select (isCI = false) > correctly limits options when message wraps to multiple lines 1`] = ` -[ - "", - "│ -◆ This is a very -│ long message that -│ will wrap to -│ multiple lines -● Option 0 -Option 1 -Option 2 -... -└ -", - "", - "", - "", - "Option 0 -● Option 1 -Option 2 -... -└ -", - "", - "", - "", - "Option 1 -● Option 2 -... -└ -", - "", - "", - "", - "... -● Option 3 -Option 4 -... -└ -", - "", - "", - "", - "● Option 4 -Option 5 -... -└ -", - "", - "", - "", - "◇ This is a very -│ long message that -│ will wrap to -│ multiple lines -Option 4", - " -", - "", -] -`; - -exports[`select (isCI = false) > correctly limits options with explicit multiline message 1`] = ` -[ - "", - "│ -◆ Choose an option: -│ Line 2 of the message -│ Line 3 of the message -● Option 0 -Option 1 -Option 2 -Option 3 -... -└ -", - "", - "", - "", - "Option 0 -● Option 1 -Option 2 -Option 3 -... -└ -", - "", - "", - "", - "Option 1 -● Option 2 -Option 3 -... -└ -", - "", - "", - "", - "... -Option 2 -● Option 3 -Option 4 -... -└ -", - "", - "", - "", - "◇ Choose an option: -│ Line 2 of the message -│ Line 3 of the message -Option 3", - " -", - "", -] -`; - -exports[`select (isCI = false) > down arrow selects next option 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "opt0 -● opt1 -└ -", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`select (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● opt0 -opt1 - -", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = false) > handles mixed size re-renders 1`] = ` -[ - "", - "│ -◆ Whatever -● Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -... -└ -", - "", - "", - "│ -◆ Whatever -... -Option 0 -Option 1 -Option 2 -● Option 3 -└ -", - "", - "", - "", - "◇ Whatever -Option 3", - " -", - "", -] -`; - -exports[`select (isCI = false) > renders disabled options 1`] = ` -[ - "", - "│ -◆ foo -Option 0 -● Option 1 -Option 2 (Hint 2) -└ -", - "", - "", - "", - "◇ foo -Option 1", - " -", - "", -] -`; - -exports[`select (isCI = false) > renders multi-line option labels 1`] = ` -[ - "", - "│ -◆ foo -● Option 0 -│ with multiple lines -Option 1 -└ -", - "", - "", - "", - "Option 0 -with multiple lines -● Option 1 -└ -", - "", - "", - "", - "◇ foo -Option 1", - " -", - "", -] -`; - -exports[`select (isCI = false) > renders option hints 1`] = ` -[ - "", - "│ -◆ foo -● opt0 (Hint 0) -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = false) > renders option labels 1`] = ` -[ - "", - "│ -◆ foo -● Option 0 -Option 1 -└ -", - "", - "", - "", - "◇ foo -Option 0", - " -", - "", -] -`; - -exports[`select (isCI = false) > renders options and message 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = false) > up arrow selects previous option 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "opt0 -● opt1 -└ -", - "", - "", - "", - "● opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● opt0 -opt1 - -", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = false) > wraps long cancelled message 1`] = ` -[ - "", - "│ -◆ foo -● foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo -Option 1 -└ -", - "", - "", - "", - "■ foo -│ foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo -│", - " -", - "", -] -`; - -exports[`select (isCI = false) > wraps long messages 1`] = ` -[ - "", - "│ -◆ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -● opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = false) > wraps long results 1`] = ` -[ - "", - "│ -◆ foo -● foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo -Option 1 -└ -", - "", - "", - "", - "◇ foo -foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo", - " -", - "", -] -`; - -exports[`select (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - " -", - "", -] -`; - -exports[`select (isCI = true) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "■ foo -│ opt0 -│", - " -", - "", -] -`; - -exports[`select (isCI = true) > correctly limits options when message wraps to multiple lines 1`] = ` -[ - "", - "│ -◆ This is a very -│ long message that -│ will wrap to -│ multiple lines -● Option 0 -Option 1 -Option 2 -... -└ -", - "", - "", - "", - "Option 0 -● Option 1 -Option 2 -... -└ -", - "", - "", - "", - "Option 1 -● Option 2 -... -└ -", - "", - "", - "", - "... -● Option 3 -Option 4 -... -└ -", - "", - "", - "", - "● Option 4 -Option 5 -... -└ -", - "", - "", - "", - "◇ This is a very -│ long message that -│ will wrap to -│ multiple lines -Option 4", - " -", - "", -] -`; - -exports[`select (isCI = true) > correctly limits options with explicit multiline message 1`] = ` -[ - "", - "│ -◆ Choose an option: -│ Line 2 of the message -│ Line 3 of the message -● Option 0 -Option 1 -Option 2 -Option 3 -... -└ -", - "", - "", - "", - "Option 0 -● Option 1 -Option 2 -Option 3 -... -└ -", - "", - "", - "", - "Option 1 -● Option 2 -Option 3 -... -└ -", - "", - "", - "", - "... -Option 2 -● Option 3 -Option 4 -... -└ -", - "", - "", - "", - "◇ Choose an option: -│ Line 2 of the message -│ Line 3 of the message -Option 3", - " -", - "", -] -`; - -exports[`select (isCI = true) > down arrow selects next option 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "opt0 -● opt1 -└ -", - "", - "", - "", - "◇ foo -opt1", - " -", - "", -] -`; - -exports[`select (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● opt0 -opt1 - -", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = true) > handles mixed size re-renders 1`] = ` -[ - "", - "│ -◆ Whatever -● Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -│ Long Option -... -└ -", - "", - "", - "│ -◆ Whatever -... -Option 0 -Option 1 -Option 2 -● Option 3 -└ -", - "", - "", - "", - "◇ Whatever -Option 3", - " -", - "", -] -`; - -exports[`select (isCI = true) > renders disabled options 1`] = ` -[ - "", - "│ -◆ foo -Option 0 -● Option 1 -Option 2 (Hint 2) -└ -", - "", - "", - "", - "◇ foo -Option 1", - " -", - "", -] -`; - -exports[`select (isCI = true) > renders multi-line option labels 1`] = ` -[ - "", - "│ -◆ foo -● Option 0 -│ with multiple lines -Option 1 -└ -", - "", - "", - "", - "Option 0 -with multiple lines -● Option 1 -└ -", - "", - "", - "", - "◇ foo -Option 1", - " -", - "", -] -`; - -exports[`select (isCI = true) > renders option hints 1`] = ` -[ - "", - "│ -◆ foo -● opt0 (Hint 0) -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = true) > renders option labels 1`] = ` -[ - "", - "│ -◆ foo -● Option 0 -Option 1 -└ -", - "", - "", - "", - "◇ foo -Option 0", - " -", - "", -] -`; - -exports[`select (isCI = true) > renders options and message 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = true) > up arrow selects previous option 1`] = ` -[ - "", - "│ -◆ foo -● opt0 -opt1 -└ -", - "", - "", - "", - "opt0 -● opt1 -└ -", - "", - "", - "", - "● opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -● opt0 -opt1 - -", - "", - "", - "◇ foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = true) > wraps long cancelled message 1`] = ` -[ - "", - "│ -◆ foo -● foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo -Option 1 -└ -", - "", - "", - "", - "■ foo -│ foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo -│", - " -", - "", -] -`; - -exports[`select (isCI = true) > wraps long messages 1`] = ` -[ - "", - "│ -◆ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -● opt0 -opt1 -└ -", - "", - "", - "", - "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -opt0", - " -", - "", -] -`; - -exports[`select (isCI = true) > wraps long results 1`] = ` -[ - "", - "│ -◆ foo -● foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo -│ foo foo foo foo -Option 1 -└ -", - "", - "", - "", - "◇ foo -foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo foo foo foo - foo foo foo foo foo foo -foo foo foo foo", - " -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/spinner.test.ts.snap b/packages/prompts/test/__snapshots__/spinner.test.ts.snap deleted file mode 100644 index 71cd44a7..00000000 --- a/packages/prompts/test/__snapshots__/spinner.test.ts.snap +++ /dev/null @@ -1,1008 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`spinner (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -", - "■ Canceled -", - "", -] -`; - -exports[`spinner (isCI = false) > clear > stops and clears the spinner from the output 1`] = ` -[ - "", - "│ -", - "◒ Loading", - "", - "", - "", -] -`; - -exports[`spinner (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◒ foo", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > indicator customization > custom delay 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "◐ ", - "", - "", - "◓ ", - "", - "", - "◑ ", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > indicator customization > custom frame style 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "◐ ", - "", - "", - "◓ ", - "", - "", - "◑ ", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > indicator customization > custom frames 1`] = ` -[ - "", - "│ -", - "🐴 ", - "", - "", - "🦋 ", - "", - "", - "🐙 ", - "", - "", - "🐶 ", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > indicator customization > custom frames with lots of frame have consistent ellipsis display 1`] = ` -[ - "", - "│ -", - "0 ", - "", - "", - "1 ", - "", - "", - "2 ", - "", - "", - "3 ", - "", - "", - "4 ", - "", - "", - "5 ", - "", - "", - "6 ", - "", - "", - "7 ", - "", - "", - "8 .", - "", - "", - "9 .", - "", - "", - "0 .", - "", - "", - "1 .", - "", - "", - "2 .", - "", - "", - "3 .", - "", - "", - "4 .", - "", - "", - "5 .", - "", - "", - "6 ..", - "", - "", - "7 ..", - "", - "", - "8 ..", - "", - "", - "9 ..", - "", - "", - "0 ..", - "", - "", - "1 ..", - "", - "", - "2 ..", - "", - "", - "3 ..", - "", - "", - "4 ...", - "", - "", - "5 ...", - "", - "", - "6 ...", - "", - "", - "7 ...", - "", - "", - "8 ...", - "", - "", - "9 ...", - "", - "", - "0 ...", - "", - "", - "1 ...", - "", - "", - "2 ...", - "", - "", - "3 ", - "", - "", - "4 ", - "", - "", - "5 ", - "", - "", - "6 ", - "", - "", - "7 ", - "", - "", - "8 ", - "", - "", - "9 ", - "", - "", - "0 ", - "", - "", - "1 .", - "", - "", - "2 .", - "", - "", - "3 .", - "", - "", - "4 .", - "", - "", - "5 .", - "", - "", - "6 .", - "", - "", - "7 .", - "", - "", - "8 .", - "", - "", - "9 ..", - "", - "", - "0 ..", - "", - "", - "1 ..", - "", - "", - "2 ..", - "", - "", - "3 ..", - "", - "", - "4 ..", - "", - "", - "5 ..", - "", - "", - "6 ..", - "", - "", - "7 ...", - "", - "", - "8 ...", - "", - "", - "9 ...", - "", - "", - "0 ...", - "", - "", - "1 ...", - "", - "", - "2 ...", - "", - "", - "3 ...", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > message > sets message for next frame 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "◐ foo", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > process exit handling > prioritizes cancel option over global setting 1`] = ` -[ - "", - "│ -", - "■ Spinner cancel message -", - "", -] -`; - -exports[`spinner (isCI = false) > process exit handling > prioritizes error option over global setting 1`] = ` -[ - "", - "│ -", - "▲ Spinner error message -", - "", -] -`; - -exports[`spinner (isCI = false) > process exit handling > uses custom cancel message when provided directly 1`] = ` -[ - "", - "│ -", - "■ Custom cancel message -", - "", -] -`; - -exports[`spinner (isCI = false) > process exit handling > uses custom error message when provided directly 1`] = ` -[ - "", - "│ -", - "▲ Custom error message -", - "", -] -`; - -exports[`spinner (isCI = false) > process exit handling > uses default cancel message 1`] = ` -[ - "", - "│ -", - "■ Canceled -", - "", -] -`; - -exports[`spinner (isCI = false) > process exit handling > uses global custom cancel message from settings 1`] = ` -[ - "", - "│ -", - "■ Global cancel message -", - "", -] -`; - -exports[`spinner (isCI = false) > process exit handling > uses global custom error message from settings 1`] = ` -[ - "", - "│ -", - "▲ Global error message -", - "", -] -`; - -exports[`spinner (isCI = false) > start > handles multi-line messages 1`] = ` -[ - "", - "│ -", - "◒ foo -bar -baz", - "", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > start > handles wrapping 1`] = ` -[ - "", - "│ -", - "◒ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxx", - "", - "", - "", - "◇ stopped -", - "", -] -`; - -exports[`spinner (isCI = false) > start > renders frames at interval 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "◐ ", - "", - "", - "◓ ", - "", - "", - "◑ ", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > start > renders message 1`] = ` -[ - "", - "│ -", - "◒ foo", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > start > renders timer when indicator is "timer" 1`] = ` -[ - "", - "│ -", - "◒ [0s]", - "", - "", - "◇ [0s] -", - "", -] -`; - -exports[`spinner (isCI = false) > stop > renders cancel symbol when calling cancel() 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "■ -", - "", -] -`; - -exports[`spinner (isCI = false) > stop > renders error symbol when calling error() 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "▲ -", - "", -] -`; - -exports[`spinner (isCI = false) > stop > renders message 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "◇ foo -", - "", -] -`; - -exports[`spinner (isCI = false) > stop > renders message when cancelling 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "■ too dizzy — spinning cancelled -", - "", -] -`; - -exports[`spinner (isCI = false) > stop > renders message when erroring 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "▲ error: spun too fast! -", - "", -] -`; - -exports[`spinner (isCI = false) > stop > renders message without removing dots 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "◇ foo. -", - "", -] -`; - -exports[`spinner (isCI = false) > stop > renders submit symbol and stops spinner 1`] = ` -[ - "", - "│ -", - "◒ ", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◒ foo", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -", - "■ Canceled -", - "", -] -`; - -exports[`spinner (isCI = true) > clear > stops and clears the spinner from the output 1`] = ` -[ - "", - "│ -", - "◒ Loading...", - " -", - "", - "", - "", -] -`; - -exports[`spinner (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◒ foo...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > indicator customization > custom delay 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > indicator customization > custom frame style 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > indicator customization > custom frames 1`] = ` -[ - "", - "│ -", - "🐴 ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > indicator customization > custom frames with lots of frame have consistent ellipsis display 1`] = ` -[ - "", - "│ -", - "0 ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > message > sets message for next frame 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◐ foo...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > process exit handling > prioritizes cancel option over global setting 1`] = ` -[ - "", - "│ -", - "■ Spinner cancel message -", - "", -] -`; - -exports[`spinner (isCI = true) > process exit handling > prioritizes error option over global setting 1`] = ` -[ - "", - "│ -", - "▲ Spinner error message -", - "", -] -`; - -exports[`spinner (isCI = true) > process exit handling > uses custom cancel message when provided directly 1`] = ` -[ - "", - "│ -", - "■ Custom cancel message -", - "", -] -`; - -exports[`spinner (isCI = true) > process exit handling > uses custom error message when provided directly 1`] = ` -[ - "", - "│ -", - "▲ Custom error message -", - "", -] -`; - -exports[`spinner (isCI = true) > process exit handling > uses default cancel message 1`] = ` -[ - "", - "│ -", - "■ Canceled -", - "", -] -`; - -exports[`spinner (isCI = true) > process exit handling > uses global custom cancel message from settings 1`] = ` -[ - "", - "│ -", - "■ Global cancel message -", - "", -] -`; - -exports[`spinner (isCI = true) > process exit handling > uses global custom error message from settings 1`] = ` -[ - "", - "│ -", - "▲ Global error message -", - "", -] -`; - -exports[`spinner (isCI = true) > start > handles multi-line messages 1`] = ` -[ - "", - "│ -", - "◒ foo -bar -baz...", - " -", - "", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > start > handles wrapping 1`] = ` -[ - "", - "│ -", - "◒ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxx...", - " -", - "", - "", - "", - "◇ stopped -", - "", -] -`; - -exports[`spinner (isCI = true) > start > renders frames at interval 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > start > renders message 1`] = ` -[ - "", - "│ -", - "◒ foo...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > start > renders timer when indicator is "timer" 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◇ [0s] -", - "", -] -`; - -exports[`spinner (isCI = true) > stop > renders cancel symbol when calling cancel() 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "■ -", - "", -] -`; - -exports[`spinner (isCI = true) > stop > renders error symbol when calling error() 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "▲ -", - "", -] -`; - -exports[`spinner (isCI = true) > stop > renders message 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◇ foo -", - "", -] -`; - -exports[`spinner (isCI = true) > stop > renders message when cancelling 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "■ too dizzy — spinning cancelled -", - "", -] -`; - -exports[`spinner (isCI = true) > stop > renders message when erroring 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "▲ error: spun too fast! -", - "", -] -`; - -exports[`spinner (isCI = true) > stop > renders message without removing dots 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◇ foo. -", - "", -] -`; - -exports[`spinner (isCI = true) > stop > renders submit symbol and stops spinner 1`] = ` -[ - "", - "│ -", - "◒ ...", - " -", - "", - "", - "◇ -", - "", -] -`; - -exports[`spinner (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◒ foo...", - " -", - "", - "", - "◇ -", - "", -] -`; diff --git a/packages/prompts/test/__snapshots__/task-log.test.ts.snap b/packages/prompts/test/__snapshots__/task-log.test.ts.snap deleted file mode 100644 index f7f1275a..00000000 --- a/packages/prompts/test/__snapshots__/task-log.test.ts.snap +++ /dev/null @@ -1,1738 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`taskLog (isCI = false) > error > clears output if showLog = false 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "│ -■ some error! -", -] -`; - -exports[`taskLog (isCI = false) > error > renders output with message 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "│ -■ some error! -", - "│ -line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = false) > group > applies limit per group 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -", - "Group 1 -", - "Group 1 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -", - "Group 1 -", - "Group 1 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -", - "", - "Group 0 -", - "Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -", - "", - "Group 0 -", - "Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 1 -Group 1 line 2 -", - "", - "Group 0 -", - "Group 0 line 2 -Group 0 line 3 -", - "Group 1 -", - "Group 1 line 1 -Group 1 line 2 -", - "", - "Group 0 -", - "Group 0 line 2 -Group 0 line 3 -", - "Group 1 -", - "Group 1 line 2 -Group 1 line 3 -", - "", - "Group 0 -", - "Group 0 line 3 -Group 0 line 4 -", - "Group 1 -", - "Group 1 line 2 -Group 1 line 3 -", - "", - "Group 0 -", - "Group 0 line 3 -Group 0 line 4 -", - "Group 1 -", - "Group 1 line 3 -Group 1 line 4 -", -] -`; - -exports[`taskLog (isCI = false) > group > can render multiple groups of different sizes 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -Group 1 line 4 -", -] -`; - -exports[`taskLog (isCI = false) > group > can render multiple groups of equal size 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -", - "Group 1 -", - "Group 1 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -", - "Group 1 -", - "Group 1 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -Group 0 line 3 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -Group 0 line 3 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -Group 0 line 3 -Group 0 line 4 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -Group 0 line 3 -Group 0 line 4 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -Group 1 line 4 -", -] -`; - -exports[`taskLog (isCI = false) > group > handles empty groups 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "◆ Group success! -", -] -`; - -exports[`taskLog (isCI = false) > group > renders error state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "■ Group error! -", -] -`; - -exports[`taskLog (isCI = false) > group > renders group error state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "■ Group error! -", -] -`; - -exports[`taskLog (isCI = false) > group > renders group success state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "◆ Group success! -", -] -`; - -exports[`taskLog (isCI = false) > group > renders success state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "◆ Group success! -", -] -`; - -exports[`taskLog (isCI = false) > group > showLog shows all groups in order 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "Group 0 -", - "Group 0 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -", - "", - "Group 0 -", - "Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -Group 1 line 4 -", - "", - "◆ Group 0 success! -", - "Group 1 -", - "Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -Group 1 line 4 -", - "", - "◆ Group 0 success! -", - "■ Group 1 error! -", - "", - "│ -■ overall error -", - "Group 0 -", - "│ -Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "│ -Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -Group 1 line 4 -", -] -`; - -exports[`taskLog (isCI = false) > message > can write line by line 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = false) > message > can write multiple lines 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = false) > message > destructive ansi codes are stripped 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 1 -", - "", - "line 1 -line 2 bad ansi! -", - "", - "line 1 -line 2 bad ansi! -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > message > enforces limit if set 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 1 -line 2 -", -] -`; - -exports[`taskLog (isCI = false) > message > prints empty lines 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 1 -", - "", - "line 1 - -", - "", - "line 1 - -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > message > raw = true appends message text until newline 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0still line 0 -", - "", - "line 0still line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = false) > message > raw = true works when mixed with non-raw messages 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0still line 0 -", - "", - "line 0still line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = false) > message > raw = true works when started with non-raw messages 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 0 -line 1still line 1 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > error > outputs limited log with limit by default 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 1 -line 2 -", - "", - "line 2 -line 3 -", - "", - "│ -■ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > error > retainLog = false outputs full log without limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 0 -line 1 -line 2 -", - "", - "line 0 -line 1 -line 2 -line 3 -", - "", - "│ -■ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > error > retainLog = false outputs limited log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 1 -line 2 -", - "", - "line 2 -line 3 -", - "", - "│ -■ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > error > retainLog = true outputs full log 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 0 -line 1 -line 2 -", - "", - "line 0 -line 1 -line 2 -line 3 -", - "", - "│ -■ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > error > retainLog = true outputs full log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 1 -line 2 -", - "", - "line 2 -line 3 -", - "", - "│ -■ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > success > outputs limited log with limit by default 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 1 -line 2 -", - "", - "line 2 -line 3 -", - "", - "│ -◆ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > success > retainLog = false outputs full log without limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 0 -line 1 -line 2 -", - "", - "line 0 -line 1 -line 2 -line 3 -", - "", - "│ -◆ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > success > retainLog = false outputs limited log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 1 -line 2 -", - "", - "line 2 -line 3 -", - "", - "│ -◆ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > success > retainLog = true outputs full log 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 0 -line 1 -line 2 -", - "", - "line 0 -line 1 -line 2 -line 3 -", - "", - "│ -◆ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > retainLog > success > retainLog = true outputs full log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "line 1 -line 2 -", - "", - "line 2 -line 3 -", - "", - "│ -◆ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = false) > success > clears output and renders message 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "│ -◆ success! -", -] -`; - -exports[`taskLog (isCI = false) > success > renders output if showLog = true 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "line 0 -", - "", - "line 0 -line 1 -", - "", - "│ -◆ success! -", - "│ -line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = false) > writes message header 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", -] -`; - -exports[`taskLog (isCI = true) > error > clears output if showLog = false 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "│ -■ some error! -", -] -`; - -exports[`taskLog (isCI = true) > error > renders output with message 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "│ -■ some error! -", - "│ -line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = true) > group > applies limit per group 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", - "", - "", - "", - "", - "", - "", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > group > can render multiple groups of different sizes 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", - "", - "", - "", - "", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > group > can render multiple groups of equal size 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", - "", - "", - "", - "", - "", - "", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > group > handles empty groups 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", -] -`; - -exports[`taskLog (isCI = true) > group > renders error state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", -] -`; - -exports[`taskLog (isCI = true) > group > renders group error state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", -] -`; - -exports[`taskLog (isCI = true) > group > renders group success state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", -] -`; - -exports[`taskLog (isCI = true) > group > renders success state 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", -] -`; - -exports[`taskLog (isCI = true) > group > showLog shows all groups in order 1`] = ` -[ - "│ -", - "◇ Some log -", - "│ -", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "│ -■ overall error -", - "Group 0 -", - "│ -Group 0 line 0 -Group 0 line 1 -Group 0 line 2 -", - "Group 1 -", - "│ -Group 1 line 0 -Group 1 line 1 -Group 1 line 2 -Group 1 line 3 -Group 1 line 4 -", -] -`; - -exports[`taskLog (isCI = true) > message > can write line by line 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", -] -`; - -exports[`taskLog (isCI = true) > message > can write multiple lines 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", -] -`; - -exports[`taskLog (isCI = true) > message > destructive ansi codes are stripped 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > message > enforces limit if set 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > message > prints empty lines 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > message > raw = true appends message text until newline 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > message > raw = true works when mixed with non-raw messages 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > message > raw = true works when started with non-raw messages 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", -] -`; - -exports[`taskLog (isCI = true) > retainLog > error > outputs limited log with limit by default 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -■ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > error > retainLog = false outputs full log without limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -■ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > error > retainLog = false outputs limited log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -■ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > error > retainLog = true outputs full log 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -■ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > error > retainLog = true outputs full log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -■ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > success > outputs limited log with limit by default 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -◆ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > success > retainLog = false outputs full log without limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -◆ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > success > retainLog = false outputs limited log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -◆ woo! -", - "│ -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > success > retainLog = true outputs full log 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -◆ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > retainLog > success > retainLog = true outputs full log with limit 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "", - "", - "│ -◆ woo! -", - "│ -line 0 -line 1 -line 2 -line 3 -", -] -`; - -exports[`taskLog (isCI = true) > success > clears output and renders message 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "│ -◆ success! -", -] -`; - -exports[`taskLog (isCI = true) > success > renders output if showLog = true 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", - "", - "", - "│ -◆ success! -", - "│ -line 0 -line 1 -", -] -`; - -exports[`taskLog (isCI = true) > writes message header 1`] = ` -[ - "│ -", - "◇ foo -", - "│ -", -] -`; diff --git a/packages/prompts/test/__snapshots__/text.test.ts.snap b/packages/prompts/test/__snapshots__/text.test.ts.snap deleted file mode 100644 index c50bf5b1..00000000 --- a/packages/prompts/test/__snapshots__/text.test.ts.snap +++ /dev/null @@ -1,595 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`text (isCI = false) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - " -", - "", -] -`; - -exports[`text (isCI = false) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "■ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > defaultValue sets the value but does not render 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "◇ foo -bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > empty string when no value and no default 1`] = ` -[ - "", - "│ -◆ foo -│   (hit Enter to use default) -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`text (isCI = false) > placeholder is not used as value when pressing enter 1`] = ` -[ - "", - "│ -◆ foo -│   (hit Enter to use default) -└ -", - "", - "", - "", - "◇ foo -default-value", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "■ foo -│ xy -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders placeholder if set 1`] = ` -[ - "", - "│ -◆ foo -│ bar -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`text (isCI = false) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`text (isCI = true) > can be aborted by a signal 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - " -", - "", -] -`; - -exports[`text (isCI = true) > can cancel 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "■ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > defaultValue sets the value but does not render 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "◇ foo -bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > empty string when no value and no default 1`] = ` -[ - "", - "│ -◆ foo -│   (hit Enter to use default) -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > global withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; - -exports[`text (isCI = true) > placeholder is not used as value when pressing enter 1`] = ` -[ - "", - "│ -◆ foo -│   (hit Enter to use default) -└ -", - "", - "", - "", - "◇ foo -default-value", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "■ foo -│ xy -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders placeholder if set 1`] = ` -[ - "", - "│ -◆ foo -│ bar -└ -", - "", - "", - "", - "◇ foo -│", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "│ xy█", - "", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`text (isCI = true) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "▲ foo -│ x█ -should be xy -", - "", - "", - "", - "◆ foo -│ xy█ -└ -", - "", - "", - "", - "◇ foo -xy", - " -", - "", -] -`; - -exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` -[ - "", - "◆ foo -_ - -", - "", - "", - "◇ foo -", - " -", - "", -] -`; diff --git a/packages/prompts/test/multi-line.test.ts b/packages/prompts/test/multi-line.test.ts deleted file mode 100644 index 69edc66d..00000000 --- a/packages/prompts/test/multi-line.test.ts +++ /dev/null @@ -1,272 +0,0 @@ -import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; - -describe.each(['true', 'false'])('multiline (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); - - beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - updateSettings({ withGuide: true }); - }); - - test('renders message', async () => { - const result = prompts.multiline({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - await result; - - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders placeholder if set', async () => { - const result = prompts.multiline({ - message: 'foo', - placeholder: 'bar', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(output.buffer).toMatchSnapshot(); - expect(value).toBe(''); - }); - - test('can cancel', async () => { - const result = prompts.multiline({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', 'escape', { name: 'escape' }); - - const value = await result; - - expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders cancelled value if one set', async () => { - const result = prompts.multiline({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'escape' }); - - const value = await result; - - expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders submitted value', async () => { - const result = prompts.multiline({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('defaultValue sets the value but does not render', async () => { - const result = prompts.multiline({ - message: 'foo', - defaultValue: 'bar', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('bar'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('validation errors render and clear', async () => { - const result = prompts.multiline({ - message: 'foo', - validate: (val) => (val !== 'xy' ? 'should be xy' : undefined), - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('validation errors render and clear (using Error)', async () => { - const result = prompts.multiline({ - message: 'foo', - validate: (val) => (val !== 'xy' ? new Error('should be xy') : undefined), - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('placeholder is not used as value when pressing enter', async () => { - const result = prompts.multiline({ - message: 'foo', - placeholder: ' (submit to use default)', - defaultValue: 'default-value', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('default-value'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('empty string when no value and no default', async () => { - const result = prompts.multiline({ - message: 'foo', - placeholder: ' (submit to use default)', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe(''); - expect(output.buffer).toMatchSnapshot(); - }); - - test('can be aborted by a signal', async () => { - const controller = new AbortController(); - const result = prompts.multiline({ - message: 'foo', - input, - output, - signal: controller.signal, - }); - - controller.abort(); - const value = await result; - expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); - }); - - test('withGuide: false removes guide', async () => { - const result = prompts.multiline({ - message: 'foo', - withGuide: false, - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - await result; - - expect(output.buffer).toMatchSnapshot(); - }); - - test('global withGuide: false removes guide', async () => { - updateSettings({ withGuide: false }); - - const result = prompts.multiline({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - - await result; - - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders submit button', async () => { - const result = prompts.multiline({ - message: 'foo', - input, - output, - showSubmit: true, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '\t', { name: 'tab' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); - }); -}); diff --git a/packages/prompts/test/password.test.ts b/packages/prompts/test/password.test.ts deleted file mode 100644 index 536b3b41..00000000 --- a/packages/prompts/test/password.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { updateSettings } from '@clack/core'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as prompts from '../src/index.js'; -import { MockReadable, MockWritable } from './test-utils.js'; - -describe.each(['true', 'false'])('password (isCI = %s)', (isCI) => { - let originalCI: string | undefined; - let output: MockWritable; - let input: MockReadable; - - beforeAll(() => { - originalCI = process.env.CI; - process.env.CI = isCI; - }); - - afterAll(() => { - process.env.CI = originalCI; - }); - - beforeEach(() => { - output = new MockWritable(); - input = new MockReadable(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - updateSettings({ withGuide: true }); - }); - - test('renders message', async () => { - const result = prompts.password({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - - await result; - - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders masked value', async () => { - const result = prompts.password({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders custom mask', async () => { - const result = prompts.password({ - message: 'foo', - mask: '*', - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - - await result; - - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders and clears validation errors', async () => { - const result = prompts.password({ - message: 'foo', - validate: (value) => { - if (!value || value.length < 2) { - return 'Password must be at least 2 characters'; - } - - return undefined; - }, - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('xy'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('renders cancelled value', async () => { - const result = prompts.password({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'escape' }); - - const value = await result; - - expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); - }); - - test('can be aborted by a signal', async () => { - const controller = new AbortController(); - const result = prompts.password({ - message: 'foo', - input, - output, - signal: controller.signal, - }); - - controller.abort(); - const value = await result; - expect(prompts.isCancel(value)).toBe(true); - expect(output.buffer).toMatchSnapshot(); - }); - - test('clears input on error when clearOnError is true', async () => { - const result = prompts.password({ - message: 'foo', - input, - output, - validate: (v) => (v === 'yz' ? undefined : 'Error'), - clearOnError: true, - }); - - input.emit('keypress', 'x', { name: 'x' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - - const value = await result; - - expect(value).toBe('yz'); - expect(output.buffer).toMatchSnapshot(); - }); - - test('withGuide: false removes guide', async () => { - const result = prompts.password({ - message: 'foo', - withGuide: false, - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - - await result; - - expect(output.buffer).toMatchSnapshot(); - }); - - test('global withGuide: false removes guide', async () => { - updateSettings({ withGuide: false }); - - const result = prompts.password({ - message: 'foo', - input, - output, - }); - - input.emit('keypress', '', { name: 'return' }); - - await result; - - expect(output.buffer).toMatchSnapshot(); - }); -}); diff --git a/packages/prompts/test/test-utils.ts b/packages/prompts/test/test-utils.ts deleted file mode 100644 index 414ce247..00000000 --- a/packages/prompts/test/test-utils.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Readable, Writable } from 'node:stream'; - -export class MockWritable extends Writable { - public buffer: string[] = []; - public isTTY = false; - public columns = 80; - public rows = 20; - - _write( - chunk: any, - _encoding: BufferEncoding, - callback: (error?: Error | null | undefined) => void - ): void { - this.buffer.push(chunk.toString()); - callback(); - } -} - -export class MockReadable extends Readable { - protected _buffer: unknown[] | null = []; - - _read() { - if (this._buffer === null) { - this.push(null); - return; - } - - for (const val of this._buffer) { - this.push(val); - } - - this._buffer = []; - } - - pushValue(val: unknown): void { - this._buffer?.push(val); - } - - close(): void { - this._buffer = null; - } -} From bb03123b5ce7fd6e44e0f8461daee32ae134086b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 2 Apr 2026 19:18:00 -0400 Subject: [PATCH 3/3] test(prompts): update snapshots --- .../__snapshots__/autocomplete.test.ts.snap | 895 +++++++++ .../src/__snapshots__/box.test.ts.snap | 551 ++++++ .../src/__snapshots__/confirm.test.ts.snap | 481 +++++ .../src/__snapshots__/date.test.ts.snap | 263 +++ .../group-multi-select.test.ts.snap | 1205 ++++++++++++ .../src/__snapshots__/log.test.ts.snap | 283 +++ .../src/__snapshots__/multi-line.test.ts.snap | 831 ++++++++ .../__snapshots__/multi-select.test.ts.snap | 1561 +++++++++++++++ .../src/__snapshots__/note.test.ts.snap | 385 ++++ .../src/__snapshots__/password.test.ts.snap | 463 +++++ .../src/__snapshots__/path.test.ts.snap | 643 ++++++ .../__snapshots__/progress-bar.test.ts.snap | 586 ++++++ .../src/__snapshots__/select-key.test.ts.snap | 613 ++++++ .../src/__snapshots__/select.test.ts.snap | 961 +++++++++ .../src/__snapshots__/spinner.test.ts.snap | 1008 ++++++++++ .../src/__snapshots__/task-log.test.ts.snap | 1738 +++++++++++++++++ .../src/__snapshots__/text.test.ts.snap | 595 ++++++ 17 files changed, 13062 insertions(+) create mode 100644 packages/prompts/src/__snapshots__/autocomplete.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/box.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/confirm.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/date.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/group-multi-select.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/log.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/multi-line.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/multi-select.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/note.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/password.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/path.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/progress-bar.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/select-key.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/select.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/spinner.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/task-log.test.ts.snap create mode 100644 packages/prompts/src/__snapshots__/text.test.ts.snap diff --git a/packages/prompts/src/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/src/__snapshots__/autocomplete.test.ts.snap new file mode 100644 index 00000000..efb12990 --- /dev/null +++ b/packages/prompts/src/__snapshots__/autocomplete.test.ts.snap @@ -0,0 +1,895 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`autocomplete > autocompleteMultiselect respects global withGuide: false 1`] = ` +[ + "", + "◆ Select fruits + +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +", + "", + "", + "", + "Search: +Apple +◻ Banana +Cherry +Grape +Orange +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +", + "", + "", + "", + "◼ Banana", + "", + "", + "", + "◇ Select fruits +1 items selected", + " +", + "", +] +`; + +exports[`autocomplete > autocompleteMultiselect respects withGuide: false 1`] = ` +[ + "", + "◆ Select fruits + +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +", + "", + "", + "", + "Search: +Apple +◻ Banana +Cherry +Grape +Orange +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +", + "", + "", + "", + "◼ Banana", + "", + "", + "", + "◇ Select fruits +1 items selected", + " +", + "", +] +`; + +exports[`autocomplete > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + " +", + "", +] +`; + +exports[`autocomplete > cannot select disabled options when only one left 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: k█ (1 match) +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +│", + " +", + "", +] +`; + +exports[`autocomplete > displays disabled options correctly 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: +Apple +● Banana +Cherry +Grape +Orange +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Banana +● Cherry +Grape +Orange +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Cherry +● Grape +Orange +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Grape +● Orange +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "● Apple +Banana +Cherry +Grape +Orange +○ Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Apple", + " +", + "", +] +`; + +exports[`autocomplete > limits displayed options when maxItems is set 1`] = ` +[ + "", + "│ +◆ Select an option +│ +Search: _ +● Option 0 +Option 1 +Option 2 +Option 3 +Option 4 +... +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select an option +Option 0", + " +", + "", +] +`; + +exports[`autocomplete > placeholder is shown if set 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: Type to search... +● Apple +Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: g█ (2 matches) +● Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Grape", + " +", + "", +] +`; + +exports[`autocomplete > renders bottom ellipsis when items do not fit 1`] = ` +[ + "", + "│ +◆ Select an option +│ +Search: _ +● Line 0 +│ Line 1 +│ Line 2 +│ Line 3 +... +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "◇ Select an option +Line 0 +Line 1 +Line 2 +Line 3", + " +", + "", +] +`; + +exports[`autocomplete > renders initial UI with message and instructions 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Apple", + " +", + "", +] +`; + +exports[`autocomplete > renders placeholder if set 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: Type to search... +● Apple +Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Apple", + " +", + "", +] +`; + +exports[`autocomplete > renders top ellipsis when scrolled down and its do not fit 1`] = ` +[ + "", + "│ +◆ Select an option +│ +Search: _ +... +● Option 2 +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "│ +◇ Select an option +Option 2", + " +", + "", +] +`; + +exports[`autocomplete > shows hint when option has hint and is focused 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: +Apple +● Banana +Cherry +Grape +Orange +Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Banana +● Cherry +Grape +Orange +Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Cherry +● Grape +Orange +Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Grape +● Orange +Kiwi +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Orange +● Kiwi (New Zealand) +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Kiwi", + " +", + "", +] +`; + +exports[`autocomplete > shows no matches message when search has no results 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: z█ (0 matches) +No matches found +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +│", + " +", + "", +] +`; + +exports[`autocomplete > shows selected value in submit state 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: +Apple +● Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Banana", + " +", + "", +] +`; + +exports[`autocomplete > shows strikethrough in cancel state 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "■ Select a fruit +│", + " +", + "", +] +`; + +exports[`autocomplete > supports initialValue 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +Apple +Banana +● Cherry +Grape +Orange +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Cherry", + " +", + "", +] +`; + +exports[`autocomplete with custom filter > falls back to default filter when not provided 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: a█ (2 matches) +● Apple +Banana +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Apple", + " +", + "", +] +`; + +exports[`autocomplete with custom filter > uses custom filter function when provided 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +● Apple +Banana +Cherry +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: a█ (1 match) +● Apple +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +Apple", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > can use navigation keys to select options 1`] = ` +[ + "", + "│ +◆ Select fruits +│ +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: +Apple +◻ Banana +Cherry +Grape +Orange +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◼ Banana", + "", + "", + "", + "", + "Banana +◻ Cherry +Grape +Orange +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◼ Cherry", + "", + "", + "", + "", + "◇ Select fruits +2 items selected", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > cannot select disabled options when only one left 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +◻ Kiwi +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: k█ (1 match) +◻ Kiwi +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +0 items selected", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > displays disabled options correctly 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +◻ Kiwi +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: +Apple +◻ Banana +Cherry +Grape +Orange +◻ Kiwi +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Banana +◻ Cherry +Grape +Orange +◻ Kiwi +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Cherry +◻ Grape +Orange +◻ Kiwi +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Grape +◻ Orange +◻ Kiwi +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◻ Apple +Banana +Cherry +Grape +Orange +◻ Kiwi +↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◼ Apple", + "", + "", + "", + "", + "◇ Select a fruit +1 items selected", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > renders error when empty selection & required is true 1`] = ` +[ + "", + "│ +◆ Select a fruit +│ +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ Select a fruit +│ +Search: _ +Please select at least one item +◻ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ Select a fruit +│ +Search: _ +◼ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +1 items selected", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > supports custom filter function 1`] = ` +[ + "", + "│ +◆ Select fruits +│ +Search: _ +◻ Apple +Banana +Cherry +Grape +Orange +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: a█ (1 match) +◻ Apple +↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◼ Apple", + "", + "", + "", + "", + "◇ Select fruits +1 items selected", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/box.test.ts.snap b/packages/prompts/src/__snapshots__/box.test.ts.snap new file mode 100644 index 00000000..cb745226 --- /dev/null +++ b/packages/prompts/src/__snapshots__/box.test.ts.snap @@ -0,0 +1,551 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`box (isCI = false) > cannot have width larger than 100% 1`] = ` +[ + "│ ┌─title──────────────────────────────────────────────────────────────────────┐ +", + "│ │ message │ +", + "│ └────────────────────────────────────────────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders as specified width 1`] = ` +[ + "│ ┌─title──────────────────────────────┐ +", + "│ │ short │ +", + "│ │ somewhat questionably long line │ +", + "│ └────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders as wide as longest line with width: auto 1`] = ` +[ + "│ ┌─title──────────────────────────────┐ +", + "│ │ short │ +", + "│ │ somewhat questionably long line │ +", + "│ └────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders auto width with content longer than title 1`] = ` +[ + "│ ┌─title──────────────────────────┐ +", + "│ │ messagemessagemessagemessage │ +", + "│ └────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders auto width with title longer than content 1`] = ` +[ + "│ ┌─titletitletitletitle─┐ +", + "│ │ message │ +", + "│ └──────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders center aligned content 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders center aligned title 1`] = ` +[ + "│ ┌───title────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders left aligned content 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders left aligned title 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders message 1`] = ` +[ + "│ ┌────────────────────────────────────────────────────────────────────────────┐ +", + "│ │ message │ +", + "│ └────────────────────────────────────────────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders message with title 1`] = ` +[ + "│ ┌─some title─────────────────────────────────────────────────────────────────┐ +", + "│ │ message │ +", + "│ └────────────────────────────────────────────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders right aligned content 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders right aligned title 1`] = ` +[ + "│ ┌──────title─┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders rounded corners when rounded is true 1`] = ` +[ + "│ ╭─title──────╮ +", + "│ │ message │ +", + "│ ╰────────────╯ +", +] +`; + +exports[`box (isCI = false) > renders specified contentPadding 1`] = ` +[ + "│ ┌─title──────────────┐ +", + "│ │ message │ +", + "│ └────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders specified titlePadding 1`] = ` +[ + "│ ┌──────title───────┐ +", + "│ │ message │ +", + "│ └──────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders truncated long titles 1`] = ` +[ + "│ ┌─foofoof...─┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders wide characters with auto width 1`] = ` +[ + "│ ┌─这是标题─────────────────┐ +", + "│ │ 이게 첫 번째 줄이에요 │ +", + "│ │ これは次の行です │ +", + "│ └──────────────────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders wide characters with specified width 1`] = ` +[ + "│ ┌─这是标题───┐ +", + "│ │ 이게 첫 │ +", + "│ │ 번째 │ +", + "│ │ 줄이에요 │ +", + "│ │ これは次 │ +", + "│ │ の行です │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders with formatBorder formatting 1`] = ` +[ + "│ ─title┐ +", + "│ │ message │ +", + "│ ┘ +", +] +`; + +exports[`box (isCI = false) > renders without guide when global withGuide is false 1`] = ` +[ + "┌─title──────┐ +", + "│ message │ +", + "└────────────┘ +", +] +`; + +exports[`box (isCI = false) > renders without guide when withGuide is false 1`] = ` +[ + "┌─title──────┐ +", + "│ message │ +", + "└────────────┘ +", +] +`; + +exports[`box (isCI = false) > wraps content to fit within specified width 1`] = ` +[ + "│ ┌─title──────────────────────────────┐ +", + "│ │ foo barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo bar │ +", + "│ └────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > cannot have width larger than 100% 1`] = ` +[ + "│ ┌─title──────────────────────────────────────────────────────────────────────┐ +", + "│ │ message │ +", + "│ └────────────────────────────────────────────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders as specified width 1`] = ` +[ + "│ ┌─title──────────────────────────────┐ +", + "│ │ short │ +", + "│ │ somewhat questionably long line │ +", + "│ └────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders as wide as longest line with width: auto 1`] = ` +[ + "│ ┌─title──────────────────────────────┐ +", + "│ │ short │ +", + "│ │ somewhat questionably long line │ +", + "│ └────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders auto width with content longer than title 1`] = ` +[ + "│ ┌─title──────────────────────────┐ +", + "│ │ messagemessagemessagemessage │ +", + "│ └────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders auto width with title longer than content 1`] = ` +[ + "│ ┌─titletitletitletitle─┐ +", + "│ │ message │ +", + "│ └──────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders center aligned content 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders center aligned title 1`] = ` +[ + "│ ┌───title────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders left aligned content 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders left aligned title 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders message 1`] = ` +[ + "│ ┌────────────────────────────────────────────────────────────────────────────┐ +", + "│ │ message │ +", + "│ └────────────────────────────────────────────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders message with title 1`] = ` +[ + "│ ┌─some title─────────────────────────────────────────────────────────────────┐ +", + "│ │ message │ +", + "│ └────────────────────────────────────────────────────────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders right aligned content 1`] = ` +[ + "│ ┌─title──────┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders right aligned title 1`] = ` +[ + "│ ┌──────title─┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders rounded corners when rounded is true 1`] = ` +[ + "│ ╭─title──────╮ +", + "│ │ message │ +", + "│ ╰────────────╯ +", +] +`; + +exports[`box (isCI = true) > renders specified contentPadding 1`] = ` +[ + "│ ┌─title──────────────┐ +", + "│ │ message │ +", + "│ └────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders specified titlePadding 1`] = ` +[ + "│ ┌──────title───────┐ +", + "│ │ message │ +", + "│ └──────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders truncated long titles 1`] = ` +[ + "│ ┌─foofoof...─┐ +", + "│ │ message │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders wide characters with auto width 1`] = ` +[ + "│ ┌─这是标题─────────────────┐ +", + "│ │ 이게 첫 번째 줄이에요 │ +", + "│ │ これは次の行です │ +", + "│ └──────────────────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders wide characters with specified width 1`] = ` +[ + "│ ┌─这是标题───┐ +", + "│ │ 이게 첫 │ +", + "│ │ 번째 │ +", + "│ │ 줄이에요 │ +", + "│ │ これは次 │ +", + "│ │ の行です │ +", + "│ └────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders with formatBorder formatting 1`] = ` +[ + "│ ─title┐ +", + "│ │ message │ +", + "│ ┘ +", +] +`; + +exports[`box (isCI = true) > renders without guide when global withGuide is false 1`] = ` +[ + "┌─title──────┐ +", + "│ message │ +", + "└────────────┘ +", +] +`; + +exports[`box (isCI = true) > renders without guide when withGuide is false 1`] = ` +[ + "┌─title──────┐ +", + "│ message │ +", + "└────────────┘ +", +] +`; + +exports[`box (isCI = true) > wraps content to fit within specified width 1`] = ` +[ + "│ ┌─title──────────────────────────────┐ +", + "│ │ foo barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo barfoo │ +", + "│ │ barfoo barfoo barfoo bar │ +", + "│ └────────────────────────────────────┘ +", +] +`; diff --git a/packages/prompts/src/__snapshots__/confirm.test.ts.snap b/packages/prompts/src/__snapshots__/confirm.test.ts.snap new file mode 100644 index 00000000..d7bafcda --- /dev/null +++ b/packages/prompts/src/__snapshots__/confirm.test.ts.snap @@ -0,0 +1,481 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`confirm (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ yes? +● Yes / No +└ +", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "■ foo +│ No +│", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > can set initialValue 1`] = ` +[ + "", + "│ +◆ foo +Yes / ● No +└ +", + "", + "", + "", + "◇ foo +No", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● Yes / No + +", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > left arrow moves to previous choice 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "Yes / ● No", + "", + "", + "", + "", + "● Yes / No", + "", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > renders custom active choice 1`] = ` +[ + "", + "│ +◆ foo +● bleep / No +└ +", + "", + "", + "", + "◇ foo +bleep", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > renders custom inactive choice 1`] = ` +[ + "", + "│ +◆ foo +● Yes / bleep +└ +", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > renders message with choices 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > renders multi-line messages correctly 1`] = ` +[ + "", + "│ +◆ foo +│ bar +│ baz +● Yes / No +└ +", + "", + "", + "", + "◇ foo +│ bar +│ baz +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > renders options in vertical alignment 1`] = ` +[ + "", + "│ +◆ foo +● Yes +No +└ +", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > right arrow moves to next choice 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "Yes / ● No", + "", + "", + "", + "", + "◇ foo +No", + " +", + "", +] +`; + +exports[`confirm (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● Yes / No + +", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ yes? +● Yes / No +└ +", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "■ foo +│ No +│", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > can set initialValue 1`] = ` +[ + "", + "│ +◆ foo +Yes / ● No +└ +", + "", + "", + "", + "◇ foo +No", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● Yes / No + +", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > left arrow moves to previous choice 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "Yes / ● No", + "", + "", + "", + "", + "● Yes / No", + "", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > renders custom active choice 1`] = ` +[ + "", + "│ +◆ foo +● bleep / No +└ +", + "", + "", + "", + "◇ foo +bleep", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > renders custom inactive choice 1`] = ` +[ + "", + "│ +◆ foo +● Yes / bleep +└ +", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > renders message with choices 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > renders multi-line messages correctly 1`] = ` +[ + "", + "│ +◆ foo +│ bar +│ baz +● Yes / No +└ +", + "", + "", + "", + "◇ foo +│ bar +│ baz +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > renders options in vertical alignment 1`] = ` +[ + "", + "│ +◆ foo +● Yes +No +└ +", + "", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > right arrow moves to next choice 1`] = ` +[ + "", + "│ +◆ foo +● Yes / No +└ +", + "", + "", + "", + "Yes / ● No", + "", + "", + "", + "", + "◇ foo +No", + " +", + "", +] +`; + +exports[`confirm (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● Yes / No + +", + "", + "", + "◇ foo +Yes", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/date.test.ts.snap b/packages/prompts/src/__snapshots__/date.test.ts.snap new file mode 100644 index 00000000..559b1be9 --- /dev/null +++ b/packages/prompts/src/__snapshots__/date.test.ts.snap @@ -0,0 +1,263 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`date (isCI = false) > can cancel 1`] = ` +[ + "", + "│ +◆ Pick a date +│ mm/dd/yyyy +└ +", + "", + "", + "", + "■ Pick a date +│", + " +", + "", +] +`; + +exports[`date (isCI = false) > defaultValue used when empty submit 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 12/25/2025 +└ +", + "", + "", + "", + "◇ Pick a date +12/25/2025", + " +", + "", +] +`; + +exports[`date (isCI = false) > renders initial value 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 01/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +01/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = false) > renders message 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 01/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +01/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = false) > renders submitted value 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 06/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +06/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = false) > supports MDY format 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 01/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +01/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ Pick a date +01/15/2025 + +", + "", + "", + "◇ Pick a date + 01/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ Pick a date +│ mm/dd/yyyy +└ +", + "", + "", + "", + "■ Pick a date +│", + " +", + "", +] +`; + +exports[`date (isCI = true) > defaultValue used when empty submit 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 12/25/2025 +└ +", + "", + "", + "", + "◇ Pick a date +12/25/2025", + " +", + "", +] +`; + +exports[`date (isCI = true) > renders initial value 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 01/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +01/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = true) > renders message 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 01/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +01/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = true) > renders submitted value 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 06/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +06/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = true) > supports MDY format 1`] = ` +[ + "", + "│ +◆ Pick a date +│ 01/15/2025 +└ +", + "", + "", + "", + "◇ Pick a date +01/15/2025", + " +", + "", +] +`; + +exports[`date (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ Pick a date +01/15/2025 + +", + "", + "", + "◇ Pick a date + 01/15/2025", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/group-multi-select.test.ts.snap b/packages/prompts/src/__snapshots__/group-multi-select.test.ts.snap new file mode 100644 index 00000000..7db02072 --- /dev/null +++ b/packages/prompts/src/__snapshots__/group-multi-select.test.ts.snap @@ -0,0 +1,1205 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`groupMultiselect (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ Select a fruit +◻ group1 +│ └ group1value0 +group2 +group2value0 +└ +", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > can deselect an option 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "group1 +group1value0 +◼ group1value1 +└ +", + "", + "", + "", + "group1 +group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > can select a group 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◼ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > can select a group by selecting all members 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "group1 +group1value0 +◼ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > can select multiple options 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ │ group1value1 +│ └ group1value2 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +group1value2 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "group1value0 +◻ group1value1 +group1value2 +└ +", + "", + "", + "", + "◼ group1value1", + "", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > can submit empty selection when require = false 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > cursorAt sets initial selection 1`] = ` +[ + "", + "│ +◆ foo +group1 +group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "◼ group1value1", + "", + "", + "", + "", + "◇ foo +group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + ◻ group1 + │ group1value0 + └ group1value1 + +", + "", + "", + "", + " group1 + ◻ group1value0 + group1value1 + +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "◇ foo + group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > groupSpacing > negative spacing is ignored 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ └ group1value0 +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◼ group1value0 +group2 +group2value0 +└ +", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > groupSpacing > renders spaced groups 1`] = ` +[ + "", + "│ +◆ foo +│ +│ +◻ group1 +│ └ group1value0 +│ +│ +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +│ +│ +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◼ group1value0 +│ +│ +group2 +group2value0 +└ +", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > initial values can be set 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > renders error when nothing selected 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "▲ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +Please select at least one option. + Press  space  to select,  enter  to submit +", + "", + "", + "", + "◆ foo +group1 +◻ group1value0 +group1value1 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > renders message with options 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +group2 +group2value0 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > selectableGroups = false > cannot select groups 1`] = ` +[ + "", + "│ +◆ foo + group1 + ◻ group1value0 + group1value1 +└ +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > selectableGroups = false > selecting all members of group does not select group 1`] = ` +[ + "", + "│ +◆ foo + group1 + ◻ group1value0 + group1value1 +└ +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "", + " group1value0 + ◻ group1value1 +└ +", + "", + "", + "", + " ◼ group1value1", + "", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > values can be non-primitive 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ value0 +│ └ value1 +└ +", + "", + "", + "", + "group1 +◻ value0 +value1 +└ +", + "", + "", + "", + "◼ value0", + "", + "", + "", + "", + "◇ foo +value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + ◻ group1 + │ group1value0 + └ group1value1 + +", + "", + "", + "", + " group1 + ◻ group1value0 + group1value1 + +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "◇ foo + group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ Select a fruit +◻ group1 +│ └ group1value0 +group2 +group2value0 +└ +", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > can deselect an option 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "group1 +group1value0 +◼ group1value1 +└ +", + "", + "", + "", + "group1 +group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > can select a group 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◼ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > can select a group by selecting all members 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "group1 +group1value0 +◼ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > can select multiple options 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ │ group1value1 +│ └ group1value2 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +group1value2 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "group1value0 +◻ group1value1 +group1value2 +└ +", + "", + "", + "", + "◼ group1value1", + "", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > can submit empty selection when require = false 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > cursorAt sets initial selection 1`] = ` +[ + "", + "│ +◆ foo +group1 +group1value0 +◻ group1value1 +└ +", + "", + "", + "", + "◼ group1value1", + "", + "", + "", + "", + "◇ foo +group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + ◻ group1 + │ group1value0 + └ group1value1 + +", + "", + "", + "", + " group1 + ◻ group1value0 + group1value1 + +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "◇ foo + group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > groupSpacing > negative spacing is ignored 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ └ group1value0 +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◼ group1value0 +group2 +group2value0 +└ +", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > groupSpacing > renders spaced groups 1`] = ` +[ + "", + "│ +◆ foo +│ +│ +◻ group1 +│ └ group1value0 +│ +│ +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +│ +│ +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◼ group1value0 +│ +│ +group2 +group2value0 +└ +", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > initial values can be set 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "◇ foo +group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > renders error when nothing selected 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +└ +", + "", + "", + "", + "▲ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +Please select at least one option. + Press  space  to select,  enter  to submit +", + "", + "", + "", + "◆ foo +group1 +◻ group1value0 +group1value1 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > renders message with options 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ group1value0 +│ └ group1value1 +group2 +group2value0 +└ +", + "", + "", + "", + "group1 +◻ group1value0 +group1value1 +group2 +group2value0 +└ +", + "", + "", + "", + "◼ group1value0", + "", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > selectableGroups = false > cannot select groups 1`] = ` +[ + "", + "│ +◆ foo + group1 + ◻ group1value0 + group1value1 +└ +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "", + "◇ foo +group1value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > selectableGroups = false > selecting all members of group does not select group 1`] = ` +[ + "", + "│ +◆ foo + group1 + ◻ group1value0 + group1value1 +└ +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "", + " group1value0 + ◻ group1value1 +└ +", + "", + "", + "", + " ◼ group1value1", + "", + "", + "", + "", + "◇ foo +group1value0, group1value1", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > values can be non-primitive 1`] = ` +[ + "", + "│ +◆ foo +◻ group1 +│ │ value0 +│ └ value1 +└ +", + "", + "", + "", + "group1 +◻ value0 +value1 +└ +", + "", + "", + "", + "◼ value0", + "", + "", + "", + "", + "◇ foo +value0", + " +", + "", +] +`; + +exports[`groupMultiselect (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + ◻ group1 + │ group1value0 + └ group1value1 + +", + "", + "", + "", + " group1 + ◻ group1value0 + group1value1 + +", + "", + "", + "", + " ◼ group1value0", + "", + "", + "", + "◇ foo + group1value0", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/log.test.ts.snap b/packages/prompts/src/__snapshots__/log.test.ts.snap new file mode 100644 index 00000000..af5e5615 --- /dev/null +++ b/packages/prompts/src/__snapshots__/log.test.ts.snap @@ -0,0 +1,283 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`log (isCI = false) > error > renders error message 1`] = ` +[ + "│ +■ error message +", +] +`; + +exports[`log (isCI = false) > info > renders info message 1`] = ` +[ + "│ +● info message +", +] +`; + +exports[`log (isCI = false) > message > renders empty lines correctly 1`] = ` +[ + "│ +│ foo +│ +│ bar +", +] +`; + +exports[`log (isCI = false) > message > renders empty lines with guide disabled 1`] = ` +[ + " +foo + +bar +", +] +`; + +exports[`log (isCI = false) > message > renders empty message correctly 1`] = ` +[ + "│ +│ +", +] +`; + +exports[`log (isCI = false) > message > renders empty message with guide disabled 1`] = ` +[ + " + +", +] +`; + +exports[`log (isCI = false) > message > renders message 1`] = ` +[ + "│ +│ message +", +] +`; + +exports[`log (isCI = false) > message > renders message from array 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = false) > message > renders message with custom spacing 1`] = ` +[ + "│ +│ +│ +│ spaced message +", +] +`; + +exports[`log (isCI = false) > message > renders message with custom symbols and spacing 1`] = ` +[ + "-- +>> custom +-- symbols +", +] +`; + +exports[`log (isCI = false) > message > renders message with guide disabled 1`] = ` +[ + " +standalone message +", +] +`; + +exports[`log (isCI = false) > message > renders multiline message 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = false) > message > renders multiline message with guide disabled 1`] = ` +[ + " +line 1 +line 2 +line 3 +", +] +`; + +exports[`log (isCI = false) > step > renders step message 1`] = ` +[ + "│ +◇ step message +", +] +`; + +exports[`log (isCI = false) > success > renders success message 1`] = ` +[ + "│ +◆ success message +", +] +`; + +exports[`log (isCI = false) > warn > renders warn message 1`] = ` +[ + "│ +▲ warn message +", +] +`; + +exports[`log (isCI = true) > error > renders error message 1`] = ` +[ + "│ +■ error message +", +] +`; + +exports[`log (isCI = true) > info > renders info message 1`] = ` +[ + "│ +● info message +", +] +`; + +exports[`log (isCI = true) > message > renders empty lines correctly 1`] = ` +[ + "│ +│ foo +│ +│ bar +", +] +`; + +exports[`log (isCI = true) > message > renders empty lines with guide disabled 1`] = ` +[ + " +foo + +bar +", +] +`; + +exports[`log (isCI = true) > message > renders empty message correctly 1`] = ` +[ + "│ +│ +", +] +`; + +exports[`log (isCI = true) > message > renders empty message with guide disabled 1`] = ` +[ + " + +", +] +`; + +exports[`log (isCI = true) > message > renders message 1`] = ` +[ + "│ +│ message +", +] +`; + +exports[`log (isCI = true) > message > renders message from array 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = true) > message > renders message with custom spacing 1`] = ` +[ + "│ +│ +│ +│ spaced message +", +] +`; + +exports[`log (isCI = true) > message > renders message with custom symbols and spacing 1`] = ` +[ + "-- +>> custom +-- symbols +", +] +`; + +exports[`log (isCI = true) > message > renders message with guide disabled 1`] = ` +[ + " +standalone message +", +] +`; + +exports[`log (isCI = true) > message > renders multiline message 1`] = ` +[ + "│ +│ line 1 +│ line 2 +│ line 3 +", +] +`; + +exports[`log (isCI = true) > message > renders multiline message with guide disabled 1`] = ` +[ + " +line 1 +line 2 +line 3 +", +] +`; + +exports[`log (isCI = true) > step > renders step message 1`] = ` +[ + "│ +◇ step message +", +] +`; + +exports[`log (isCI = true) > success > renders success message 1`] = ` +[ + "│ +◆ success message +", +] +`; + +exports[`log (isCI = true) > warn > renders warn message 1`] = ` +[ + "│ +▲ warn message +", +] +`; diff --git a/packages/prompts/src/__snapshots__/multi-line.test.ts.snap b/packages/prompts/src/__snapshots__/multi-line.test.ts.snap new file mode 100644 index 00000000..8bdb615f --- /dev/null +++ b/packages/prompts/src/__snapshots__/multi-line.test.ts.snap @@ -0,0 +1,831 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`multiline (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "■ foo +│ escape", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > defaultValue sets the value but does not render 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +bar", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > empty string when no value and no default 1`] = ` +[ + "", + "│ +◆ foo +│   (submit to use default) +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "", + " +█ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > placeholder is not used as value when pressing enter 1`] = ` +[ + "", + "│ +◆ foo +│   (submit to use default) +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +default-value", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "■ foo +│ xy", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > renders placeholder if set 1`] = ` +[ + "", + "│ +◆ foo +│ bar +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > renders submit button 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ + [ submit ] +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + " [ submit ]", + "", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "│ xy +│ █ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ x +│ █ +└ +", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "│ xy +│ █ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ x +│ █ +└ +", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "│ xy +│ █ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "", + " +█ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "■ foo +│ escape", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > defaultValue sets the value but does not render 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +bar", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > empty string when no value and no default 1`] = ` +[ + "", + "│ +◆ foo +│   (submit to use default) +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "", + " +█ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > placeholder is not used as value when pressing enter 1`] = ` +[ + "", + "│ +◆ foo +│   (submit to use default) +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +default-value", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "■ foo +│ xy", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > renders placeholder if set 1`] = ` +[ + "", + "│ +◆ foo +│ bar +└ +", + "", + "", + "", + "│ +│ █ +└ +", + "", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > renders submit button 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ + [ submit ] +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + " [ submit ]", + "", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "│ xy +│ █ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ x +│ █ +└ +", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "│ xy +│ █ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ x +│ █ +└ +", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "│ xy +│ █ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`multiline (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "", + " +█ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/multi-select.test.ts.snap b/packages/prompts/src/__snapshots__/multi-select.test.ts.snap new file mode 100644 index 00000000..a2126a38 --- /dev/null +++ b/packages/prompts/src/__snapshots__/multi-select.test.ts.snap @@ -0,0 +1,1561 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`multiselect (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "■ foo +│", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > can render option hints 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 (Hint 0) +opt1 +└ +", + "", + "", + "", + "◼ opt0 (Hint 0)", + "", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > can set cursorAt to preselect an option 1`] = ` +[ + "", + "│ +◆ foo +opt0 +◻ opt1 +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > can set custom labels 1`] = ` +[ + "", + "│ +◆ foo +◻ Option 0 +Option 1 +└ +", + "", + "", + "", + "◼ Option 0", + "", + "", + "", + "", + "◇ foo +Option 0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > can set initial values 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > can submit without selection when required = false 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +none", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +◻ opt0 +opt1 + +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > maxItems renders a sliding window 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt1 +◻ opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt2 +◻ opt3 +opt4 +... +└ +", + "", + "", + "", + "... +opt2 +opt3 +◻ opt4 +opt5 +... +└ +", + "", + "", + "", + "opt3 +opt4 +◻ opt5 +opt6 +... +└ +", + "", + "", + "", + "opt4 +opt5 +◻ opt6 +opt7 +... +└ +", + "", + "", + "", + "◼ opt6", + "", + "", + "", + "", + "◇ foo +opt6", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > renders disabled options 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +◻ opt1 +◻ opt2 (Hint 2) +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > renders message 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > renders multiple cancelled values 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "■ foo +│ opt0, opt1 +│", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > renders multiple selected options 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "opt1 +◻ opt2 +└ +", + "", + "", + "", + "◇ foo +opt0, opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > renders validation errors 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "▲ foo +◻ opt0 +opt1 +Please select at least one option. + Press  space  to select,  enter  to submit +", + "", + "", + "", + "◆ foo +◼ opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > shows hints for all selected options 1`] = ` +[ + "", + "│ +◆ foo +◼ opt0 (Hint 0) +opt1 (Hint 1) +opt2 +└ +", + "", + "", + "", + "opt0 (Hint 0) +◼ opt1 (Hint 1) +opt2 +└ +", + "", + "", + "", + "opt1 (Hint 1) +◻ opt2 (Hint 2) +└ +", + "", + "", + "", + "◇ foo +opt0, opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > sliding window loops downwards 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt1 +◻ opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt2 +◻ opt3 +opt4 +... +└ +", + "", + "", + "", + "... +opt2 +opt3 +◻ opt4 +opt5 +... +└ +", + "", + "", + "", + "opt3 +opt4 +◻ opt5 +opt6 +... +└ +", + "", + "", + "", + "opt4 +opt5 +◻ opt6 +opt7 +... +└ +", + "", + "", + "", + "opt5 +opt6 +◻ opt7 +opt8 +... +└ +", + "", + "", + "", + "opt6 +opt7 +◻ opt8 +opt9 +... +└ +", + "", + "", + "", + "opt7 +opt8 +◻ opt9 +opt10 +opt11 +└ +", + "", + "", + "", + "opt9 +◻ opt10 +opt11 +└ +", + "", + "", + "", + "opt10 +◻ opt11 +└ +", + "", + "", + "", + "◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > sliding window loops upwards 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "... +opt7 +opt8 +opt9 +opt10 +◻ opt11 +└ +", + "", + "", + "", + "◼ opt11", + "", + "", + "", + "", + "◇ foo +opt11", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +◻ opt0 +opt1 + +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > wraps cancelled state with long options 1`] = ` +[ + "", + "│ +◆ foo +◻ Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 +Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 +└ +", + "", + "", + "", + "◼ Option 0 Option 0 Option ", + "", + "", + "", + "", + "■ foo +│ Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0 +│", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > wraps long messages 1`] = ` +[ + "", + "│ +◆ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "◇ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = false) > wraps success state with long options 1`] = ` +[ + "", + "│ +◆ foo +◻ Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 +Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 +└ +", + "", + "", + "", + "◼ Option 0 Option 0 Option ", + "", + "", + "", + "", + "◇ foo +Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "■ foo +│", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > can render option hints 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 (Hint 0) +opt1 +└ +", + "", + "", + "", + "◼ opt0 (Hint 0)", + "", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > can set cursorAt to preselect an option 1`] = ` +[ + "", + "│ +◆ foo +opt0 +◻ opt1 +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > can set custom labels 1`] = ` +[ + "", + "│ +◆ foo +◻ Option 0 +Option 1 +└ +", + "", + "", + "", + "◼ Option 0", + "", + "", + "", + "", + "◇ foo +Option 0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > can set initial values 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > can submit without selection when required = false 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +none", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +◻ opt0 +opt1 + +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > maxItems renders a sliding window 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt1 +◻ opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt2 +◻ opt3 +opt4 +... +└ +", + "", + "", + "", + "... +opt2 +opt3 +◻ opt4 +opt5 +... +└ +", + "", + "", + "", + "opt3 +opt4 +◻ opt5 +opt6 +... +└ +", + "", + "", + "", + "opt4 +opt5 +◻ opt6 +opt7 +... +└ +", + "", + "", + "", + "◼ opt6", + "", + "", + "", + "", + "◇ foo +opt6", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > renders disabled options 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +◻ opt1 +◻ opt2 (Hint 2) +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > renders message 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > renders multiple cancelled values 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "■ foo +│ opt0, opt1 +│", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > renders multiple selected options 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +└ +", + "", + "", + "", + "◼ opt1", + "", + "", + "", + "", + "opt1 +◻ opt2 +└ +", + "", + "", + "", + "◇ foo +opt0, opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > renders validation errors 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "▲ foo +◻ opt0 +opt1 +Please select at least one option. + Press  space  to select,  enter  to submit +", + "", + "", + "", + "◆ foo +◼ opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > shows hints for all selected options 1`] = ` +[ + "", + "│ +◆ foo +◼ opt0 (Hint 0) +opt1 (Hint 1) +opt2 +└ +", + "", + "", + "", + "opt0 (Hint 0) +◼ opt1 (Hint 1) +opt2 +└ +", + "", + "", + "", + "opt1 (Hint 1) +◻ opt2 (Hint 2) +└ +", + "", + "", + "", + "◇ foo +opt0, opt1", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > sliding window loops downwards 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt0 +◻ opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt1 +◻ opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "opt2 +◻ opt3 +opt4 +... +└ +", + "", + "", + "", + "... +opt2 +opt3 +◻ opt4 +opt5 +... +└ +", + "", + "", + "", + "opt3 +opt4 +◻ opt5 +opt6 +... +└ +", + "", + "", + "", + "opt4 +opt5 +◻ opt6 +opt7 +... +└ +", + "", + "", + "", + "opt5 +opt6 +◻ opt7 +opt8 +... +└ +", + "", + "", + "", + "opt6 +opt7 +◻ opt8 +opt9 +... +└ +", + "", + "", + "", + "opt7 +opt8 +◻ opt9 +opt10 +opt11 +└ +", + "", + "", + "", + "opt9 +◻ opt10 +opt11 +└ +", + "", + "", + "", + "opt10 +◻ opt11 +└ +", + "", + "", + "", + "◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > sliding window loops upwards 1`] = ` +[ + "", + "│ +◆ foo +◻ opt0 +opt1 +opt2 +opt3 +opt4 +... +└ +", + "", + "", + "", + "... +opt7 +opt8 +opt9 +opt10 +◻ opt11 +└ +", + "", + "", + "", + "◼ opt11", + "", + "", + "", + "", + "◇ foo +opt11", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +◻ opt0 +opt1 + +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > wraps cancelled state with long options 1`] = ` +[ + "", + "│ +◆ foo +◻ Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 +Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 +└ +", + "", + "", + "", + "◼ Option 0 Option 0 Option ", + "", + "", + "", + "", + "■ foo +│ Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0 +│", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > wraps long messages 1`] = ` +[ + "", + "│ +◆ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +◻ opt0 +opt1 +└ +", + "", + "", + "", + "◼ opt0", + "", + "", + "", + "", + "◇ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +opt0", + " +", + "", +] +`; + +exports[`multiselect (isCI = true) > wraps success state with long options 1`] = ` +[ + "", + "│ +◆ foo +◻ Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 Option 0 Option +│ 0 Option 0 +Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 Option 1 Option +1 Option 1 +└ +", + "", + "", + "", + "◼ Option 0 Option 0 Option ", + "", + "", + "", + "", + "◇ foo +Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0 Option 0 Option 0 +Option 0", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/note.test.ts.snap b/packages/prompts/src/__snapshots__/note.test.ts.snap new file mode 100644 index 00000000..eb09ad67 --- /dev/null +++ b/packages/prompts/src/__snapshots__/note.test.ts.snap @@ -0,0 +1,385 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`note (isCI = false) > don't overflow 1`] = ` +[ + "│ +◇ title ───────────────────────────────────────────────────────────────╮ +│ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +│ +├───────────────────────────────────────────────────────────────────────╯ +", +] +`; + +exports[`note (isCI = false) > don't overflow with formatter 1`] = ` +[ + "│ +◇ title ─────────────────────────────────────────────────────────────────╮ +│ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +│ +├─────────────────────────────────────────────────────────────────────────╯ +", +] +`; + +exports[`note (isCI = false) > formatter which adds colors works 1`] = ` +[ + "│ +◇ title ──╮ +│ +line 0 │ +line 1 │ +line 2 │ +│ +├──────────╯ +", +] +`; + +exports[`note (isCI = false) > formatter which adds length works 1`] = ` +[ + "│ +◇ title ──────╮ +│ +│ * line 0 * │ +│ * line 1 * │ +│ * line 2 * │ +│ +├──────────────╯ +", +] +`; + +exports[`note (isCI = false) > handle wide characters 1`] = ` +[ + "│ +◇ 这是标题 ─╮ +│ +이게 │ +│ +번째 │ + │ +줄이 │ +에요 │ +これ │ +は次 │ +の行 │ +です │ +│ +├────────────╯ +", +] +`; + +exports[`note (isCI = false) > handle wide characters with formatter 1`] = ` +[ + "│ +◇ 这是标题 ─╮ +│ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +│ +├────────────╯ +", +] +`; + +exports[`note (isCI = false) > renders as wide as longest line 1`] = ` +[ + "│ +◇ title ───────────────────────────╮ +│ +short │ +somewhat questionably long line │ +│ +├───────────────────────────────────╯ +", +] +`; + +exports[`note (isCI = false) > renders message with title 1`] = ` +[ + "│ +◇ title ───╮ +│ +message │ +│ +├───────────╯ +", +] +`; + +exports[`note (isCI = false) > without guide 1`] = ` +[ + "◇ title ───╮ +│ +message │ +│ +╰───────────╯ +", +] +`; + +exports[`note (isCI = true) > don't overflow 1`] = ` +[ + "│ +◇ title ───────────────────────────────────────────────────────────────╮ +│ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string test string │ +test string test string test string test string test string test │ +string test string test string test string test string │ +│ +├───────────────────────────────────────────────────────────────────────╯ +", +] +`; + +exports[`note (isCI = true) > don't overflow with formatter 1`] = ` +[ + "│ +◇ title ─────────────────────────────────────────────────────────────────╮ +│ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +* test string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string test string test string test string test * │ +* string test string * │ +│ +├─────────────────────────────────────────────────────────────────────────╯ +", +] +`; + +exports[`note (isCI = true) > formatter which adds colors works 1`] = ` +[ + "│ +◇ title ──╮ +│ +line 0 │ +line 1 │ +line 2 │ +│ +├──────────╯ +", +] +`; + +exports[`note (isCI = true) > formatter which adds length works 1`] = ` +[ + "│ +◇ title ──────╮ +│ +│ * line 0 * │ +│ * line 1 * │ +│ * line 2 * │ +│ +├──────────────╯ +", +] +`; + +exports[`note (isCI = true) > handle wide characters 1`] = ` +[ + "│ +◇ 这是标题 ─╮ +│ +이게 │ +│ +번째 │ + │ +줄이 │ +에요 │ +これ │ +は次 │ +の行 │ +です │ +│ +├────────────╯ +", +] +`; + +exports[`note (isCI = true) > handle wide characters with formatter 1`] = ` +[ + "│ +◇ 这是标题 ─╮ +│ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +* * │ +│ +├────────────╯ +", +] +`; + +exports[`note (isCI = true) > renders as wide as longest line 1`] = ` +[ + "│ +◇ title ───────────────────────────╮ +│ +short │ +somewhat questionably long line │ +│ +├───────────────────────────────────╯ +", +] +`; + +exports[`note (isCI = true) > renders message with title 1`] = ` +[ + "│ +◇ title ───╮ +│ +message │ +│ +├───────────╯ +", +] +`; + +exports[`note (isCI = true) > without guide 1`] = ` +[ + "◇ title ───╮ +│ +message │ +│ +╰───────────╯ +", +] +`; diff --git a/packages/prompts/src/__snapshots__/password.test.ts.snap b/packages/prompts/src/__snapshots__/password.test.ts.snap new file mode 100644 index 00000000..99b5a8d3 --- /dev/null +++ b/packages/prompts/src/__snapshots__/password.test.ts.snap @@ -0,0 +1,463 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`password (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + " +", + "", +] +`; + +exports[`password (isCI = false) > clears input on error when clearOnError is true 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "▲ foo +│ ▪ +Error +", + "", + "", + "", + "◆ foo +│ ▪_ +└ +", + "", + "", + "", + "│ ▪▪_", + "", + "", + "", + "", + "◇ foo +▪▪", + " +", + "", +] +`; + +exports[`password (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`password (isCI = false) > renders and clears validation errors 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "▲ foo +│ ▪ +Password must be at least 2 characters +", + "", + "", + "", + "◆ foo +│ ▪▪_ +└ +", + "", + "", + "", + "◇ foo +▪▪", + " +", + "", +] +`; + +exports[`password (isCI = false) > renders cancelled value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "■ foo +│ ▪ +│", + " +", + "", +] +`; + +exports[`password (isCI = false) > renders custom mask 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ *_", + "", + "", + "", + "", + "│ **_", + "", + "", + "", + "", + "◇ foo +**", + " +", + "", +] +`; + +exports[`password (isCI = false) > renders masked value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "│ ▪▪_", + "", + "", + "", + "", + "◇ foo +▪▪", + " +", + "", +] +`; + +exports[`password (isCI = false) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "◇ foo +│ ", + " +", + "", +] +`; + +exports[`password (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`password (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + " +", + "", +] +`; + +exports[`password (isCI = true) > clears input on error when clearOnError is true 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "▲ foo +│ ▪ +Error +", + "", + "", + "", + "◆ foo +│ ▪_ +└ +", + "", + "", + "", + "│ ▪▪_", + "", + "", + "", + "", + "◇ foo +▪▪", + " +", + "", +] +`; + +exports[`password (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`password (isCI = true) > renders and clears validation errors 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "▲ foo +│ ▪ +Password must be at least 2 characters +", + "", + "", + "", + "◆ foo +│ ▪▪_ +└ +", + "", + "", + "", + "◇ foo +▪▪", + " +", + "", +] +`; + +exports[`password (isCI = true) > renders cancelled value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "■ foo +│ ▪ +│", + " +", + "", +] +`; + +exports[`password (isCI = true) > renders custom mask 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ *_", + "", + "", + "", + "", + "│ **_", + "", + "", + "", + "", + "◇ foo +**", + " +", + "", +] +`; + +exports[`password (isCI = true) > renders masked value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "│ ▪▪_", + "", + "", + "", + "", + "◇ foo +▪▪", + " +", + "", +] +`; + +exports[`password (isCI = true) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "◇ foo +│ ", + " +", + "", +] +`; + +exports[`password (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/path.test.ts.snap b/packages/prompts/src/__snapshots__/path.test.ts.snap new file mode 100644 index 00000000..e9af08d5 --- /dev/null +++ b/packages/prompts/src/__snapshots__/path.test.ts.snap @@ -0,0 +1,643 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`text (isCI = false) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "■ foo +│ /tmp/", + " +", + "", +] +`; + +exports[`text (isCI = false) > cannot submit unknown value 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/_█ +No matches found +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +Search: /tmp/_█ +No matches found +Please select a path +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > initialValue sets the value 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/bar█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/x█ +No matches found +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/xy█", + "", + "", + "", + "", + "■ foo +│ /tmp/xy", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/ba█", + "", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/r█ +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +Search: /tmp/r█ +should be /tmp/bar +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +Search: /tmp/█ +/tmp/bar +/tmp/foo +/tmp/hello +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/r█ +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +Search: /tmp/r█ +should be /tmp/bar +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +Search: /tmp/█ +/tmp/bar +/tmp/foo +/tmp/hello +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "■ foo +│ /tmp/", + " +", + "", +] +`; + +exports[`text (isCI = true) > cannot submit unknown value 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/_█ +No matches found +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +Search: /tmp/_█ +No matches found +Please select a path +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > initialValue sets the value 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/bar█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/x█ +No matches found +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/xy█", + "", + "", + "", + "", + "■ foo +│ /tmp/xy", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/ba█", + "", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/r█ +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +Search: /tmp/r█ +should be /tmp/bar +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +Search: /tmp/█ +/tmp/bar +/tmp/foo +/tmp/hello +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ +Search: /tmp/█ +● /tmp/bar +/tmp/foo +/tmp/hello +/tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/r█ +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +Search: /tmp/r█ +should be /tmp/bar +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +Search: /tmp/█ +/tmp/bar +/tmp/foo +/tmp/hello +● /tmp/root.zip +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "Search: /tmp/b█ +● /tmp/bar +↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +/tmp/bar", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/progress-bar.test.ts.snap b/packages/prompts/src/__snapshots__/progress-bar.test.ts.snap new file mode 100644 index 00000000..c1239daa --- /dev/null +++ b/packages/prompts/src/__snapshots__/progress-bar.test.ts.snap @@ -0,0 +1,586 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`prompts - progress (isCI = false) > message > sets message for next frame 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo", +] +`; + +exports[`prompts - progress (isCI = false) > process exit handling > prioritizes cancel option over global setting 1`] = ` +[ + "", + "│ +", + "■ Progress cancel message +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > process exit handling > prioritizes error option over global setting 1`] = ` +[ + "", + "│ +", + "▲ Progress error message +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > process exit handling > uses custom cancel message when provided directly 1`] = ` +[ + "", + "│ +", + "■ Custom cancel message +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > process exit handling > uses custom error message when provided directly 1`] = ` +[ + "", + "│ +", + "▲ Custom error message +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > process exit handling > uses default cancel message 1`] = ` +[ + "", + "│ +", + "■ Canceled +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > process exit handling > uses global custom cancel message from settings 1`] = ` +[ + "", + "│ +", + "■ Global cancel message +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > process exit handling > uses global custom error message from settings 1`] = ` +[ + "", + "│ +", + "▲ Global error message +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > start > renders frames at interval 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", +] +`; + +exports[`prompts - progress (isCI = false) > start > renders message 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo", +] +`; + +exports[`prompts - progress (isCI = false) > start > renders timer when indicator is "timer" 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [0s]", +] +`; + +exports[`prompts - progress (isCI = false) > stop > renders cancel symbol when calling cancel() 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "■ +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > stop > renders error symbol when calling error() 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "▲ +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > stop > renders message 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "◇ foo +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > stop > renders message when cancelling 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "■ cancelled :-( +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > stop > renders message when erroring 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "▲ FATAL ERROR! +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > stop > renders message without removing dots 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "◇ foo. +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > stop > renders submit symbol and stops progress 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ", + "", + "", + "◇ +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > style > renders block progressbar 1`] = ` +[ + "", + "│ +", + "██████████ ", + "", + "", + "██████████ ", + "", + "", + "██████████ ", + "", + "", + "██████████ ", + "", + "", + "◇ +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > style > renders heavy progressbar 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━ ", + "", + "", + "━━━━━━━━━━ ", + "", + "", + "━━━━━━━━━━ ", + "", + "", + "━━━━━━━━━━ ", + "", + "", + "◇ +", + "", +] +`; + +exports[`prompts - progress (isCI = false) > style > renders light progressbar 1`] = ` +[ + "", + "│ +", + "────────── ", + "", + "", + "────────── ", + "", + "", + "────────── ", + "", + "", + "────────── ", + "", + "", + "◇ +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > message > sets message for next frame 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo...", +] +`; + +exports[`prompts - progress (isCI = true) > process exit handling > prioritizes cancel option over global setting 1`] = ` +[ + "", + "│ +", + "■ Progress cancel message +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > process exit handling > prioritizes error option over global setting 1`] = ` +[ + "", + "│ +", + "▲ Progress error message +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > process exit handling > uses custom cancel message when provided directly 1`] = ` +[ + "", + "│ +", + "■ Custom cancel message +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > process exit handling > uses custom error message when provided directly 1`] = ` +[ + "", + "│ +", + "▲ Custom error message +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > process exit handling > uses default cancel message 1`] = ` +[ + "", + "│ +", + "■ Canceled +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > process exit handling > uses global custom cancel message from settings 1`] = ` +[ + "", + "│ +", + "■ Global cancel message +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > process exit handling > uses global custom error message from settings 1`] = ` +[ + "", + "│ +", + "▲ Global error message +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > start > renders frames at interval 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", +] +`; + +exports[`prompts - progress (isCI = true) > start > renders message 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo...", +] +`; + +exports[`prompts - progress (isCI = true) > start > renders timer when indicator is "timer" 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", +] +`; + +exports[`prompts - progress (isCI = true) > stop > renders cancel symbol when calling cancel() 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "■ +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > stop > renders error symbol when calling error() 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "▲ +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > stop > renders message 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "◇ foo +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > stop > renders message when cancelling 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "■ cancelled :-( +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > stop > renders message when erroring 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "▲ FATAL ERROR! +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > stop > renders message without removing dots 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "◇ foo. +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > stop > renders submit symbol and stops progress 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > style > renders block progressbar 1`] = ` +[ + "", + "│ +", + "██████████ ...", + " +", + "", + "", + "██████████ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > style > renders heavy progressbar 1`] = ` +[ + "", + "│ +", + "━━━━━━━━━━ ...", + " +", + "", + "", + "━━━━━━━━━━ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`prompts - progress (isCI = true) > style > renders light progressbar 1`] = ` +[ + "", + "│ +", + "────────── ...", + " +", + "", + "", + "────────── ...", + " +", + "", + "", + "◇ +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/select-key.test.ts.snap b/packages/prompts/src/__snapshots__/select-key.test.ts.snap new file mode 100644 index 00000000..6922d6c6 --- /dev/null +++ b/packages/prompts/src/__snapshots__/select-key.test.ts.snap @@ -0,0 +1,613 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`text (isCI = false) > can cancel by pressing escape 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "■ foo +│ Option A +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > caseSensitive: true makes input case-sensitive 1`] = ` +[ + "", + "│ +◆ foo + a Option a + A  Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = false) > caseSensitive: true makes options case-sensitive 1`] = ` +[ + "", + "│ +◆ foo + A Option A + b  Option B +└ +", + "", + "", + "", + "■ foo +│ Option A +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + a Option A + b  Option B + +", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = false) > input is case-insensitive by default 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = false) > long cancelled labels are wrapped correctly 1`] = ` +[ + "", + "│ +◆ Select an option: + a This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label +│ This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label + b  Short label +└ +", + "", + "", + "", + "■ Select an option: +│ This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > long option labels are wrapped correctly 1`] = ` +[ + "", + "│ +◆ Select an option: + a This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label +│ This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label + b  Short label +└ +", + "", + "", + "", + "", + "◇ Select an option: +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label", + " +", +] +`; + +exports[`text (isCI = false) > long submitted labels are wrapped correctly 1`] = ` +[ + "", + "│ +◆ Select an option: + a This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label +│ This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label + b  Short label +└ +", + "", + "", + "", + "", + "◇ Select an option: +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label", + " +", +] +`; + +exports[`text (isCI = false) > options are case-insensitive by default 1`] = ` +[ + "", + "│ +◆ foo + A Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = false) > renders message with options 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "◇ foo +Option A", + " +", + "", +] +`; + +exports[`text (isCI = false) > selects option by keypress 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option B", + " +", +] +`; + +exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + a Option A + b  Option B + +", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = true) > can cancel by pressing escape 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "■ foo +│ Option A +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > caseSensitive: true makes input case-sensitive 1`] = ` +[ + "", + "│ +◆ foo + a Option a + A  Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = true) > caseSensitive: true makes options case-sensitive 1`] = ` +[ + "", + "│ +◆ foo + A Option A + b  Option B +└ +", + "", + "", + "", + "■ foo +│ Option A +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + a Option A + b  Option B + +", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = true) > input is case-insensitive by default 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = true) > long cancelled labels are wrapped correctly 1`] = ` +[ + "", + "│ +◆ Select an option: + a This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label +│ This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label + b  Short label +└ +", + "", + "", + "", + "■ Select an option: +│ This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > long option labels are wrapped correctly 1`] = ` +[ + "", + "│ +◆ Select an option: + a This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label +│ This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label + b  Short label +└ +", + "", + "", + "", + "", + "◇ Select an option: +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label", + " +", +] +`; + +exports[`text (isCI = true) > long submitted labels are wrapped correctly 1`] = ` +[ + "", + "│ +◆ Select an option: + a This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label +│ This is a somewhat long +│ label This is a somewhat +│ long label This is a +│ somewhat long label This is +│ a somewhat long label This +│ is a somewhat long label + b  Short label +└ +", + "", + "", + "", + "", + "◇ Select an option: +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label +This is a somewhat long +label This is a somewhat +long label This is a +somewhat long label This is + a somewhat long label This + is a somewhat long label", + " +", +] +`; + +exports[`text (isCI = true) > options are case-insensitive by default 1`] = ` +[ + "", + "│ +◆ foo + A Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; + +exports[`text (isCI = true) > renders message with options 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "◇ foo +Option A", + " +", + "", +] +`; + +exports[`text (isCI = true) > selects option by keypress 1`] = ` +[ + "", + "│ +◆ foo + a Option A + b  Option B +└ +", + "", + "", + "", + "", + "◇ foo +Option B", + " +", +] +`; + +exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo + a Option A + b  Option B + +", + "", + "", + "", + "◇ foo +Option A", + " +", +] +`; diff --git a/packages/prompts/src/__snapshots__/select.test.ts.snap b/packages/prompts/src/__snapshots__/select.test.ts.snap new file mode 100644 index 00000000..636bb748 --- /dev/null +++ b/packages/prompts/src/__snapshots__/select.test.ts.snap @@ -0,0 +1,961 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`select (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + " +", + "", +] +`; + +exports[`select (isCI = false) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "■ foo +│ opt0 +│", + " +", + "", +] +`; + +exports[`select (isCI = false) > correctly limits options when message wraps to multiple lines 1`] = ` +[ + "", + "│ +◆ This is a very +│ long message that +│ will wrap to +│ multiple lines +● Option 0 +Option 1 +Option 2 +... +└ +", + "", + "", + "", + "Option 0 +● Option 1 +Option 2 +... +└ +", + "", + "", + "", + "Option 1 +● Option 2 +... +└ +", + "", + "", + "", + "... +● Option 3 +Option 4 +... +└ +", + "", + "", + "", + "● Option 4 +Option 5 +... +└ +", + "", + "", + "", + "◇ This is a very +│ long message that +│ will wrap to +│ multiple lines +Option 4", + " +", + "", +] +`; + +exports[`select (isCI = false) > correctly limits options with explicit multiline message 1`] = ` +[ + "", + "│ +◆ Choose an option: +│ Line 2 of the message +│ Line 3 of the message +● Option 0 +Option 1 +Option 2 +Option 3 +... +└ +", + "", + "", + "", + "Option 0 +● Option 1 +Option 2 +Option 3 +... +└ +", + "", + "", + "", + "Option 1 +● Option 2 +Option 3 +... +└ +", + "", + "", + "", + "... +Option 2 +● Option 3 +Option 4 +... +└ +", + "", + "", + "", + "◇ Choose an option: +│ Line 2 of the message +│ Line 3 of the message +Option 3", + " +", + "", +] +`; + +exports[`select (isCI = false) > down arrow selects next option 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "opt0 +● opt1 +└ +", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`select (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● opt0 +opt1 + +", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = false) > handles mixed size re-renders 1`] = ` +[ + "", + "│ +◆ Whatever +● Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +... +└ +", + "", + "", + "│ +◆ Whatever +... +Option 0 +Option 1 +Option 2 +● Option 3 +└ +", + "", + "", + "", + "◇ Whatever +Option 3", + " +", + "", +] +`; + +exports[`select (isCI = false) > renders disabled options 1`] = ` +[ + "", + "│ +◆ foo +Option 0 +● Option 1 +Option 2 (Hint 2) +└ +", + "", + "", + "", + "◇ foo +Option 1", + " +", + "", +] +`; + +exports[`select (isCI = false) > renders multi-line option labels 1`] = ` +[ + "", + "│ +◆ foo +● Option 0 +│ with multiple lines +Option 1 +└ +", + "", + "", + "", + "Option 0 +with multiple lines +● Option 1 +└ +", + "", + "", + "", + "◇ foo +Option 1", + " +", + "", +] +`; + +exports[`select (isCI = false) > renders option hints 1`] = ` +[ + "", + "│ +◆ foo +● opt0 (Hint 0) +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = false) > renders option labels 1`] = ` +[ + "", + "│ +◆ foo +● Option 0 +Option 1 +└ +", + "", + "", + "", + "◇ foo +Option 0", + " +", + "", +] +`; + +exports[`select (isCI = false) > renders options and message 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = false) > up arrow selects previous option 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "opt0 +● opt1 +└ +", + "", + "", + "", + "● opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● opt0 +opt1 + +", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = false) > wraps long cancelled message 1`] = ` +[ + "", + "│ +◆ foo +● foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo +Option 1 +└ +", + "", + "", + "", + "■ foo +│ foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo +│", + " +", + "", +] +`; + +exports[`select (isCI = false) > wraps long messages 1`] = ` +[ + "", + "│ +◆ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +● opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = false) > wraps long results 1`] = ` +[ + "", + "│ +◆ foo +● foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo +Option 1 +└ +", + "", + "", + "", + "◇ foo +foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo", + " +", + "", +] +`; + +exports[`select (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + " +", + "", +] +`; + +exports[`select (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "■ foo +│ opt0 +│", + " +", + "", +] +`; + +exports[`select (isCI = true) > correctly limits options when message wraps to multiple lines 1`] = ` +[ + "", + "│ +◆ This is a very +│ long message that +│ will wrap to +│ multiple lines +● Option 0 +Option 1 +Option 2 +... +└ +", + "", + "", + "", + "Option 0 +● Option 1 +Option 2 +... +└ +", + "", + "", + "", + "Option 1 +● Option 2 +... +└ +", + "", + "", + "", + "... +● Option 3 +Option 4 +... +└ +", + "", + "", + "", + "● Option 4 +Option 5 +... +└ +", + "", + "", + "", + "◇ This is a very +│ long message that +│ will wrap to +│ multiple lines +Option 4", + " +", + "", +] +`; + +exports[`select (isCI = true) > correctly limits options with explicit multiline message 1`] = ` +[ + "", + "│ +◆ Choose an option: +│ Line 2 of the message +│ Line 3 of the message +● Option 0 +Option 1 +Option 2 +Option 3 +... +└ +", + "", + "", + "", + "Option 0 +● Option 1 +Option 2 +Option 3 +... +└ +", + "", + "", + "", + "Option 1 +● Option 2 +Option 3 +... +└ +", + "", + "", + "", + "... +Option 2 +● Option 3 +Option 4 +... +└ +", + "", + "", + "", + "◇ Choose an option: +│ Line 2 of the message +│ Line 3 of the message +Option 3", + " +", + "", +] +`; + +exports[`select (isCI = true) > down arrow selects next option 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "opt0 +● opt1 +└ +", + "", + "", + "", + "◇ foo +opt1", + " +", + "", +] +`; + +exports[`select (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● opt0 +opt1 + +", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = true) > handles mixed size re-renders 1`] = ` +[ + "", + "│ +◆ Whatever +● Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +│ Long Option +... +└ +", + "", + "", + "│ +◆ Whatever +... +Option 0 +Option 1 +Option 2 +● Option 3 +└ +", + "", + "", + "", + "◇ Whatever +Option 3", + " +", + "", +] +`; + +exports[`select (isCI = true) > renders disabled options 1`] = ` +[ + "", + "│ +◆ foo +Option 0 +● Option 1 +Option 2 (Hint 2) +└ +", + "", + "", + "", + "◇ foo +Option 1", + " +", + "", +] +`; + +exports[`select (isCI = true) > renders multi-line option labels 1`] = ` +[ + "", + "│ +◆ foo +● Option 0 +│ with multiple lines +Option 1 +└ +", + "", + "", + "", + "Option 0 +with multiple lines +● Option 1 +└ +", + "", + "", + "", + "◇ foo +Option 1", + " +", + "", +] +`; + +exports[`select (isCI = true) > renders option hints 1`] = ` +[ + "", + "│ +◆ foo +● opt0 (Hint 0) +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = true) > renders option labels 1`] = ` +[ + "", + "│ +◆ foo +● Option 0 +Option 1 +└ +", + "", + "", + "", + "◇ foo +Option 0", + " +", + "", +] +`; + +exports[`select (isCI = true) > renders options and message 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = true) > up arrow selects previous option 1`] = ` +[ + "", + "│ +◆ foo +● opt0 +opt1 +└ +", + "", + "", + "", + "opt0 +● opt1 +└ +", + "", + "", + "", + "● opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +● opt0 +opt1 + +", + "", + "", + "◇ foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = true) > wraps long cancelled message 1`] = ` +[ + "", + "│ +◆ foo +● foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo +Option 1 +└ +", + "", + "", + "", + "■ foo +│ foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo +│", + " +", + "", +] +`; + +exports[`select (isCI = true) > wraps long messages 1`] = ` +[ + "", + "│ +◆ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +● opt0 +opt1 +└ +", + "", + "", + "", + "◇ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +opt0", + " +", + "", +] +`; + +exports[`select (isCI = true) > wraps long results 1`] = ` +[ + "", + "│ +◆ foo +● foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo +│ foo foo foo foo +Option 1 +└ +", + "", + "", + "", + "◇ foo +foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo foo foo foo + foo foo foo foo foo foo +foo foo foo foo", + " +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/spinner.test.ts.snap b/packages/prompts/src/__snapshots__/spinner.test.ts.snap new file mode 100644 index 00000000..71cd44a7 --- /dev/null +++ b/packages/prompts/src/__snapshots__/spinner.test.ts.snap @@ -0,0 +1,1008 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`spinner (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +", + "■ Canceled +", + "", +] +`; + +exports[`spinner (isCI = false) > clear > stops and clears the spinner from the output 1`] = ` +[ + "", + "│ +", + "◒ Loading", + "", + "", + "", +] +`; + +exports[`spinner (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◒ foo", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > indicator customization > custom delay 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◐ ", + "", + "", + "◓ ", + "", + "", + "◑ ", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > indicator customization > custom frame style 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◐ ", + "", + "", + "◓ ", + "", + "", + "◑ ", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > indicator customization > custom frames 1`] = ` +[ + "", + "│ +", + "🐴 ", + "", + "", + "🦋 ", + "", + "", + "🐙 ", + "", + "", + "🐶 ", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > indicator customization > custom frames with lots of frame have consistent ellipsis display 1`] = ` +[ + "", + "│ +", + "0 ", + "", + "", + "1 ", + "", + "", + "2 ", + "", + "", + "3 ", + "", + "", + "4 ", + "", + "", + "5 ", + "", + "", + "6 ", + "", + "", + "7 ", + "", + "", + "8 .", + "", + "", + "9 .", + "", + "", + "0 .", + "", + "", + "1 .", + "", + "", + "2 .", + "", + "", + "3 .", + "", + "", + "4 .", + "", + "", + "5 .", + "", + "", + "6 ..", + "", + "", + "7 ..", + "", + "", + "8 ..", + "", + "", + "9 ..", + "", + "", + "0 ..", + "", + "", + "1 ..", + "", + "", + "2 ..", + "", + "", + "3 ..", + "", + "", + "4 ...", + "", + "", + "5 ...", + "", + "", + "6 ...", + "", + "", + "7 ...", + "", + "", + "8 ...", + "", + "", + "9 ...", + "", + "", + "0 ...", + "", + "", + "1 ...", + "", + "", + "2 ...", + "", + "", + "3 ", + "", + "", + "4 ", + "", + "", + "5 ", + "", + "", + "6 ", + "", + "", + "7 ", + "", + "", + "8 ", + "", + "", + "9 ", + "", + "", + "0 ", + "", + "", + "1 .", + "", + "", + "2 .", + "", + "", + "3 .", + "", + "", + "4 .", + "", + "", + "5 .", + "", + "", + "6 .", + "", + "", + "7 .", + "", + "", + "8 .", + "", + "", + "9 ..", + "", + "", + "0 ..", + "", + "", + "1 ..", + "", + "", + "2 ..", + "", + "", + "3 ..", + "", + "", + "4 ..", + "", + "", + "5 ..", + "", + "", + "6 ..", + "", + "", + "7 ...", + "", + "", + "8 ...", + "", + "", + "9 ...", + "", + "", + "0 ...", + "", + "", + "1 ...", + "", + "", + "2 ...", + "", + "", + "3 ...", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > message > sets message for next frame 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◐ foo", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > process exit handling > prioritizes cancel option over global setting 1`] = ` +[ + "", + "│ +", + "■ Spinner cancel message +", + "", +] +`; + +exports[`spinner (isCI = false) > process exit handling > prioritizes error option over global setting 1`] = ` +[ + "", + "│ +", + "▲ Spinner error message +", + "", +] +`; + +exports[`spinner (isCI = false) > process exit handling > uses custom cancel message when provided directly 1`] = ` +[ + "", + "│ +", + "■ Custom cancel message +", + "", +] +`; + +exports[`spinner (isCI = false) > process exit handling > uses custom error message when provided directly 1`] = ` +[ + "", + "│ +", + "▲ Custom error message +", + "", +] +`; + +exports[`spinner (isCI = false) > process exit handling > uses default cancel message 1`] = ` +[ + "", + "│ +", + "■ Canceled +", + "", +] +`; + +exports[`spinner (isCI = false) > process exit handling > uses global custom cancel message from settings 1`] = ` +[ + "", + "│ +", + "■ Global cancel message +", + "", +] +`; + +exports[`spinner (isCI = false) > process exit handling > uses global custom error message from settings 1`] = ` +[ + "", + "│ +", + "▲ Global error message +", + "", +] +`; + +exports[`spinner (isCI = false) > start > handles multi-line messages 1`] = ` +[ + "", + "│ +", + "◒ foo +bar +baz", + "", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > start > handles wrapping 1`] = ` +[ + "", + "│ +", + "◒ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxx", + "", + "", + "", + "◇ stopped +", + "", +] +`; + +exports[`spinner (isCI = false) > start > renders frames at interval 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◐ ", + "", + "", + "◓ ", + "", + "", + "◑ ", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > start > renders message 1`] = ` +[ + "", + "│ +", + "◒ foo", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > start > renders timer when indicator is "timer" 1`] = ` +[ + "", + "│ +", + "◒ [0s]", + "", + "", + "◇ [0s] +", + "", +] +`; + +exports[`spinner (isCI = false) > stop > renders cancel symbol when calling cancel() 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "■ +", + "", +] +`; + +exports[`spinner (isCI = false) > stop > renders error symbol when calling error() 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "▲ +", + "", +] +`; + +exports[`spinner (isCI = false) > stop > renders message 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◇ foo +", + "", +] +`; + +exports[`spinner (isCI = false) > stop > renders message when cancelling 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "■ too dizzy — spinning cancelled +", + "", +] +`; + +exports[`spinner (isCI = false) > stop > renders message when erroring 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "▲ error: spun too fast! +", + "", +] +`; + +exports[`spinner (isCI = false) > stop > renders message without removing dots 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◇ foo. +", + "", +] +`; + +exports[`spinner (isCI = false) > stop > renders submit symbol and stops spinner 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◒ foo", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +", + "■ Canceled +", + "", +] +`; + +exports[`spinner (isCI = true) > clear > stops and clears the spinner from the output 1`] = ` +[ + "", + "│ +", + "◒ Loading...", + " +", + "", + "", + "", +] +`; + +exports[`spinner (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◒ foo...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > indicator customization > custom delay 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > indicator customization > custom frame style 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > indicator customization > custom frames 1`] = ` +[ + "", + "│ +", + "🐴 ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > indicator customization > custom frames with lots of frame have consistent ellipsis display 1`] = ` +[ + "", + "│ +", + "0 ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > message > sets message for next frame 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◐ foo...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > process exit handling > prioritizes cancel option over global setting 1`] = ` +[ + "", + "│ +", + "■ Spinner cancel message +", + "", +] +`; + +exports[`spinner (isCI = true) > process exit handling > prioritizes error option over global setting 1`] = ` +[ + "", + "│ +", + "▲ Spinner error message +", + "", +] +`; + +exports[`spinner (isCI = true) > process exit handling > uses custom cancel message when provided directly 1`] = ` +[ + "", + "│ +", + "■ Custom cancel message +", + "", +] +`; + +exports[`spinner (isCI = true) > process exit handling > uses custom error message when provided directly 1`] = ` +[ + "", + "│ +", + "▲ Custom error message +", + "", +] +`; + +exports[`spinner (isCI = true) > process exit handling > uses default cancel message 1`] = ` +[ + "", + "│ +", + "■ Canceled +", + "", +] +`; + +exports[`spinner (isCI = true) > process exit handling > uses global custom cancel message from settings 1`] = ` +[ + "", + "│ +", + "■ Global cancel message +", + "", +] +`; + +exports[`spinner (isCI = true) > process exit handling > uses global custom error message from settings 1`] = ` +[ + "", + "│ +", + "▲ Global error message +", + "", +] +`; + +exports[`spinner (isCI = true) > start > handles multi-line messages 1`] = ` +[ + "", + "│ +", + "◒ foo +bar +baz...", + " +", + "", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > start > handles wrapping 1`] = ` +[ + "", + "│ +", + "◒ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxx...", + " +", + "", + "", + "", + "◇ stopped +", + "", +] +`; + +exports[`spinner (isCI = true) > start > renders frames at interval 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > start > renders message 1`] = ` +[ + "", + "│ +", + "◒ foo...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > start > renders timer when indicator is "timer" 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ [0s] +", + "", +] +`; + +exports[`spinner (isCI = true) > stop > renders cancel symbol when calling cancel() 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "■ +", + "", +] +`; + +exports[`spinner (isCI = true) > stop > renders error symbol when calling error() 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "▲ +", + "", +] +`; + +exports[`spinner (isCI = true) > stop > renders message 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ foo +", + "", +] +`; + +exports[`spinner (isCI = true) > stop > renders message when cancelling 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "■ too dizzy — spinning cancelled +", + "", +] +`; + +exports[`spinner (isCI = true) > stop > renders message when erroring 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "▲ error: spun too fast! +", + "", +] +`; + +exports[`spinner (isCI = true) > stop > renders message without removing dots 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ foo. +", + "", +] +`; + +exports[`spinner (isCI = true) > stop > renders submit symbol and stops spinner 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + +exports[`spinner (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◒ foo...", + " +", + "", + "", + "◇ +", + "", +] +`; diff --git a/packages/prompts/src/__snapshots__/task-log.test.ts.snap b/packages/prompts/src/__snapshots__/task-log.test.ts.snap new file mode 100644 index 00000000..f7f1275a --- /dev/null +++ b/packages/prompts/src/__snapshots__/task-log.test.ts.snap @@ -0,0 +1,1738 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`taskLog (isCI = false) > error > clears output if showLog = false 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "│ +■ some error! +", +] +`; + +exports[`taskLog (isCI = false) > error > renders output with message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "│ +■ some error! +", + "│ +line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = false) > group > applies limit per group 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +", + "Group 1 +", + "Group 1 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +", + "Group 1 +", + "Group 1 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +", + "", + "Group 0 +", + "Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +", + "", + "Group 0 +", + "Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 1 +Group 1 line 2 +", + "", + "Group 0 +", + "Group 0 line 2 +Group 0 line 3 +", + "Group 1 +", + "Group 1 line 1 +Group 1 line 2 +", + "", + "Group 0 +", + "Group 0 line 2 +Group 0 line 3 +", + "Group 1 +", + "Group 1 line 2 +Group 1 line 3 +", + "", + "Group 0 +", + "Group 0 line 3 +Group 0 line 4 +", + "Group 1 +", + "Group 1 line 2 +Group 1 line 3 +", + "", + "Group 0 +", + "Group 0 line 3 +Group 0 line 4 +", + "Group 1 +", + "Group 1 line 3 +Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = false) > group > can render multiple groups of different sizes 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = false) > group > can render multiple groups of equal size 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +", + "Group 1 +", + "Group 1 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +", + "Group 1 +", + "Group 1 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +Group 0 line 3 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +Group 0 line 3 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +Group 0 line 3 +Group 0 line 4 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +Group 0 line 3 +Group 0 line 4 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = false) > group > handles empty groups 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "◆ Group success! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "■ Group error! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders group error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "■ Group error! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders group success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "◆ Group success! +", +] +`; + +exports[`taskLog (isCI = false) > group > renders success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "◆ Group success! +", +] +`; + +exports[`taskLog (isCI = false) > group > showLog shows all groups in order 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "Group 0 +", + "Group 0 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +", + "", + "Group 0 +", + "Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +Group 1 line 4 +", + "", + "◆ Group 0 success! +", + "Group 1 +", + "Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +Group 1 line 4 +", + "", + "◆ Group 0 success! +", + "■ Group 1 error! +", + "", + "│ +■ overall error +", + "Group 0 +", + "│ +Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "│ +Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = false) > message > can write line by line 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = false) > message > can write multiple lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = false) > message > destructive ansi codes are stripped 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 1 +", + "", + "line 1 +line 2 bad ansi! +", + "", + "line 1 +line 2 bad ansi! +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > message > enforces limit if set 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 1 +line 2 +", +] +`; + +exports[`taskLog (isCI = false) > message > prints empty lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 1 +", + "", + "line 1 + +", + "", + "line 1 + +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > message > raw = true appends message text until newline 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0still line 0 +", + "", + "line 0still line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = false) > message > raw = true works when mixed with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0still line 0 +", + "", + "line 0still line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = false) > message > raw = true works when started with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 0 +line 1still line 1 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > error > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 1 +line 2 +", + "", + "line 2 +line 3 +", + "", + "│ +■ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > error > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 0 +line 1 +line 2 +", + "", + "line 0 +line 1 +line 2 +line 3 +", + "", + "│ +■ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > error > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 1 +line 2 +", + "", + "line 2 +line 3 +", + "", + "│ +■ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > error > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 0 +line 1 +line 2 +", + "", + "line 0 +line 1 +line 2 +line 3 +", + "", + "│ +■ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > error > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 1 +line 2 +", + "", + "line 2 +line 3 +", + "", + "│ +■ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > success > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 1 +line 2 +", + "", + "line 2 +line 3 +", + "", + "│ +◆ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > success > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 0 +line 1 +line 2 +", + "", + "line 0 +line 1 +line 2 +line 3 +", + "", + "│ +◆ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > success > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 1 +line 2 +", + "", + "line 2 +line 3 +", + "", + "│ +◆ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > success > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 0 +line 1 +line 2 +", + "", + "line 0 +line 1 +line 2 +line 3 +", + "", + "│ +◆ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > retainLog > success > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "line 1 +line 2 +", + "", + "line 2 +line 3 +", + "", + "│ +◆ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = false) > success > clears output and renders message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "│ +◆ success! +", +] +`; + +exports[`taskLog (isCI = false) > success > renders output if showLog = true 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "line 0 +", + "", + "line 0 +line 1 +", + "", + "│ +◆ success! +", + "│ +line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = false) > writes message header 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", +] +`; + +exports[`taskLog (isCI = true) > error > clears output if showLog = false 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +■ some error! +", +] +`; + +exports[`taskLog (isCI = true) > error > renders output with message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +■ some error! +", + "│ +line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = true) > group > applies limit per group 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > group > can render multiple groups of different sizes 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > group > can render multiple groups of equal size 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > group > handles empty groups 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", +] +`; + +exports[`taskLog (isCI = true) > group > renders error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > renders group error state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > renders group success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > renders success state 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > group > showLog shows all groups in order 1`] = ` +[ + "│ +", + "◇ Some log +", + "│ +", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "│ +■ overall error +", + "Group 0 +", + "│ +Group 0 line 0 +Group 0 line 1 +Group 0 line 2 +", + "Group 1 +", + "│ +Group 1 line 0 +Group 1 line 1 +Group 1 line 2 +Group 1 line 3 +Group 1 line 4 +", +] +`; + +exports[`taskLog (isCI = true) > message > can write line by line 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", +] +`; + +exports[`taskLog (isCI = true) > message > can write multiple lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", +] +`; + +exports[`taskLog (isCI = true) > message > destructive ansi codes are stripped 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > message > enforces limit if set 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > message > prints empty lines 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > message > raw = true appends message text until newline 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > message > raw = true works when mixed with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > message > raw = true works when started with non-raw messages 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", +] +`; + +exports[`taskLog (isCI = true) > retainLog > error > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > error > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > error > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > error > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > error > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +■ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > success > outputs limited log with limit by default 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > success > retainLog = false outputs full log without limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > success > retainLog = false outputs limited log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > success > retainLog = true outputs full log 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > retainLog > success > retainLog = true outputs full log with limit 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "", + "", + "│ +◆ woo! +", + "│ +line 0 +line 1 +line 2 +line 3 +", +] +`; + +exports[`taskLog (isCI = true) > success > clears output and renders message 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +◆ success! +", +] +`; + +exports[`taskLog (isCI = true) > success > renders output if showLog = true 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", + "", + "", + "│ +◆ success! +", + "│ +line 0 +line 1 +", +] +`; + +exports[`taskLog (isCI = true) > writes message header 1`] = ` +[ + "│ +", + "◇ foo +", + "│ +", +] +`; diff --git a/packages/prompts/src/__snapshots__/text.test.ts.snap b/packages/prompts/src/__snapshots__/text.test.ts.snap new file mode 100644 index 00000000..c50bf5b1 --- /dev/null +++ b/packages/prompts/src/__snapshots__/text.test.ts.snap @@ -0,0 +1,595 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`text (isCI = false) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + " +", + "", +] +`; + +exports[`text (isCI = false) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "■ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > defaultValue sets the value but does not render 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "◇ foo +bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > empty string when no value and no default 1`] = ` +[ + "", + "│ +◆ foo +│   (hit Enter to use default) +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`text (isCI = false) > placeholder is not used as value when pressing enter 1`] = ` +[ + "", + "│ +◆ foo +│   (hit Enter to use default) +└ +", + "", + "", + "", + "◇ foo +default-value", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "■ foo +│ xy +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders placeholder if set 1`] = ` +[ + "", + "│ +◆ foo +│ bar +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`text (isCI = false) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`text (isCI = true) > can be aborted by a signal 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + " +", + "", +] +`; + +exports[`text (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "■ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > defaultValue sets the value but does not render 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "◇ foo +bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > empty string when no value and no default 1`] = ` +[ + "", + "│ +◆ foo +│   (hit Enter to use default) +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > global withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`; + +exports[`text (isCI = true) > placeholder is not used as value when pressing enter 1`] = ` +[ + "", + "│ +◆ foo +│   (hit Enter to use default) +└ +", + "", + "", + "", + "◇ foo +default-value", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "■ foo +│ xy +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders placeholder if set 1`] = ` +[ + "", + "│ +◆ foo +│ bar +└ +", + "", + "", + "", + "◇ foo +│", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`text (isCI = true) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ x█", + "", + "", + "", + "", + "▲ foo +│ x█ +should be xy +", + "", + "", + "", + "◆ foo +│ xy█ +└ +", + "", + "", + "", + "◇ foo +xy", + " +", + "", +] +`; + +exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` +[ + "", + "◆ foo +_ + +", + "", + "", + "◇ foo +", + " +", + "", +] +`;