Skip to content

Commit 0036ee4

Browse files
committed
test: add initial tests
1 parent 1fefce5 commit 0036ee4

File tree

8 files changed

+149
-6
lines changed

8 files changed

+149
-6
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ export { default as SelectPrompt } from './prompts/select.js';
88
export { default as SelectKeyPrompt } from './prompts/select-key.js';
99
export { default as TextPrompt } from './prompts/text.js';
1010
export type { ClackState as State } from './types.js';
11-
export { block, getColumns, isCancel } from './utils/index.js';
11+
export { block, getColumns, getRows, isCancel } from './utils/index.js';
1212
export type { ClackSettings } from './utils/settings.js';
1313
export { settings, updateSettings } from './utils/settings.js';

packages/core/src/utils/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,11 @@ export const getColumns = (output: Writable): number => {
9090
}
9191
return 80;
9292
};
93+
94+
export const getRows = (output: Writable): number => {
95+
const withRows = output as Writable & { rows?: number };
96+
if ('rows' in withRows && typeof withRows.rows === 'number') {
97+
return withRows.rows;
98+
}
99+
return 20;
100+
};

packages/prompts/src/limit-options.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Writable } from 'node:stream';
2-
import { WriteStream } from 'node:tty';
3-
import { getColumns } from '@clack/core';
2+
import { getColumns, getRows } from '@clack/core';
43
import { wrapAnsi } from 'fast-wrap-ansi';
54
import color from 'picocolors';
65
import type { CommonOptions } from './common.js';
@@ -41,13 +40,13 @@ export const limitOptions = <TOption>(params: LimitOptionsParams<TOption>): stri
4140
const columnPadding = params.columnPadding ?? 0;
4241
const rowPadding = params.rowPadding ?? 4;
4342
const maxWidth = columns - columnPadding;
44-
const rows = output instanceof WriteStream && output.rows !== undefined ? output.rows : 20;
43+
const rows = getRows(output);
4544
const overflowFormat = color.dim('...');
4645

4746
const paramMaxItems = params.maxItems ?? Number.POSITIVE_INFINITY;
4847
const outputMaxItems = Math.max(rows - rowPadding, 0);
4948
// We clamp to minimum 5 because anything less doesn't make sense UX wise
50-
const maxItems = Math.min(outputMaxItems, Math.max(paramMaxItems, 5));
49+
const maxItems = Math.max(paramMaxItems, 5);
5150
let slidingWindowLocation = 0;
5251

