Skip to content

feat(@clack/core,@clack/prompts): mock API #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ce8e6a6
feat: improve types event emitter & global aliases
cpreston321 Aug 24, 2023
18d3367
chore: format
cpreston321 Aug 26, 2023
013ff40
feat: mock api
orochaa Sep 8, 2023
6e436ed
test: text prompt
orochaa Sep 8, 2023
b98c671
test: core Prompt
orochaa Sep 10, 2023
1d50857
test: ConfirmPrompt
orochaa Sep 11, 2023
902b088
test: TextPrompt
orochaa Sep 11, 2023
4601ecc
test: PasswordPrompt
orochaa Sep 11, 2023
b7fff84
test: SelectPrompt
orochaa Sep 11, 2023
691eaaf
test: MultiSelectPrompt
orochaa Sep 11, 2023
d494793
test: GroupMultiSelectPrompt
orochaa Sep 12, 2023
f239a1b
test: SelectKeyPrompt
orochaa Sep 12, 2023
ca67dc2
test: clack core exports
orochaa Sep 12, 2023
11dc7a3
test: core utils
orochaa Sep 12, 2023
c0ad854
test: core alias utils
orochaa Sep 12, 2023
942baee
test: core string utils
orochaa Sep 12, 2023
fa60690
test: password prompt
orochaa Sep 12, 2023
e427cdd
test: confirm prompt
orochaa Sep 13, 2023
f0cbeff
test: select component
orochaa Sep 13, 2023
ddcb83c
test: multiselect component
orochaa Sep 13, 2023
364f949
test: log prompts
orochaa Sep 13, 2023
992d06c
test: spinner prompt
orochaa Sep 14, 2023
241d9c8
test: tasks prompt
orochaa Sep 14, 2023
d8e2213
test: selectKey prompt
orochaa Sep 14, 2023
52ad956
test: clack prompts package exports
orochaa Sep 14, 2023
77d281a
test: note prompt
orochaa Sep 14, 2023
c2575d5
test: group prompt
orochaa Sep 14, 2023
ec0a91e
test: improve package exports tests
orochaa Sep 14, 2023
f95fd50
test: groupMultiselect prompt
orochaa Sep 18, 2023
79fef40
test: prompts format utils
orochaa Sep 18, 2023
776dcc3
test: improve test coverage
orochaa Sep 18, 2023
febb482
fix: ci
orochaa Sep 18, 2023
9ef008b
fix: add missing setCursor
orochaa Sep 26, 2023
e598138
refactor: return maskedValue to masked
orochaa Sep 27, 2023
06c890b
fix: mock.pressKey key param
orochaa Nov 10, 2023
2e622d2
refactor: use expect.toMatchSnapshot to test conplex frames
orochaa Dec 13, 2023
8adada2
test: add missing tests
orochaa Mar 21, 2024
50a9c3c
docs: add how to test
orochaa Mar 21, 2024
98d8c81
test: add tests from #195
orochaa Nov 26, 2024
2890f81
chore: format with biome
orochaa Dec 15, 2024
12951c1
chore: shrink changes on pnpm-lock.yaml
orochaa Dec 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "biomejs.biome"
}
14 changes: 14 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @type {import('jest').Config} */
export default {
bail: true,
clearMocks: true,
testEnvironment: 'node',
transform: {
'^.+\\.ts$': '@swc/jest',
},
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
testRegex: ['__tests__/.+(spec|test).ts$'],
setupFiles: ['<rootDir>/setup.tests.ts'],
};
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@
"format": "biome check --write",
"lint": "biome lint --write --unsafe",
"type-check": "biome lint && tsc",
"test": "pnpm -r run test",
"test": "jest --no-cache",
"test:w": "npm test -- --watch",
"test:ci": "npm test -- --coverage",
"ci:version": "changeset version && pnpm install --no-frozen-lockfile",
"ci:publish": "changeset publish"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@changesets/cli": "^2.26.2",
"@swc/core": "^1.3.83",
"@swc/jest": "^0.2.29",
"@types/jest": "^29.5.4",
"@types/node": "^18.16.0",
"jest": "^29.6.4",
"typescript": "^5.2.2",
"unbuild": "^2.0.0"
},
Expand Down
25 changes: 25 additions & 0 deletions packages/core/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { readdirSync } from 'node:fs';
import { join } from 'node:path';
import * as packageExports from '../src/index';

