-
Notifications
You must be signed in to change notification settings - Fork 88
/
Copy pathpromptForChange.test.ts
162 lines (139 loc) · 6.2 KB
/
promptForChange.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals';
import prompts from 'prompts';
import {
promptForChange,
_getChangeFileInfoFromResponse,
_promptForPackageChange,
} from '../../changefile/promptForChange';
import { ChangeFilePromptOptions } from '../../types/ChangeFilePrompt';
import { initMockLogs } from '../../__fixtures__/mockLogs';
import { MockStdin } from '../../__fixtures__/mockStdin';
import { MockStdout } from '../../__fixtures__/mockStdout';
import { makePackageInfos } from '../../__fixtures__/packageInfos';
// prompts writes to stdout (not console) in a way that can't really be mocked with spies,
// so instead we inject a custom mock stdout stream, as well as stdin for entering answers
let stdin: MockStdin;
let stdout: MockStdout;
jest.mock('prompts', () => {
const realPrompts = jest.requireActual('prompts') as typeof prompts;
return ((questions, options) => {
const questionsArr = Array.isArray(questions) ? questions : [questions];
return realPrompts(
questionsArr.map(q => ({ ...q, stdin, stdout })),
options
);
}) as typeof prompts;
});
/**
* These combined tests mainly cover various early bail-out cases (the only meaningful logic in
* `promptForChange` itself), plus one basic case.
* More detailed scenarios are covered by the tests for the helper functions.
*/
describe('promptForChange', () => {
/** Wait for the prompt to finish rendering (simulates real user input) */
const waitForPrompt = () => new Promise(resolve => process.nextTick(resolve));
const logs = initMockLogs();
/**
* Default: packages foo, bar, baz; changes in foo, bar
* (this is a function so stdin/stdout will be the latest values)
*/
const defaultParams = (): Parameters<typeof promptForChange>[0] => ({
changedPackages: ['foo', 'bar'],
packageInfos: makePackageInfos({ foo: {}, bar: {}, baz: {} }),
packageGroups: {},
options: { message: '' },
recentMessages: ['commit 2', 'commit 1'],
email: null,
});
beforeEach(() => {
stdin = new MockStdin();
stdout = new MockStdout();
});
afterEach(() => {
stdin.destroy();
stdout.destroy();
});
it('does not create change files when there are no changes', async () => {
const changeFiles = await promptForChange({ ...defaultParams(), changedPackages: [] });
expect(changeFiles).toBeUndefined();
});
it('does not prompt if options.type and options.message are provided', async () => {
const changeFiles = await promptForChange({
...defaultParams(),
options: { type: 'minor', message: 'message' },
});
expect(changeFiles).toEqual([
expect.objectContaining({ type: 'minor', comment: 'message', packageName: 'foo' }),
expect.objectContaining({ type: 'minor', comment: 'message', packageName: 'bar' }),
]);
});
// Do one "normal" end-to-end case (the details are covered by helper function tests)
it('prompts for change for multiple packages', async () => {
const changeFilesPromise = promptForChange(defaultParams());
await waitForPrompt();
// verify asking for first package
expect(logs.mocks.log).toHaveBeenLastCalledWith('Please describe the changes for: foo (currently v1.0.0)');
// choose custom options for this package
stdin.emitKey({ name: 'down' });
await stdin.sendByChar('\n');
stdin.emitKey({ name: 'down' });
await stdin.sendByChar('\n');
// verify asking for second package
expect(logs.mocks.log).toHaveBeenLastCalledWith('Please describe the changes for: bar (currently v1.0.0)');
// choose defaults
await stdin.sendByChar('\n\n');
expect(await changeFilesPromise).toEqual([
expect.objectContaining({ comment: 'commit 1', packageName: 'foo', type: 'minor' }),
expect.objectContaining({ comment: 'commit 2', packageName: 'bar', type: 'patch' }),
]);
});
it('stops before asking questions if one package has invalid options', async () => {
const changeFiles = await promptForChange({
...defaultParams(),
packageInfos: makePackageInfos({
foo: {},
bar: { combinedOptions: { disallowedChangeTypes: ['major', 'minor', 'patch', 'none'] } },
}),
});
expect(changeFiles).toBeUndefined();
expect(logs.mocks.error).toHaveBeenLastCalledWith('No valid change types available for package "bar"');
expect(logs.mocks.log).not.toHaveBeenCalled();
});
it('stops partway through if the user cancels', async () => {
const changeFilesPromise = promptForChange(defaultParams());
await waitForPrompt();
// use defaults for first package
expect(logs.mocks.log).toHaveBeenLastCalledWith('Please describe the changes for: foo (currently v1.0.0)');
await stdin.sendByChar('\n\n');
// cancel for second package
expect(logs.mocks.log).toHaveBeenLastCalledWith('Please describe the changes for: bar (currently v1.0.0)');
stdin.emitKey({ name: 'c', ctrl: true });
// nothing is returned
expect(await changeFilesPromise).toBeUndefined();
expect(logs.mocks.log).toHaveBeenLastCalledWith('Cancelled, no change files are written');
});
it('stops partway through if a response is invalid', async () => {
// this can only happen with custom prompts
const changeFilePrompt: ChangeFilePromptOptions = {
changePrompt: () => [{ type: 'text', name: 'type', message: 'enter any type' }],
};
const changeFilesPromise = promptForChange({
...defaultParams(),
changedPackages: ['foo', 'bar'],
packageInfos: makePackageInfos({
foo: { combinedOptions: { changeFilePrompt } },
bar: { combinedOptions: { changeFilePrompt } },
}),
});
await waitForPrompt();
// enter a valid type for foo
expect(logs.mocks.log).toHaveBeenLastCalledWith('Please describe the changes for: foo (currently v1.0.0)');
expect(stdout.getOutput()).toMatch(/enter any type/);
await stdin.sendByChar('patch\n');
// enter an invalid type for bar
expect(logs.mocks.log).toHaveBeenCalledWith('Please describe the changes for: bar (currently v1.0.0)');
await stdin.sendByChar('invalid\n');
expect(await changeFilesPromise).toBeUndefined();
expect(logs.mocks.error).toHaveBeenLastCalledWith('Prompt response contains invalid change type "invalid"');
});
});