5352
if (cursor >= maxItems - 3) {

packages/prompts/test/__snapshots__/autocomplete.test.ts.snap

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,34 @@ exports[`autocomplete > limits displayed options when maxItems is set 1`] = `
4646
]
4747
`;
4848
49+
exports[`autocomplete > renders bottom ellipsis when items do not fit 1`] = `
50+
[
51+
"<cursor.hide>",
52+
"│
53+
◆ Select an option
54+
│
55+
│ Search: _
56+
│ ● Line 0
57+
│ Line 1
58+
│ Line 2
59+
│ Line 3
60+
│ ...
61+
│ ↑/↓ to select • Enter: confirm • Type: to search
62+
└",
63+
"<cursor.backward count=999><cursor.up count=10>",
64+
"<cursor.down count=1>",
65+
"<erase.down>",
66+
"◇ Select an option
67+
│ Line 0
68+
Line 1
69+
Line 2
70+
Line 3",
71+
"
72+
",
73+
"<cursor.show>",
74+
]
75+
`;
76+
4977
exports[`autocomplete > renders initial UI with message and instructions 1`] = `
5078
[
5179
"<cursor.hide>",
@@ -96,6 +124,28 @@ exports[`autocomplete > renders placeholder if set 1`] = `
96124
]
97125
`;
98126
127+
exports[`autocomplete > renders top ellipsis when scrolled down and its do not fit 1`] = `
128+
[
129+
"<cursor.hide>",
130+
"│
131+
◆ Select an option
132+
│
133+
│ Search: _
134+
│ ...
135+
│ ● Option 2
136+
│ ↑/↓ to select • Enter: confirm • Type: to search
137+
└",
138+
"<cursor.backward count=999><cursor.up count=7>",
139+
"<cursor.down count=1>",
140+
"<erase.down>",
141+
"◇ Select an option
142+
│ Option 2",
143+
"
144+
",
145+
"<cursor.show>",
146+
]
147+
`;
148+
99149
exports[`autocomplete > shows hint when option has hint and is focused 1`] = `
100150
[
101151
"<cursor.hide>",

packages/prompts/test/autocomplete.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,63 @@ describe('autocomplete', () => {
168168
expect(isCancel(value)).toBe(true);
169169
expect(output.buffer).toMatchSnapshot();
170170
});
171+
172+
test('renders bottom ellipsis when items do not fit', async () => {
173+
output.rows = 5;
174+
175+
const options = [
176+
{
177+
value: Array.from({ length: 4 })
178+
.map((_val, index) => `Line ${index}`)
179+
.join('\n'),
180+
},
181+
{
182+
value: 'Option 2',
183+
},
184+
];
185+
186+
const result = autocomplete({
187+
message: 'Select an option',
188+
options,
189+
maxItems: 5,
190+
input,
191+
output,
192+
});
193+
194+
input.emit('keypress', '', { name: 'return' });
195+
await result;
196+
expect(output.buffer).toMatchSnapshot();
197+
});
198+
199+
test('renders top ellipsis when scrolled down and its do not fit', async () => {
200+
output.rows = 5;
201+
202+
const options = [
203+
{
204+
value: 'option1',
205+
label: Array.from({ length: 4 })
206+
.map((_val, index) => `Line ${index}`)
207+
.join('\n'),
208+
},
209+
{
210+
value: 'option2',
211+
label: 'Option 2',
212+
},
213+
];
214+
215+
const result = autocomplete({
216+
message: 'Select an option',
217+
options,
218+
initialValue: 'option2',
219+
maxItems: 5,
220+
input,
221+
output,
222+
});
223+
224+
input.emit('keypress', '', { name: 'return' });
225+
await result;
226+
expect(output.buffer).toMatchSnapshot();
227+
});
171228
});
172229

173230
describe('autocompleteMultiselect', () => {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { beforeEach, describe, expect, test } from 'vitest';
2+
import { type LimitOptionsParams, limitOptions } from '../src/index.js';
3+
import { MockWritable } from './test-utils.js';
4+
5+
describe('limitOptions', () => {
6+
let output: MockWritable;
7+
let options: LimitOptionsParams<{ value: string }>;
8+
9+
beforeEach(() => {
10+
output = new MockWritable();
11+
options = {
12+
output,
13+
options: [],
14+
maxItems: undefined,
15+
cursor: 0,
16+
style: (option) => option.value,
17+
columnPadding: undefined,
18+
rowPadding: undefined,
19+
};
20+
});
21+
22+
test('returns all items if they fit', async () => {
23+
options.options = [{ value: 'Item 1' }, { value: 'Item 2' }, { value: 'Item 3' }];
24+
options.maxItems = 5;
25+
const result = limitOptions(options);
26+
expect(result).toEqual(['Item 1', 'Item 2', 'Item 3']);
27+
});
28+
});

packages/prompts/test/test-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export class MockWritable extends Writable {
44
public buffer: string[] = [];
55
public isTTY = false;
66
public columns = 80;
7+
public rows = 20;
78

89
_write(
910
chunk: any,

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
"@clack/prompts": ["./packages/prompts/src/index.ts"]
1818
}
1919
},
20-
"include": ["packages/*/src/**/*"]
20+
"include": ["packages/*/src/**/*.ts", "packages/*/test/**/*.ts"]
2121
}

0 commit comments

Comments
 (0)