Skip to content

Commit 2d3a57c

Browse files
committed
Merge branch 'mock-api' of https://github.com/Mist3rBru/clack into mock-api
2 parents 2bbd33e + c40a558 commit 2d3a57c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+6506
-2063
lines changed

.changeset/swift-jars-destroy.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clack/prompts': patch
3+
'@clack/core': patch
4+
---
5+
6+
Add global aliases and type event emiitter for core.

examples/basic/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ async function main() {
77

88
await setTimeout(1000);
99

10+
p.setGlobalAliases([
11+
['w', 'up'],
12+
['s', 'down'],
13+
['a', 'left'],
14+
['d', 'right'],
15+
['escape', 'cancel'],
16+
]);
17+
1018
p.intro(`${color.bgCyan(color.black(' create-app '))}`);
1119

1220
const project = await p.group(

jest.config.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** @type {import('jest').Config} */
2+
export default {
3+
bail: true,
4+
clearMocks: true,
5+
testEnvironment: 'node',
6+
transform: {
7+
'^.+\\.ts$': '@swc/jest',
8+
},
9+
moduleNameMapper: {
10+
'^(\\.{1,2}/.*)\\.js$': '$1',
11+
},
12+
testRegex: ['__tests__/.+(spec|test).ts$'],
13+
setupFiles: ['<rootDir>/setup.tests.ts'],
14+
};

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@
1313
"format:code": "prettier -w . --cache",
1414
"format:imports": "organize-imports-cli ./packages/*/tsconfig.json",
1515
"type-check": "tsc",
16-
"test": "pnpm -r run test",
16+
"test": "jest --no-cache",
17+
"test:w": "npm test -- --watch",
18+
"test:ci": "npm test -- --coverage",
1719
"ci:version": "changeset version && pnpm install --no-frozen-lockfile",
1820
"ci:publish": "changeset publish",
1921
"ci:format": "pnpm run format"
2022
},
2123
"devDependencies": {
2224
"@changesets/cli": "^2.26.2",
25+
"@swc/core": "^1.3.83",
26+
"@swc/jest": "^0.2.29",
27+
"@types/jest": "^29.5.4",
2328
"@types/node": "^18.16.0",
29+
"jest": "^29.6.4",
2430
"organize-imports-cli": "^0.10.0",
2531
"prettier": "^3.0.2",
2632
"typescript": "^5.2.2",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { readdirSync } from 'node:fs';
2+
import { join } from 'node:path';
3+
import * as packageExports from '../src/index';
4+
5+
describe('Package', () => {
6+
const exportedKeys = Object.keys(packageExports);
7+
8+
it('should export all prompts', async () => {
9+
const promptsPath = join(__dirname, '../src/prompts');
10+
const promptFiles = readdirSync(promptsPath);
11+
12+
for (const file of promptFiles) {
13+
const prompt = await import(join(promptsPath, file));
14+
expect(exportedKeys).toContain(prompt.default.name);
15+
}
16+
});
17+
18+
it('should export selected utils', async () => {
19+
const utils: string[] = ['block', 'isCancel', 'mockPrompt', 'setGlobalAliases'];
20+
21+
for (const util of utils) {
22+
expect(exportedKeys).toContain(util);
23+
}
24+
});
25+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './mock-readable.js';
2+
export * from './mock-writable.js';
File renamed without changes.
File renamed without changes.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { ConfirmPrompt, mockPrompt, setGlobalAliases } from '../../src';
2+
import { ConfirmOptions } from '../../src/prompts/confirm';
3+
4+
const makeSut = (opts?: Partial<ConfirmOptions>) => {
5+
return new ConfirmPrompt({
6+
render() {
7+
return this.value;
8+
},
9+
active: 'yes',
10+
inactive: 'no',
11+
...opts,
12+
}).prompt();
13+
};
14+
15+
describe('ConfirmPrompt', () => {
16+
const mock = mockPrompt<ConfirmPrompt>();
17+
18+
afterEach(() => {
19+
mock.close();
20+
});
21+
22+
it('should set a boolean value', () => {
23+
makeSut({
24+
// @ts-expect-error
25+
initialValue: ' ',
26+
});
27+
expect(mock.value).toBe(true);
28+
mock.close();
29+
30+
makeSut({
31+
// @ts-expect-error
32+
initialValue: '',
33+
});
34+
expect(mock.state).toBe('initial');
35+
expect(mock.value).toBe(false);
36+
});
37+
38+
it('should change value when cursor changes', () => {
39+
makeSut();
40+
41+
expect(mock.value).toBe(false);
42+
mock.pressKey('up', { name: 'up' });
43+
expect(mock.cursor).toBe(0);
44+
expect(mock.value).toBe(true);
45+
mock.pressKey('right', { name: 'right' });
46+
expect(mock.cursor).toBe(1);
47+
expect(mock.value).toBe(false);
48+
mock.pressKey('left', { name: 'left' });
49+
expect(mock.cursor).toBe(0);
50+
expect(mock.value).toBe(true);
51+
});
52+
53+
it('should change value on cursor alias', () => {
54+
setGlobalAliases([['u', 'up']]);
55+
makeSut();
56+
57+
expect(mock.value).toBe(false);
58+
mock.pressKey('u', { name: 'u' });
59+
expect(mock.value).toBe(true);
60+
});
61+
62+
it('should not change value on type', () => {
63+
makeSut();
64+
65+
expect(mock.value).toBe(false);
66+
mock.pressKey('t', { name: 't' });
67+
expect(mock.value).toBe(false);
68+
mock.pressKey('e', { name: 'e' });
69+
expect(mock.value).toBe(false);
70+
});
71+
72+
it('should submit value', () => {
73+
makeSut();
74+
75+
mock.submit();
76+
77+
expect(mock.state).toBe('submit');
78+
expect(mock.value).toBe(false);
79+
});
80+
81+
it('should submit value on confirm alias', () => {
82+
const aliases = [
83+
['y', true],
84+
['n', false],
85+
] as const;
86+
87+
for (const [alias, expected] of aliases) {
88+
makeSut();
89+
expect(mock.state).not.toBe('submit');
90+
mock.pressKey(alias, { name: alias });
91+
expect(mock.state).toBe('submit');
92+
expect(mock.value).toBe(expected);
93+
mock.close();
94+
}
95+
});
96+
});
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { GroupMultiSelectPrompt, mockPrompt, setGlobalAliases } from '../../src';
2+
import { GroupMultiSelectOptions } from '../../src/prompts/group-multiselect';
3+
4+
const makeSut = (opts?: Partial<GroupMultiSelectOptions<{ value: string }>>) => {
5+
return new GroupMultiSelectPrompt<any>({
6+
render() {
7+
return this.value;
8+
},
9+
options: {
10+
'changed packages': [{ value: '@scope/a' }, { value: '@scope/b' }, { value: '@scope/c' }],
11+
'unchanged packages': [{ value: '@scope/x' }, { value: '@scope/y' }, { value: '@scope/z' }],
12+
},
13+
...opts,
14+
}).prompt();
15+
};
16+
17+
describe('GroupMultiSelectPrompt', () => {
18+
const mock = mockPrompt<GroupMultiSelectPrompt<{ value: string }>>();
19+
20+
afterEach(() => {
21+
mock.close();
22+
});
23+
24+
it('should set options', () => {
25+
makeSut();
26+
27+
expect(mock.options).toStrictEqual([
28+
{ label: 'changed packages', value: 'changed packages', group: true },
29+
{ value: '@scope/a', group: 'changed packages' },
30+
{ value: '@scope/b', group: 'changed packages' },
31+
{ value: '@scope/c', group: 'changed packages' },
32+
{ label: 'unchanged packages', value: 'unchanged packages', group: true },
33+
{ value: '@scope/x', group: 'unchanged packages' },
34+
{ value: '@scope/y', group: 'unchanged packages' },
35+
{ value: '@scope/z', group: 'unchanged packages' },
36+
]);
37+
});
38+
39+
it('should set initialValues', () => {
40+
makeSut({
41+
initialValues: ['@scope/a', 'unchanged packages'],
42+
});
43+
44+
expect(mock.value).toStrictEqual(['@scope/a', '@scope/x', '@scope/y', '@scope/z']);
45+
});
46+
47+
it('should set initial cursor position', () => {
48+
makeSut({
49+
cursorAt: '@scope/b',
50+
});
51+
52+
expect(mock.cursor).toBe(2);
53+
});
54+
55+
it('should set default cursor position', () => {
56+
makeSut();
57+
58+
expect(mock.cursor).toBe(0);
59+
});
60+
61+
it('should change cursor position on cursor', () => {
62+
makeSut({
63+
options: {
64+
groupA: [{ value: '1' }],
65+
groupB: [{ value: '1' }],
66+
},
67+
});
68+
const moves = [
69+
['down', 1],
70+
['right', 2],
71+
['down', 3],
72+
['right', 0],
73+
['left', 3],
74+
['up', 2],
75+
] as const;
76+
77+
for (const [cursor, index] of moves) {
78+
mock.emit('cursor', cursor);
79+
expect(mock.cursor).toBe(index);
80+
}
81+
});
82+
83+
it('should change cursor position on cursor alias', () => {
84+
setGlobalAliases([
85+
['d', 'down'],
86+
['u', 'up'],
87+
]);
88+
makeSut();
89+
const moves = [
90+
['d', 1],
91+
['u', 0],
92+
] as const;
93+
94+
for (const [cursor, index] of moves) {
95+
mock.pressKey(cursor, { name: cursor });
96+
expect(mock.cursor).toBe(index);
97+
}
98+
});
99+
100+
it('should toggle option', () => {
101+
makeSut();
102+
103+
mock.emit('cursor', 'down');
104+
mock.emit('cursor', 'space');
105+
106+
expect(mock.value).toStrictEqual(['@scope/a']);
107+
});
108+
109+
it('should toggle multiple options', () => {
110+
makeSut();
111+
112+
mock.emit('cursor', 'down');
113+
mock.emit('cursor', 'space');
114+
mock.emit('cursor', 'down');
115+
mock.emit('cursor', 'space');
116+
117+
expect(mock.value).toStrictEqual(['@scope/a', '@scope/b']);
118+
});
119+
120+
it('should untoggle option', () => {
121+
makeSut();
122+
123+
mock.emit('cursor', 'down');
124+
mock.emit('cursor', 'space');
125+
mock.emit('cursor', 'space');
126+
127+
expect(mock.value).toStrictEqual([]);
128+
});
129+
130+
it('should toggle group', () => {
131+
makeSut();
132+
133+
mock.emit('cursor', 'space');
134+
135+
expect(mock.value).toStrictEqual(['@scope/a', '@scope/b', '@scope/c']);
136+
});
137+
138+
it('should toggle multiple groups', () => {
139+
makeSut();
140+
141+
mock.emit('cursor', 'space');
142+
mock.emit('cursor', 'down');
143+
mock.emit('cursor', 'down');
144+
mock.emit('cursor', 'down');
145+
mock.emit('cursor', 'down');
146+
mock.emit('cursor', 'space');
147+
148+
expect(mock.value).toStrictEqual([
149+
'@scope/a',
150+
'@scope/b',
151+
'@scope/c',
152+
'@scope/x',
153+
'@scope/y',
154+
'@scope/z',
155+
]);
156+
});
157+
158+
it('should untoggle group', () => {
159+
makeSut();
160+
161+
mock.emit('cursor', 'space');
162+
mock.emit('cursor', 'space');
163+
164+
expect(mock.value).toStrictEqual([]);
165+
});
166+
167+
it('should submit value', () => {
168+
makeSut({
169+
initialValues: ['changed packages'],
170+
});
171+
172+
mock.submit();
173+
174+
expect(mock.state).toBe('submit');
175+
expect(mock.value).toStrictEqual(['@scope/a', '@scope/b', '@scope/c']);
176+
});
177+
});

0 commit comments

Comments
 (0)