describe('Package', () => {
const exportedKeys = Object.keys(packageExports);

it('should export all prompts', async () => {
const promptsPath = join(__dirname, '../src/prompts');
const promptFiles = readdirSync(promptsPath);

for (const file of promptFiles) {
const prompt = await import(join(promptsPath, file));
expect(exportedKeys).toContain(prompt.default.name);
}
});

it('should export selected utils', async () => {
const utils: string[] = ['block', 'isCancel', 'mockPrompt', 'setGlobalAliases'];

for (const util of utils) {
expect(exportedKeys).toContain(util);
}
});
});
2 changes: 2 additions & 0 deletions packages/core/__tests__/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './mock-readable.js';
export * from './mock-writable.js';
96 changes: 96 additions & 0 deletions packages/core/__tests__/prompts/confirm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { ConfirmPrompt, mockPrompt, setGlobalAliases } from '../../src';
import type { ConfirmOptions } from '../../src/prompts/confirm';

const makeSut = (opts?: Partial<ConfirmOptions>) => {
return new ConfirmPrompt({
render() {
return this.value;
},
active: 'yes',
inactive: 'no',
...opts,
}).prompt();
};

describe('ConfirmPrompt', () => {
const mock = mockPrompt<ConfirmPrompt>();

afterEach(() => {
mock.close();
});

it('should set a boolean value', () => {
makeSut({
// @ts-expect-error
initialValue: ' ',
});
expect(mock.value).toBe(true);
mock.close();

makeSut({
// @ts-expect-error
initialValue: '',
});
expect(mock.state).toBe('initial');
expect(mock.value).toBe(false);
});

it('should change value when cursor changes', () => {
makeSut();

expect(mock.value).toBe(false);
mock.pressKey('up', { name: 'up' });
expect(mock.cursor).toBe(0);
expect(mock.value).toBe(true);
mock.pressKey('right', { name: 'right' });
expect(mock.cursor).toBe(1);
expect(mock.value).toBe(false);
mock.pressKey('left', { name: 'left' });
expect(mock.cursor).toBe(0);
expect(mock.value).toBe(true);
});

it('should change value on cursor alias', () => {
setGlobalAliases([['u', 'up']]);
makeSut();

expect(mock.value).toBe(false);
mock.pressKey('u', { name: 'u' });
expect(mock.value).toBe(true);
});

it('should not change value on type', () => {
makeSut();

expect(mock.value).toBe(false);
mock.pressKey('t', { name: 't' });
expect(mock.value).toBe(false);
mock.pressKey('e', { name: 'e' });
expect(mock.value).toBe(false);
});

it('should submit value', () => {
makeSut();

mock.submit();

expect(mock.state).toBe('submit');
expect(mock.value).toBe(false);
});

it('should submit value on confirm alias', () => {
const aliases = [
['y', true],
['n', false],
] as const;

for (const [alias, expected] of aliases) {
makeSut();
expect(mock.state).not.toBe('submit');
mock.pressKey(alias, { name: alias });
expect(mock.state).toBe('submit');
expect(mock.value).toBe(expected);
mock.close();
}
});
});
177 changes: 177 additions & 0 deletions packages/core/__tests__/prompts/group-multiselect.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { GroupMultiSelectPrompt, mockPrompt, setGlobalAliases } from '../../src';
import type { GroupMultiSelectOptions } from '../../src/prompts/group-multiselect';

const makeSut = (opts?: Partial<GroupMultiSelectOptions<{ value: string }>>) => {
return new GroupMultiSelectPrompt<any>({
render() {
return this.value;
},
options: {
'changed packages': [{ value: '@scope/a' }, { value: '@scope/b' }, { value: '@scope/c' }],
'unchanged packages': [{ value: '@scope/x' }, { value: '@scope/y' }, { value: '@scope/z' }],
},
...opts,
}).prompt();
};

