@vibedx/vibekit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/assets/config.yml +35 -0
- package/assets/default.md +47 -0
- package/assets/instructions/README.md +46 -0
- package/assets/instructions/claude.md +83 -0
- package/assets/instructions/codex.md +19 -0
- package/index.js +106 -0
- package/package.json +90 -0
- package/src/commands/close/index.js +66 -0
- package/src/commands/close/index.test.js +235 -0
- package/src/commands/get-started/index.js +138 -0
- package/src/commands/get-started/index.test.js +246 -0
- package/src/commands/init/index.js +51 -0
- package/src/commands/init/index.test.js +159 -0
- package/src/commands/link/index.js +395 -0
- package/src/commands/link/index.test.js +28 -0
- package/src/commands/lint/index.js +657 -0
- package/src/commands/lint/index.test.js +569 -0
- package/src/commands/list/index.js +131 -0
- package/src/commands/list/index.test.js +153 -0
- package/src/commands/new/index.js +305 -0
- package/src/commands/new/index.test.js +256 -0
- package/src/commands/refine/index.js +741 -0
- package/src/commands/refine/index.test.js +28 -0
- package/src/commands/review/index.js +957 -0
- package/src/commands/review/index.test.js +193 -0
- package/src/commands/start/index.js +180 -0
- package/src/commands/start/index.test.js +88 -0
- package/src/commands/unlink/index.js +123 -0
- package/src/commands/unlink/index.test.js +22 -0
- package/src/utils/arrow-select.js +233 -0
- package/src/utils/cli.js +489 -0
- package/src/utils/cli.test.js +9 -0
- package/src/utils/git.js +146 -0
- package/src/utils/git.test.js +330 -0
- package/src/utils/index.js +193 -0
- package/src/utils/index.test.js +375 -0
- package/src/utils/prompts.js +47 -0
- package/src/utils/prompts.test.js +165 -0
- package/src/utils/test-helpers.js +492 -0
- package/src/utils/ticket.js +423 -0
- package/src/utils/ticket.test.js +190 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import {
|
|
5
|
+
createTempDir,
|
|
6
|
+
cleanupTempDir,
|
|
7
|
+
mockProcessCwd,
|
|
8
|
+
createMockVibeProject
|
|
9
|
+
} from './test-helpers.js';
|
|
10
|
+
import {
|
|
11
|
+
getTicketsDir,
|
|
12
|
+
getConfig,
|
|
13
|
+
getNextTicketId,
|
|
14
|
+
createSlug,
|
|
15
|
+
createFullSlug
|
|
16
|
+
} from './index.js';
|
|
17
|
+
|
|
18
|
+
describe('utils/index.js', () => {
|
|
19
|
+
let tempDir;
|
|
20
|
+
let restoreCwd;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
tempDir = createTempDir('utils-test');
|
|
24
|
+
restoreCwd = mockProcessCwd(tempDir);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
restoreCwd();
|
|
29
|
+
cleanupTempDir(tempDir);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('getTicketsDir', () => {
|
|
33
|
+
it('should return default tickets directory when no config exists', () => {
|
|
34
|
+
// Act
|
|
35
|
+
const result = getTicketsDir();
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect(result).toBe(path.join(tempDir, '.vibe', 'tickets'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return custom tickets directory from config', () => {
|
|
42
|
+
// Arrange
|
|
43
|
+
createMockVibeProject(tempDir, {
|
|
44
|
+
configData: `project:
|
|
45
|
+
name: Test Project
|
|
46
|
+
|
|
47
|
+
tickets:
|
|
48
|
+
path: custom/tickets
|
|
49
|
+
`
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Act
|
|
53
|
+
const result = getTicketsDir();
|
|
54
|
+
|
|
55
|
+
// Assert
|
|
56
|
+
expect(result).toBe(path.join(tempDir, 'custom', 'tickets'));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle relative paths in config', () => {
|
|
60
|
+
// Arrange
|
|
61
|
+
createMockVibeProject(tempDir, {
|
|
62
|
+
configData: `tickets:
|
|
63
|
+
path: ./my-tickets
|
|
64
|
+
`
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Act
|
|
68
|
+
const result = getTicketsDir();
|
|
69
|
+
|
|
70
|
+
// Assert
|
|
71
|
+
expect(result).toBe(path.join(tempDir, 'my-tickets'));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should fallback to default when config path is invalid', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
createMockVibeProject(tempDir, {
|
|
77
|
+
configData: `tickets:
|
|
78
|
+
path: null
|
|
79
|
+
`
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Act
|
|
83
|
+
const result = getTicketsDir();
|
|
84
|
+
|
|
85
|
+
// Assert
|
|
86
|
+
expect(result).toBe(path.join(tempDir, '.vibe', 'tickets'));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('getConfig', () => {
|
|
91
|
+
it('should return empty object when no config exists', () => {
|
|
92
|
+
// Act
|
|
93
|
+
const result = getConfig();
|
|
94
|
+
|
|
95
|
+
// Assert
|
|
96
|
+
expect(result).toEqual({});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should return parsed config when file exists', () => {
|
|
100
|
+
// Arrange
|
|
101
|
+
createMockVibeProject(tempDir, {
|
|
102
|
+
configData: `project:
|
|
103
|
+
name: Test Project
|
|
104
|
+
version: 1.0.0
|
|
105
|
+
|
|
106
|
+
tickets:
|
|
107
|
+
priority_options:
|
|
108
|
+
- low
|
|
109
|
+
- medium
|
|
110
|
+
- high
|
|
111
|
+
`
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Act
|
|
115
|
+
const result = getConfig();
|
|
116
|
+
|
|
117
|
+
// Assert
|
|
118
|
+
expect(result.project.name).toBe('Test Project');
|
|
119
|
+
expect(result.project.version).toBe('1.0.0');
|
|
120
|
+
expect(result.tickets.priority_options).toContain('medium');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should handle malformed YAML gracefully', () => {
|
|
124
|
+
// Arrange
|
|
125
|
+
const vibeDir = path.join(tempDir, '.vibe');
|
|
126
|
+
fs.mkdirSync(vibeDir, { recursive: true });
|
|
127
|
+
fs.writeFileSync(path.join(vibeDir, 'config.yml'), 'invalid: yaml: content: [', 'utf-8');
|
|
128
|
+
|
|
129
|
+
// Mock console.error to suppress expected error output
|
|
130
|
+
const originalError = console.error;
|
|
131
|
+
console.error = jest.fn();
|
|
132
|
+
|
|
133
|
+
// Act
|
|
134
|
+
const result = getConfig();
|
|
135
|
+
|
|
136
|
+
// Assert
|
|
137
|
+
expect(result).toEqual({});
|
|
138
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
139
|
+
expect.stringContaining('❌ Error reading config:')
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Restore console.error
|
|
143
|
+
console.error = originalError;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should handle non-object YAML content', () => {
|
|
147
|
+
// Arrange
|
|
148
|
+
const vibeDir = path.join(tempDir, '.vibe');
|
|
149
|
+
fs.mkdirSync(vibeDir, { recursive: true });
|
|
150
|
+
fs.writeFileSync(path.join(vibeDir, 'config.yml'), 'just a string', 'utf-8');
|
|
151
|
+
|
|
152
|
+
// Act
|
|
153
|
+
const result = getConfig();
|
|
154
|
+
|
|
155
|
+
// Assert
|
|
156
|
+
expect(result).toEqual({});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('getNextTicketId', () => {
|
|
161
|
+
it('should return TKT-001 when no tickets exist', () => {
|
|
162
|
+
// Arrange
|
|
163
|
+
createMockVibeProject(tempDir);
|
|
164
|
+
|
|
165
|
+
// Act
|
|
166
|
+
const result = getNextTicketId();
|
|
167
|
+
|
|
168
|
+
// Assert
|
|
169
|
+
expect(result).toBe('TKT-001');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should return TKT-001 when tickets directory does not exist', () => {
|
|
173
|
+
// Act
|
|
174
|
+
const result = getNextTicketId();
|
|
175
|
+
|
|
176
|
+
// Assert
|
|
177
|
+
expect(result).toBe('TKT-001');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should increment from existing tickets', () => {
|
|
181
|
+
// Arrange
|
|
182
|
+
createMockVibeProject(tempDir, {
|
|
183
|
+
withTickets: [
|
|
184
|
+
{ id: 'TKT-001', title: 'First ticket' },
|
|
185
|
+
{ id: 'TKT-002', title: 'Second ticket' },
|
|
186
|
+
{ id: 'TKT-005', title: 'Fifth ticket' }
|
|
187
|
+
]
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Act
|
|
191
|
+
const result = getNextTicketId();
|
|
192
|
+
|
|
193
|
+
// Assert
|
|
194
|
+
expect(result).toBe('TKT-006');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle non-ticket files in directory', () => {
|
|
198
|
+
// Arrange
|
|
199
|
+
const vibeProject = createMockVibeProject(tempDir, {
|
|
200
|
+
withTickets: [{ id: 'TKT-003', title: 'Third ticket' }]
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Add non-ticket files
|
|
204
|
+
fs.writeFileSync(path.join(vibeProject.ticketsDir, 'README.md'), 'Some readme', 'utf-8');
|
|
205
|
+
fs.writeFileSync(path.join(vibeProject.ticketsDir, 'notes.txt'), 'Some notes', 'utf-8');
|
|
206
|
+
|
|
207
|
+
// Act
|
|
208
|
+
const result = getNextTicketId();
|
|
209
|
+
|
|
210
|
+
// Assert
|
|
211
|
+
expect(result).toBe('TKT-004');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle malformed ticket filenames', () => {
|
|
215
|
+
// Arrange
|
|
216
|
+
const vibeProject = createMockVibeProject(tempDir, {
|
|
217
|
+
withTickets: [{ id: 'TKT-002', title: 'Valid ticket' }]
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Add invalid ticket files
|
|
221
|
+
fs.writeFileSync(path.join(vibeProject.ticketsDir, 'TKT-invalid.md'), 'Invalid', 'utf-8');
|
|
222
|
+
fs.writeFileSync(path.join(vibeProject.ticketsDir, 'TKT-.md'), 'Empty number', 'utf-8');
|
|
223
|
+
|
|
224
|
+
// Act
|
|
225
|
+
const result = getNextTicketId();
|
|
226
|
+
|
|
227
|
+
// Assert
|
|
228
|
+
expect(result).toBe('TKT-003');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('createSlug', () => {
|
|
233
|
+
it('should create basic slug from title', () => {
|
|
234
|
+
// Act
|
|
235
|
+
const result = createSlug('Fix User Authentication Bug');
|
|
236
|
+
|
|
237
|
+
// Assert
|
|
238
|
+
expect(result).toBe('fix-user-authentication-bug');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should handle empty or null title', () => {
|
|
242
|
+
// Act & Assert
|
|
243
|
+
expect(createSlug('')).toBe('');
|
|
244
|
+
expect(createSlug(null)).toBe('');
|
|
245
|
+
expect(createSlug(undefined)).toBe('');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should handle non-string input', () => {
|
|
249
|
+
// Act & Assert
|
|
250
|
+
expect(createSlug(123)).toBe('');
|
|
251
|
+
expect(createSlug({})).toBe('');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should remove special characters', () => {
|
|
255
|
+
// Act
|
|
256
|
+
const result = createSlug('Add @user #login with $pecial ch@rs!');
|
|
257
|
+
|
|
258
|
+
// Assert
|
|
259
|
+
expect(result).toBe('add-user-login-with-pecial');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should limit words based on config', () => {
|
|
263
|
+
// Arrange
|
|
264
|
+
const config = {
|
|
265
|
+
tickets: {
|
|
266
|
+
slug: {
|
|
267
|
+
word_limit: 3
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Act
|
|
273
|
+
const result = createSlug('This is a very long title with many words', config);
|
|
274
|
+
|
|
275
|
+
// Assert
|
|
276
|
+
expect(result).toBe('this-is-a');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should limit length based on config', () => {
|
|
280
|
+
// Arrange
|
|
281
|
+
const config = {
|
|
282
|
+
tickets: {
|
|
283
|
+
slug: {
|
|
284
|
+
max_length: 10
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Act
|
|
290
|
+
const result = createSlug('Very long title that should be truncated', config);
|
|
291
|
+
|
|
292
|
+
// Assert
|
|
293
|
+
expect(result).toBe('very-long');
|
|
294
|
+
expect(result.length).toBeLessThanOrEqual(10);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should use default values for invalid config', () => {
|
|
298
|
+
// Arrange
|
|
299
|
+
const config = {
|
|
300
|
+
tickets: {
|
|
301
|
+
slug: {
|
|
302
|
+
max_length: -5,
|
|
303
|
+
word_limit: 0
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Act
|
|
309
|
+
const result = createSlug('Test title', config);
|
|
310
|
+
|
|
311
|
+
// Assert
|
|
312
|
+
expect(result).toBe('t'); // Limited by clamped max_length
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should return fallback for empty result', () => {
|
|
316
|
+
// Act
|
|
317
|
+
const result = createSlug('!@#$%^&*()');
|
|
318
|
+
|
|
319
|
+
// Assert
|
|
320
|
+
expect(result).toBe('untitled');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should handle multiple spaces and trim properly', () => {
|
|
324
|
+
// Act
|
|
325
|
+
const result = createSlug(' Multiple Spaces Between Words ');
|
|
326
|
+
|
|
327
|
+
// Assert
|
|
328
|
+
expect(result).toBe('multiple-spaces-between-words');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('createFullSlug', () => {
|
|
333
|
+
it('should combine ticket ID and slug', () => {
|
|
334
|
+
// Act
|
|
335
|
+
const result = createFullSlug('TKT-001', 'fix-auth-bug');
|
|
336
|
+
|
|
337
|
+
// Assert
|
|
338
|
+
expect(result).toBe('TKT-001-fix-auth-bug');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should handle empty ticket ID', () => {
|
|
342
|
+
// Act & Assert
|
|
343
|
+
expect(createFullSlug('', 'test-slug')).toBe('');
|
|
344
|
+
expect(createFullSlug(null, 'test-slug')).toBe('');
|
|
345
|
+
expect(createFullSlug(undefined, 'test-slug')).toBe('');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should handle empty slug text', () => {
|
|
349
|
+
// Act & Assert
|
|
350
|
+
expect(createFullSlug('TKT-001', '')).toBe('TKT-001');
|
|
351
|
+
expect(createFullSlug('TKT-001', null)).toBe('TKT-001');
|
|
352
|
+
expect(createFullSlug('TKT-001', undefined)).toBe('TKT-001');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should handle both empty inputs', () => {
|
|
356
|
+
// Act & Assert
|
|
357
|
+
expect(createFullSlug('', '')).toBe('');
|
|
358
|
+
expect(createFullSlug(null, null)).toBe('');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should trim whitespace from inputs', () => {
|
|
362
|
+
// Act
|
|
363
|
+
const result = createFullSlug(' TKT-001 ', ' test-slug ');
|
|
364
|
+
|
|
365
|
+
// Assert
|
|
366
|
+
expect(result).toBe('TKT-001-test-slug');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should handle non-string inputs', () => {
|
|
370
|
+
// Act & Assert
|
|
371
|
+
expect(createFullSlug(123, 'slug')).toBe('');
|
|
372
|
+
expect(createFullSlug('TKT-001', 123)).toBe('TKT-001');
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { input, select, confirm, spinner } from './cli.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced prompt with emoji and consistent formatting
|
|
5
|
+
* @param {string} emoji - Emoji to display with the prompt
|
|
6
|
+
* @param {string} message - The prompt message
|
|
7
|
+
* @param {Object} options - Configuration options
|
|
8
|
+
* @param {string|null} options.defaultValue - Default value if user provides no input
|
|
9
|
+
* @param {boolean} options.required - Whether input is required
|
|
10
|
+
* @param {string} options.type - Type of prompt ('input' or 'confirm')
|
|
11
|
+
* @returns {Promise<string|boolean>} User's response
|
|
12
|
+
*/
|
|
13
|
+
export async function emojiPrompt(emoji, message, options = {}) {
|
|
14
|
+
const { defaultValue = null, required = false, type = 'input' } = options;
|
|
15
|
+
const enhancedMessage = `${emoji} ${message}`;
|
|
16
|
+
|
|
17
|
+
if (type === 'confirm') {
|
|
18
|
+
return confirm(enhancedMessage, defaultValue);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return input(enhancedMessage, { defaultValue, required });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Confirmation prompt with emoji
|
|
26
|
+
* @param {string} emoji - Emoji to display
|
|
27
|
+
* @param {string} message - Confirmation message
|
|
28
|
+
* @param {boolean} defaultValue - Default confirmation value
|
|
29
|
+
* @returns {Promise<boolean>} User's confirmation
|
|
30
|
+
*/
|
|
31
|
+
export async function confirmPrompt(emoji, message, defaultValue = true) {
|
|
32
|
+
return emojiPrompt(emoji, message, { type: 'confirm', defaultValue });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Input prompt with emoji
|
|
37
|
+
* @param {string} emoji - Emoji to display
|
|
38
|
+
* @param {string} message - Input message
|
|
39
|
+
* @param {Object} options - Input options
|
|
40
|
+
* @returns {Promise<string>} User's input
|
|
41
|
+
*/
|
|
42
|
+
export async function inputPrompt(emoji, message, options = {}) {
|
|
43
|
+
return emojiPrompt(emoji, message, options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Re-export core CLI functions for convenience
|
|
47
|
+
export { input, select, confirm, spinner };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { describe, it, expect, jest } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
// Mock the CLI module
|
|
4
|
+
jest.unstable_mockModule('./cli.js', () => ({
|
|
5
|
+
input: jest.fn(),
|
|
6
|
+
select: jest.fn(),
|
|
7
|
+
confirm: jest.fn(),
|
|
8
|
+
spinner: jest.fn()
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('prompts utilities', () => {
|
|
12
|
+
let mockCli;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
// Import the mocked CLI module
|
|
16
|
+
mockCli = await import('./cli.js');
|
|
17
|
+
|
|
18
|
+
// Reset all mocks
|
|
19
|
+
Object.values(mockCli).forEach(mock => mock.mockClear());
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('emojiPrompt', () => {
|
|
23
|
+
it('should call input with emoji-enhanced message', async () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
mockCli.input.mockResolvedValue('test response');
|
|
26
|
+
const { emojiPrompt } = await import('./prompts.js');
|
|
27
|
+
|
|
28
|
+
// Act
|
|
29
|
+
const result = await emojiPrompt('🎯', 'What is your goal?', { defaultValue: 'none' });
|
|
30
|
+
|
|
31
|
+
// Assert
|
|
32
|
+
expect(mockCli.input).toHaveBeenCalledWith('🎯 What is your goal?', { defaultValue: 'none', required: false });
|
|
33
|
+
expect(result).toBe('test response');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should call confirm when type is confirm', async () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
mockCli.confirm.mockResolvedValue(true);
|
|
39
|
+
const { emojiPrompt } = await import('./prompts.js');
|
|
40
|
+
|
|
41
|
+
// Act
|
|
42
|
+
const result = await emojiPrompt('❓', 'Are you sure?', { type: 'confirm', defaultValue: false });
|
|
43
|
+
|
|
44
|
+
// Assert
|
|
45
|
+
expect(mockCli.confirm).toHaveBeenCalledWith('❓ Are you sure?', false);
|
|
46
|
+
expect(result).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle required input option', async () => {
|
|
50
|
+
// Arrange
|
|
51
|
+
mockCli.input.mockResolvedValue('required response');
|
|
52
|
+
const { emojiPrompt } = await import('./prompts.js');
|
|
53
|
+
|
|
54
|
+
// Act
|
|
55
|
+
const result = await emojiPrompt('⚠️', 'Required field:', { required: true });
|
|
56
|
+
|
|
57
|
+
// Assert
|
|
58
|
+
expect(mockCli.input).toHaveBeenCalledWith('⚠️ Required field:', { defaultValue: null, required: true });
|
|
59
|
+
expect(result).toBe('required response');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should use default options when none provided', async () => {
|
|
63
|
+
// Arrange
|
|
64
|
+
mockCli.input.mockResolvedValue('default response');
|
|
65
|
+
const { emojiPrompt } = await import('./prompts.js');
|
|
66
|
+
|
|
67
|
+
// Act
|
|
68
|
+
const result = await emojiPrompt('📝', 'Enter text:');
|
|
69
|
+
|
|
70
|
+
// Assert
|
|
71
|
+
expect(mockCli.input).toHaveBeenCalledWith('📝 Enter text:', { defaultValue: null, required: false });
|
|
72
|
+
expect(result).toBe('default response');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('confirmPrompt', () => {
|
|
77
|
+
it('should call emojiPrompt with confirm type', async () => {
|
|
78
|
+
// Arrange
|
|
79
|
+
mockCli.confirm.mockResolvedValue(true);
|
|
80
|
+
const { confirmPrompt } = await import('./prompts.js');
|
|
81
|
+
|
|
82
|
+
// Act
|
|
83
|
+
const result = await confirmPrompt('🤔', 'Do you want to continue?');
|
|
84
|
+
|
|
85
|
+
// Assert
|
|
86
|
+
expect(mockCli.confirm).toHaveBeenCalledWith('🤔 Do you want to continue?', true);
|
|
87
|
+
expect(result).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should use custom default value', async () => {
|
|
91
|
+
// Arrange
|
|
92
|
+
mockCli.confirm.mockResolvedValue(false);
|
|
93
|
+
const { confirmPrompt } = await import('./prompts.js');
|
|
94
|
+
|
|
95
|
+
// Act
|
|
96
|
+
const result = await confirmPrompt('🛑', 'Cancel operation?', false);
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(mockCli.confirm).toHaveBeenCalledWith('🛑 Cancel operation?', false);
|
|
100
|
+
expect(result).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('inputPrompt', () => {
|
|
105
|
+
it('should call emojiPrompt with input type', async () => {
|
|
106
|
+
// Arrange
|
|
107
|
+
mockCli.input.mockResolvedValue('user input');
|
|
108
|
+
const { inputPrompt } = await import('./prompts.js');
|
|
109
|
+
|
|
110
|
+
// Act
|
|
111
|
+
const result = await inputPrompt('💬', 'Enter your message:', { defaultValue: 'hello' });
|
|
112
|
+
|
|
113
|
+
// Assert
|
|
114
|
+
expect(mockCli.input).toHaveBeenCalledWith('💬 Enter your message:', { defaultValue: 'hello', required: false });
|
|
115
|
+
expect(result).toBe('user input');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle empty options', async () => {
|
|
119
|
+
// Arrange
|
|
120
|
+
mockCli.input.mockResolvedValue('empty options response');
|
|
121
|
+
const { inputPrompt } = await import('./prompts.js');
|
|
122
|
+
|
|
123
|
+
// Act
|
|
124
|
+
const result = await inputPrompt('📋', 'Enter data:');
|
|
125
|
+
|
|
126
|
+
// Assert
|
|
127
|
+
expect(mockCli.input).toHaveBeenCalledWith('📋 Enter data:', { defaultValue: null, required: false });
|
|
128
|
+
expect(result).toBe('empty options response');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('re-exported functions', () => {
|
|
133
|
+
it('should re-export input function', async () => {
|
|
134
|
+
// Act
|
|
135
|
+
const { input } = await import('./prompts.js');
|
|
136
|
+
|
|
137
|
+
// Assert
|
|
138
|
+
expect(input).toBe(mockCli.input);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should re-export select function', async () => {
|
|
142
|
+
// Act
|
|
143
|
+
const { select } = await import('./prompts.js');
|
|
144
|
+
|
|
145
|
+
// Assert
|
|
146
|
+
expect(select).toBe(mockCli.select);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should re-export confirm function', async () => {
|
|
150
|
+
// Act
|
|
151
|
+
const { confirm } = await import('./prompts.js');
|
|
152
|
+
|
|
153
|
+
// Assert
|
|
154
|
+
expect(confirm).toBe(mockCli.confirm);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should re-export spinner function', async () => {
|
|
158
|
+
// Act
|
|
159
|
+
const { spinner } = await import('./prompts.js');
|
|
160
|
+
|
|
161
|
+
// Assert
|
|
162
|
+
expect(spinner).toBe(mockCli.spinner);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|