describe('GroupMultiSelectPrompt', () => {
const mock = mockPrompt<GroupMultiSelectPrompt<{ value: string }>>();

afterEach(() => {
mock.close();
});

it('should set options', () => {
makeSut();

expect(mock.options).toStrictEqual([
{ label: 'changed packages', value: 'changed packages', group: true },
{ value: '@scope/a', group: 'changed packages' },
{ value: '@scope/b', group: 'changed packages' },
{ value: '@scope/c', group: 'changed packages' },
{ label: 'unchanged packages', value: 'unchanged packages', group: true },
{ value: '@scope/x', group: 'unchanged packages' },
{ value: '@scope/y', group: 'unchanged packages' },
{ value: '@scope/z', group: 'unchanged packages' },
]);
});

it('should set initialValues', () => {
makeSut({
initialValues: ['@scope/a', 'unchanged packages'],
});

expect(mock.value).toStrictEqual(['@scope/a', '@scope/x', '@scope/y', '@scope/z']);
});

it('should set initial cursor position', () => {
makeSut({
cursorAt: '@scope/b',
});

expect(mock.cursor).toBe(2);
});

it('should set default cursor position', () => {
makeSut();

expect(mock.cursor).toBe(0);
});

it('should change cursor position on cursor', () => {
makeSut({
options: {
groupA: [{ value: '1' }],
groupB: [{ value: '1' }],
},
});
const moves = [
['down', 1],
['right', 2],
['down', 3],
['right', 0],
['left', 3],
['up', 2],
] as const;

for (const [cursor, index] of moves) {
mock.emit('cursor', cursor);
expect(mock.cursor).toBe(index);
}
});

it('should change cursor position on cursor alias', () => {
setGlobalAliases([
['d', 'down'],
['u', 'up'],
]);
makeSut();
const moves = [
['d', 1],
['u', 0],
] as const;

for (const [cursor, index] of moves) {
mock.pressKey(cursor, { name: cursor });
expect(mock.cursor).toBe(index);
}
});

it('should toggle option', () => {
makeSut();

mock.emit('cursor', 'down');
mock.emit('cursor', 'space');

expect(mock.value).toStrictEqual(['@scope/a']);
});

it('should toggle multiple options', () => {
makeSut();

mock.emit('cursor', 'down');
mock.emit('cursor', 'space');
mock.emit('cursor', 'down');
mock.emit('cursor', 'space');

expect(mock.value).toStrictEqual(['@scope/a', '@scope/b']);
});

it('should untoggle option', () => {
makeSut();

mock.emit('cursor', 'down');
mock.emit('cursor', 'space');
mock.emit('cursor', 'space');

expect(mock.value).toStrictEqual([]);
});

it('should toggle group', () => {
makeSut();

mock.emit('cursor', 'space');

expect(mock.value).toStrictEqual(['@scope/a', '@scope/b', '@scope/c']);
});

it('should toggle multiple groups', () => {
makeSut();

mock.emit('cursor', 'space');
mock.emit('cursor', 'down');
mock.emit('cursor', 'down');
mock.emit('cursor', 'down');
mock.emit('cursor', 'down');
mock.emit('cursor', 'space');

expect(mock.value).toStrictEqual([
'@scope/a',
'@scope/b',
'@scope/c',
'@scope/x',
'@scope/y',
'@scope/z',
]);
});

it('should untoggle group', () => {
makeSut();

mock.emit('cursor', 'space');
mock.emit('cursor', 'space');

expect(mock.value).toStrictEqual([]);
});

it('should submit value', () => {
makeSut({
initialValues: ['changed packages'],
});

mock.submit();

expect(mock.state).toBe('submit');
expect(mock.value).toStrictEqual(['@scope/a', '@scope/b', '@scope/c']);
});
});
Loading
Loading