@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Tests command registration, generate command, migration command,
|
|
5
|
+
* help command, and command execution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach, afterEach, mock, spyOn } from 'bun:test';
|
|
9
|
+
import { registry, defineCommand, type CommandHandler, type RegisteredCommand } from '../../src/cli/commands';
|
|
10
|
+
import type { CommandDefinition, ParsedArgs } from '../../src/cli/core/args';
|
|
11
|
+
import { CLIError, CLIErrorType, run } from '../../src/cli/index';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Command Registry Tests
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
describe('Command Registry', () => {
|
|
20
|
+
// Create a fresh registry for each test by using the existing registry
|
|
21
|
+
const testCommands: CommandDefinition[] = [];
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// Clear any test commands we've added
|
|
25
|
+
testCommands.length = 0;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('register', () => {
|
|
29
|
+
test('should register a command with definition and handler', () => {
|
|
30
|
+
const definition: CommandDefinition = {
|
|
31
|
+
name: 'test-cmd',
|
|
32
|
+
description: 'Test command',
|
|
33
|
+
};
|
|
34
|
+
const handler: CommandHandler = () => {};
|
|
35
|
+
|
|
36
|
+
defineCommand(definition, handler);
|
|
37
|
+
|
|
38
|
+
expect(registry.has('test-cmd')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('should register command with alias', () => {
|
|
42
|
+
const definition: CommandDefinition = {
|
|
43
|
+
name: 'test-alias-cmd',
|
|
44
|
+
alias: 'tac',
|
|
45
|
+
description: 'Test command with alias',
|
|
46
|
+
};
|
|
47
|
+
const handler: CommandHandler = () => {};
|
|
48
|
+
|
|
49
|
+
defineCommand(definition, handler);
|
|
50
|
+
|
|
51
|
+
expect(registry.has('test-alias-cmd')).toBe(true);
|
|
52
|
+
expect(registry.has('tac')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('get', () => {
|
|
57
|
+
test('should get registered command by name', () => {
|
|
58
|
+
const definition: CommandDefinition = {
|
|
59
|
+
name: 'get-test-cmd',
|
|
60
|
+
description: 'Test command',
|
|
61
|
+
};
|
|
62
|
+
const handler: CommandHandler = () => {};
|
|
63
|
+
defineCommand(definition, handler);
|
|
64
|
+
|
|
65
|
+
const cmd = registry.get('get-test-cmd');
|
|
66
|
+
expect(cmd).toBeDefined();
|
|
67
|
+
expect(cmd?.definition.name).toBe('get-test-cmd');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should get command by alias', () => {
|
|
71
|
+
const definition: CommandDefinition = {
|
|
72
|
+
name: 'get-alias-test',
|
|
73
|
+
alias: 'gat',
|
|
74
|
+
description: 'Test command',
|
|
75
|
+
};
|
|
76
|
+
const handler: CommandHandler = () => {};
|
|
77
|
+
defineCommand(definition, handler);
|
|
78
|
+
|
|
79
|
+
const cmd = registry.get('gat');
|
|
80
|
+
expect(cmd).toBeDefined();
|
|
81
|
+
expect(cmd?.definition.name).toBe('get-alias-test');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('should return undefined for non-existent command', () => {
|
|
85
|
+
const cmd = registry.get('non-existent-command-xyz');
|
|
86
|
+
expect(cmd).toBeUndefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('has', () => {
|
|
91
|
+
test('should return true for registered command', () => {
|
|
92
|
+
const definition: CommandDefinition = {
|
|
93
|
+
name: 'has-test-cmd',
|
|
94
|
+
description: 'Test command',
|
|
95
|
+
};
|
|
96
|
+
defineCommand(definition, () => {});
|
|
97
|
+
|
|
98
|
+
expect(registry.has('has-test-cmd')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('should return false for non-existent command', () => {
|
|
102
|
+
expect(registry.has('non-existent-cmd-xyz')).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('getAll', () => {
|
|
107
|
+
test('should return all command definitions', () => {
|
|
108
|
+
const commands = registry.getAll();
|
|
109
|
+
expect(Array.isArray(commands)).toBe(true);
|
|
110
|
+
expect(commands.length).toBeGreaterThan(0);
|
|
111
|
+
|
|
112
|
+
// Check that built-in commands are registered
|
|
113
|
+
const names = commands.map(c => c.name);
|
|
114
|
+
expect(names).toContain('generate');
|
|
115
|
+
expect(names).toContain('help');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('execute', () => {
|
|
120
|
+
test('should execute command handler', async () => {
|
|
121
|
+
let executed = false;
|
|
122
|
+
const definition: CommandDefinition = {
|
|
123
|
+
name: 'execute-test-cmd',
|
|
124
|
+
description: 'Test command',
|
|
125
|
+
};
|
|
126
|
+
defineCommand(definition, () => {
|
|
127
|
+
executed = true;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const args: ParsedArgs = {
|
|
131
|
+
command: 'execute-test-cmd',
|
|
132
|
+
positionals: [],
|
|
133
|
+
options: {},
|
|
134
|
+
flags: new Set(),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
await registry.execute('execute-test-cmd', args);
|
|
138
|
+
expect(executed).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('should throw for non-existent command', async () => {
|
|
142
|
+
const args: ParsedArgs = {
|
|
143
|
+
command: 'non-existent-cmd',
|
|
144
|
+
positionals: [],
|
|
145
|
+
options: {},
|
|
146
|
+
flags: new Set(),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
expect(async () => {
|
|
150
|
+
await registry.execute('non-existent-cmd', args);
|
|
151
|
+
}).toThrow('Unknown command: non-existent-cmd');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should pass arguments to handler', async () => {
|
|
155
|
+
let receivedArgs: ParsedArgs | null = null;
|
|
156
|
+
const definition: CommandDefinition = {
|
|
157
|
+
name: 'args-test-cmd',
|
|
158
|
+
description: 'Test command',
|
|
159
|
+
};
|
|
160
|
+
defineCommand(definition, (args) => {
|
|
161
|
+
receivedArgs = args;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const args: ParsedArgs = {
|
|
165
|
+
command: 'args-test-cmd',
|
|
166
|
+
positionals: ['pos1', 'pos2'],
|
|
167
|
+
options: { flag: true },
|
|
168
|
+
flags: new Set(['flag']),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
await registry.execute('args-test-cmd', args);
|
|
172
|
+
expect(receivedArgs).toEqual(args);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// Generate Command Tests
|
|
179
|
+
// ============================================================================
|
|
180
|
+
|
|
181
|
+
describe('Generate Command', () => {
|
|
182
|
+
const testDir = path.join(process.cwd(), 'test-temp-generate');
|
|
183
|
+
|
|
184
|
+
beforeEach(async () => {
|
|
185
|
+
// Clean up and create test directory
|
|
186
|
+
if (fs.existsSync(testDir)) {
|
|
187
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
190
|
+
|
|
191
|
+
// Create a minimal Bueno project structure
|
|
192
|
+
fs.mkdirSync(path.join(testDir, 'server'), { recursive: true });
|
|
193
|
+
fs.mkdirSync(path.join(testDir, 'server', 'modules'), { recursive: true });
|
|
194
|
+
fs.mkdirSync(path.join(testDir, 'server', 'common'), { recursive: true });
|
|
195
|
+
fs.mkdirSync(path.join(testDir, 'server', 'database', 'migrations'), { recursive: true });
|
|
196
|
+
|
|
197
|
+
// Create package.json
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
path.join(testDir, 'package.json'),
|
|
200
|
+
JSON.stringify({ name: 'test-project', dependencies: { bueno: 'latest' } }),
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
afterEach(async () => {
|
|
205
|
+
// Clean up test directory
|
|
206
|
+
if (fs.existsSync(testDir)) {
|
|
207
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('Generator Types', () => {
|
|
212
|
+
const generatorTypes = [
|
|
213
|
+
{ type: 'controller', short: 'c', expectedDir: 'modules' },
|
|
214
|
+
{ type: 'service', short: 's', expectedDir: 'modules' },
|
|
215
|
+
{ type: 'module', short: 'm', expectedDir: 'modules' },
|
|
216
|
+
{ type: 'guard', short: 'gu', expectedDir: 'common/guards' },
|
|
217
|
+
{ type: 'interceptor', short: 'i', expectedDir: 'common/interceptors' },
|
|
218
|
+
{ type: 'pipe', short: 'p', expectedDir: 'common/pipes' },
|
|
219
|
+
{ type: 'filter', short: 'f', expectedDir: 'common/filters' },
|
|
220
|
+
{ type: 'dto', short: 'd', expectedDir: 'modules' },
|
|
221
|
+
{ type: 'middleware', short: 'mw', expectedDir: 'common/middleware' },
|
|
222
|
+
{ type: 'migration', short: 'mi', expectedDir: 'database/migrations' },
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
for (const { type, short } of generatorTypes) {
|
|
226
|
+
test(`should have '${type}' generator registered with alias '${short}'`, () => {
|
|
227
|
+
expect(registry.has('generate')).toBe(true);
|
|
228
|
+
expect(registry.has('g')).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('Template Content', () => {
|
|
234
|
+
test('generate command should be registered', () => {
|
|
235
|
+
const cmd = registry.get('generate');
|
|
236
|
+
expect(cmd).toBeDefined();
|
|
237
|
+
expect(cmd?.definition.name).toBe('generate');
|
|
238
|
+
expect(cmd?.definition.alias).toBe('g');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('generate command should have correct positionals defined', () => {
|
|
242
|
+
const cmd = registry.get('generate');
|
|
243
|
+
expect(cmd?.definition.positionals).toBeDefined();
|
|
244
|
+
expect(cmd?.definition.positionals?.length).toBe(2);
|
|
245
|
+
expect(cmd?.definition.positionals?.[0]?.name).toBe('type');
|
|
246
|
+
expect(cmd?.definition.positionals?.[1]?.name).toBe('name');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('generate command should have options defined', () => {
|
|
250
|
+
const cmd = registry.get('generate');
|
|
251
|
+
expect(cmd?.definition.options).toBeDefined();
|
|
252
|
+
const optionNames = cmd?.definition.options?.map(o => o.name) ?? [];
|
|
253
|
+
expect(optionNames).toContain('module');
|
|
254
|
+
expect(optionNames).toContain('path');
|
|
255
|
+
expect(optionNames).toContain('dry-run');
|
|
256
|
+
expect(optionNames).toContain('force');
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// Migration Command Tests
|
|
263
|
+
// ============================================================================
|
|
264
|
+
|
|
265
|
+
describe('Migration Command', () => {
|
|
266
|
+
test('migration command should be registered', () => {
|
|
267
|
+
const cmd = registry.get('migration');
|
|
268
|
+
expect(cmd).toBeDefined();
|
|
269
|
+
expect(cmd?.definition.name).toBe('migration');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('migration command should have correct description', () => {
|
|
273
|
+
const cmd = registry.get('migration');
|
|
274
|
+
expect(cmd?.definition.description).toContain('migration');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Help Command Tests
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
describe('Help Command', () => {
|
|
283
|
+
test('help command should be registered', () => {
|
|
284
|
+
const cmd = registry.get('help');
|
|
285
|
+
expect(cmd).toBeDefined();
|
|
286
|
+
expect(cmd?.definition.name).toBe('help');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('help command should have correct description', () => {
|
|
290
|
+
const cmd = registry.get('help');
|
|
291
|
+
expect(cmd?.definition.description).toContain('help');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('help command should have optional command positional', () => {
|
|
295
|
+
const cmd = registry.get('help');
|
|
296
|
+
expect(cmd?.definition.positionals).toBeDefined();
|
|
297
|
+
expect(cmd?.definition.positionals?.[0]?.name).toBe('command');
|
|
298
|
+
expect(cmd?.definition.positionals?.[0]?.required).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('help command should have --all option', () => {
|
|
302
|
+
const cmd = registry.get('help');
|
|
303
|
+
const options = cmd?.definition.options ?? [];
|
|
304
|
+
const allOption = options.find(o => o.name === 'all');
|
|
305
|
+
expect(allOption).toBeDefined();
|
|
306
|
+
expect(allOption?.alias).toBe('a');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// Dev Command Tests
|
|
312
|
+
// ============================================================================
|
|
313
|
+
|
|
314
|
+
describe('Dev Command', () => {
|
|
315
|
+
test('dev command should be registered', () => {
|
|
316
|
+
const cmd = registry.get('dev');
|
|
317
|
+
expect(cmd).toBeDefined();
|
|
318
|
+
expect(cmd?.definition.name).toBe('dev');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('dev command should have port option', () => {
|
|
322
|
+
const cmd = registry.get('dev');
|
|
323
|
+
const options = cmd?.definition.options ?? [];
|
|
324
|
+
const portOption = options.find(o => o.name === 'port');
|
|
325
|
+
expect(portOption).toBeDefined();
|
|
326
|
+
expect(portOption?.alias).toBe('p');
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// Build Command Tests
|
|
332
|
+
// ============================================================================
|
|
333
|
+
|
|
334
|
+
describe('Build Command', () => {
|
|
335
|
+
test('build command should be registered', () => {
|
|
336
|
+
const cmd = registry.get('build');
|
|
337
|
+
expect(cmd).toBeDefined();
|
|
338
|
+
expect(cmd?.definition.name).toBe('build');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('build command should have target option', () => {
|
|
342
|
+
const cmd = registry.get('build');
|
|
343
|
+
const options = cmd?.definition.options ?? [];
|
|
344
|
+
const targetOption = options.find(o => o.name === 'target');
|
|
345
|
+
expect(targetOption).toBeDefined();
|
|
346
|
+
expect(targetOption?.alias).toBe('t');
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ============================================================================
|
|
351
|
+
// Start Command Tests
|
|
352
|
+
// ============================================================================
|
|
353
|
+
|
|
354
|
+
describe('Start Command', () => {
|
|
355
|
+
test('start command should be registered', () => {
|
|
356
|
+
const cmd = registry.get('start');
|
|
357
|
+
expect(cmd).toBeDefined();
|
|
358
|
+
expect(cmd?.definition.name).toBe('start');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ============================================================================
|
|
363
|
+
// New Command Tests
|
|
364
|
+
// ============================================================================
|
|
365
|
+
|
|
366
|
+
describe('New Command', () => {
|
|
367
|
+
test('new command should be registered', () => {
|
|
368
|
+
const cmd = registry.get('new');
|
|
369
|
+
expect(cmd).toBeDefined();
|
|
370
|
+
expect(cmd?.definition.name).toBe('new');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('new command should have template option', () => {
|
|
374
|
+
const cmd = registry.get('new');
|
|
375
|
+
const options = cmd?.definition.options ?? [];
|
|
376
|
+
const templateOption = options.find(o => o.name === 'template');
|
|
377
|
+
expect(templateOption).toBeDefined();
|
|
378
|
+
expect(templateOption?.alias).toBe('t');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('new command should have framework option', () => {
|
|
382
|
+
const cmd = registry.get('new');
|
|
383
|
+
const options = cmd?.definition.options ?? [];
|
|
384
|
+
const frameworkOption = options.find(o => o.name === 'framework');
|
|
385
|
+
expect(frameworkOption).toBeDefined();
|
|
386
|
+
expect(frameworkOption?.alias).toBe('f');
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ============================================================================
|
|
391
|
+
// CLI Error Tests
|
|
392
|
+
// ============================================================================
|
|
393
|
+
|
|
394
|
+
describe('CLIError', () => {
|
|
395
|
+
test('should create error with message and type', () => {
|
|
396
|
+
const error = new CLIError('Test error', CLIErrorType.INVALID_ARGS);
|
|
397
|
+
expect(error.message).toBe('Test error');
|
|
398
|
+
expect(error.type).toBe(CLIErrorType.INVALID_ARGS);
|
|
399
|
+
expect(error.name).toBe('CLIError');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('should have default exit code of 1', () => {
|
|
403
|
+
const error = new CLIError('Test error', CLIErrorType.INVALID_ARGS);
|
|
404
|
+
expect(error.exitCode).toBe(1);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('should accept custom exit code', () => {
|
|
408
|
+
const error = new CLIError('Test error', CLIErrorType.INVALID_ARGS, 2);
|
|
409
|
+
expect(error.exitCode).toBe(2);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('should be instance of Error', () => {
|
|
413
|
+
const error = new CLIError('Test error', CLIErrorType.INVALID_ARGS);
|
|
414
|
+
expect(error).toBeInstanceOf(Error);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe('CLIErrorType', () => {
|
|
419
|
+
test('should have all error types defined', () => {
|
|
420
|
+
expect(CLIErrorType.INVALID_ARGS).toBe('INVALID_ARGS');
|
|
421
|
+
expect(CLIErrorType.FILE_EXISTS).toBe('FILE_EXISTS');
|
|
422
|
+
expect(CLIErrorType.FILE_NOT_FOUND).toBe('FILE_NOT_FOUND');
|
|
423
|
+
expect(CLIErrorType.MODULE_NOT_FOUND).toBe('MODULE_NOT_FOUND');
|
|
424
|
+
expect(CLIErrorType.TEMPLATE_ERROR).toBe('TEMPLATE_ERROR');
|
|
425
|
+
expect(CLIErrorType.DATABASE_ERROR).toBe('DATABASE_ERROR');
|
|
426
|
+
expect(CLIErrorType.NETWORK_ERROR).toBe('NETWORK_ERROR');
|
|
427
|
+
expect(CLIErrorType.PERMISSION_ERROR).toBe('PERMISSION_ERROR');
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// ============================================================================
|
|
432
|
+
// CLI Run Function Tests
|
|
433
|
+
// ============================================================================
|
|
434
|
+
|
|
435
|
+
describe('run function', () => {
|
|
436
|
+
// Note: These tests verify the run function behavior without actually executing commands
|
|
437
|
+
|
|
438
|
+
test('should show version with --version flag', async () => {
|
|
439
|
+
const originalExit = process.exit;
|
|
440
|
+
const exitMock = mock((code: number) => {
|
|
441
|
+
throw new Error(`Exit with code ${code}`);
|
|
442
|
+
});
|
|
443
|
+
process.exit = exitMock as typeof process.exit;
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
await run(['--version']);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
// Expected to throw due to process.exit mock
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
process.exit = originalExit;
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('should show help with --help flag', async () => {
|
|
455
|
+
const originalExit = process.exit;
|
|
456
|
+
const exitMock = mock((code: number) => {
|
|
457
|
+
throw new Error(`Exit with code ${code}`);
|
|
458
|
+
});
|
|
459
|
+
process.exit = exitMock as typeof process.exit;
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
await run(['--help']);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
// Expected to throw due to process.exit mock
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
process.exit = originalExit;
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test('should show help with -h flag', async () => {
|
|
471
|
+
const originalExit = process.exit;
|
|
472
|
+
const exitMock = mock((code: number) => {
|
|
473
|
+
throw new Error(`Exit with code ${code}`);
|
|
474
|
+
});
|
|
475
|
+
process.exit = exitMock as typeof process.exit;
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
await run(['-h']);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
// Expected to throw due to process.exit mock
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
process.exit = originalExit;
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('should show help when no command provided', async () => {
|
|
487
|
+
const originalExit = process.exit;
|
|
488
|
+
const exitMock = mock((code: number) => {
|
|
489
|
+
throw new Error(`Exit with code ${code}`);
|
|
490
|
+
});
|
|
491
|
+
process.exit = exitMock as typeof process.exit;
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
await run([]);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
// Expected to throw due to process.exit mock
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
process.exit = originalExit;
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// ============================================================================
|
|
504
|
+
// Command Definition Validation Tests
|
|
505
|
+
// ============================================================================
|
|
506
|
+
|
|
507
|
+
describe('Command Definition Validation', () => {
|
|
508
|
+
test('all built-in commands should have required properties', () => {
|
|
509
|
+
const commands = registry.getAll();
|
|
510
|
+
|
|
511
|
+
for (const cmd of commands) {
|
|
512
|
+
expect(cmd.name).toBeDefined();
|
|
513
|
+
expect(typeof cmd.name).toBe('string');
|
|
514
|
+
expect(cmd.name.length).toBeGreaterThan(0);
|
|
515
|
+
expect(cmd.description).toBeDefined();
|
|
516
|
+
expect(typeof cmd.description).toBe('string');
|
|
517
|
+
expect(cmd.description.length).toBeGreaterThan(0);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test('all commands with options should have valid option definitions', () => {
|
|
522
|
+
const commands = registry.getAll();
|
|
523
|
+
|
|
524
|
+
for (const cmd of commands) {
|
|
525
|
+
if (cmd.options) {
|
|
526
|
+
for (const opt of cmd.options) {
|
|
527
|
+
expect(opt.name).toBeDefined();
|
|
528
|
+
expect(typeof opt.name).toBe('string');
|
|
529
|
+
expect(opt.type).toBeDefined();
|
|
530
|
+
expect(['string', 'boolean', 'number']).toContain(opt.type);
|
|
531
|
+
expect(opt.description).toBeDefined();
|
|
532
|
+
expect(typeof opt.description).toBe('string');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('all commands with positionals should have valid positional definitions', () => {
|
|
539
|
+
const commands = registry.getAll();
|
|
540
|
+
|
|
541
|
+
for (const cmd of commands) {
|
|
542
|
+
if (cmd.positionals) {
|
|
543
|
+
for (const pos of cmd.positionals) {
|
|
544
|
+
expect(pos.name).toBeDefined();
|
|
545
|
+
expect(typeof pos.name).toBe('string');
|
|
546
|
+
expect(pos.required).toBeDefined();
|
|
547
|
+
expect(typeof pos.required).toBe('boolean');
|
|
548
|
+
expect(pos.description).toBeDefined();
|
|
549
|
+
expect(typeof pos.description).toBe('string');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test('all command aliases should be unique', () => {
|
|
556
|
+
const commands = registry.getAll();
|
|
557
|
+
const aliases = new Set<string>();
|
|
558
|
+
|
|
559
|
+
for (const cmd of commands) {
|
|
560
|
+
if (cmd.alias) {
|
|
561
|
+
expect(aliases.has(cmd.alias)).toBe(false);
|
|
562
|
+
aliases.add(cmd.alias);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('all command names should be unique', () => {
|
|
568
|
+
const commands = registry.getAll();
|
|
569
|
+
const names = new Set<string>();
|
|
570
|
+
|
|
571
|
+
for (const cmd of commands) {
|
|
572
|
+
expect(names.has(cmd.name)).toBe(false);
|
|
573
|
+
names.add(cmd.name);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// ============================================================================
|
|
579
|
+
// Generator Alias Tests
|
|
580
|
+
// ============================================================================
|
|
581
|
+
|
|
582
|
+
describe('Generator Aliases', () => {
|
|
583
|
+
const aliasMap: Record<string, string> = {
|
|
584
|
+
c: 'controller',
|
|
585
|
+
s: 'service',
|
|
586
|
+
m: 'module',
|
|
587
|
+
gu: 'guard',
|
|
588
|
+
i: 'interceptor',
|
|
589
|
+
p: 'pipe',
|
|
590
|
+
f: 'filter',
|
|
591
|
+
d: 'dto',
|
|
592
|
+
mw: 'middleware',
|
|
593
|
+
mi: 'migration',
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
test('generate command should accept all type aliases', () => {
|
|
597
|
+
const cmd = registry.get('generate');
|
|
598
|
+
expect(cmd).toBeDefined();
|
|
599
|
+
|
|
600
|
+
// The generate command should handle these aliases internally
|
|
601
|
+
// We verify the command is properly registered
|
|
602
|
+
expect(cmd?.definition.positionals?.[0]?.name).toBe('type');
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// ============================================================================
|
|
607
|
+
// Spinner Tests
|
|
608
|
+
// ============================================================================
|
|
609
|
+
|
|
610
|
+
import { Spinner, spinner, ProgressBar, progressBar, runTasks } from '../../src/cli/core/spinner';
|
|
611
|
+
|
|
612
|
+
describe('Spinner', () => {
|
|
613
|
+
test('should create spinner with default options', () => {
|
|
614
|
+
const s = new Spinner();
|
|
615
|
+
expect(s).toBeDefined();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test('should create spinner with custom text', () => {
|
|
619
|
+
const s = new Spinner({ text: 'Loading...' });
|
|
620
|
+
expect(s).toBeDefined();
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test('should create spinner with custom color', () => {
|
|
624
|
+
const s = new Spinner({ text: 'Loading...', color: 'green' });
|
|
625
|
+
expect(s).toBeDefined();
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test('start should return spinner instance', () => {
|
|
629
|
+
const s = new Spinner({ text: 'Loading...' });
|
|
630
|
+
const result = s.start();
|
|
631
|
+
expect(result).toBe(s);
|
|
632
|
+
s.stop();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test('update should change text', () => {
|
|
636
|
+
const s = new Spinner({ text: 'Loading...' });
|
|
637
|
+
s.start();
|
|
638
|
+
const result = s.update('Still loading...');
|
|
639
|
+
expect(result).toBe(s);
|
|
640
|
+
s.stop();
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test('success should stop with success symbol', () => {
|
|
644
|
+
const s = new Spinner({ text: 'Loading...' });
|
|
645
|
+
s.start();
|
|
646
|
+
const result = s.success('Done!');
|
|
647
|
+
expect(result).toBe(s);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test('error should stop with error symbol', () => {
|
|
651
|
+
const s = new Spinner({ text: 'Loading...' });
|
|
652
|
+
s.start();
|
|
653
|
+
const result = s.error('Failed!');
|
|
654
|
+
expect(result).toBe(s);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test('warn should stop with warning symbol', () => {
|
|
658
|
+
const s = new Spinner({ text: 'Loading...' });
|
|
659
|
+
s.start();
|
|
660
|
+
const result = s.warn('Warning!');
|
|
661
|
+
expect(result).toBe(s);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test('info should stop with info symbol', () => {
|
|
665
|
+
const s = new Spinner({ text: 'Loading...' });
|
|
666
|
+
s.start();
|
|
667
|
+
const result = s.info('Info!');
|
|
668
|
+
expect(result).toBe(s);
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
describe('spinner factory', () => {
|
|
673
|
+
test('should create and start spinner', () => {
|
|
674
|
+
const s = spinner('Loading...');
|
|
675
|
+
expect(s).toBeInstanceOf(Spinner);
|
|
676
|
+
s.stop();
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
describe('ProgressBar', () => {
|
|
681
|
+
test('should create progress bar with required options', () => {
|
|
682
|
+
const pb = new ProgressBar({ total: 100 });
|
|
683
|
+
expect(pb).toBeDefined();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test('should create progress bar with custom width', () => {
|
|
687
|
+
const pb = new ProgressBar({ total: 100, width: 50 });
|
|
688
|
+
expect(pb).toBeDefined();
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
test('start should return progress bar instance', () => {
|
|
692
|
+
const pb = new ProgressBar({ total: 100 });
|
|
693
|
+
const result = pb.start();
|
|
694
|
+
expect(result).toBe(pb);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
test('update should change current value', () => {
|
|
698
|
+
const pb = new ProgressBar({ total: 100 });
|
|
699
|
+
pb.start();
|
|
700
|
+
const result = pb.update(50);
|
|
701
|
+
expect(result).toBe(pb);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
test('increment should increase current value', () => {
|
|
705
|
+
const pb = new ProgressBar({ total: 100 });
|
|
706
|
+
pb.start();
|
|
707
|
+
const result = pb.increment(5);
|
|
708
|
+
expect(result).toBe(pb);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
test('complete should set to total', () => {
|
|
712
|
+
const pb = new ProgressBar({ total: 100 });
|
|
713
|
+
pb.start();
|
|
714
|
+
const result = pb.complete();
|
|
715
|
+
expect(result).toBe(pb);
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
describe('progressBar factory', () => {
|
|
720
|
+
test('should create progress bar', () => {
|
|
721
|
+
const pb = progressBar({ total: 100 });
|
|
722
|
+
expect(pb).toBeInstanceOf(ProgressBar);
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe('runTasks', () => {
|
|
727
|
+
test('should run tasks sequentially', async () => {
|
|
728
|
+
const order: string[] = [];
|
|
729
|
+
|
|
730
|
+
await runTasks([
|
|
731
|
+
{
|
|
732
|
+
text: 'Task 1',
|
|
733
|
+
task: async () => {
|
|
734
|
+
order.push('1');
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
text: 'Task 2',
|
|
739
|
+
task: async () => {
|
|
740
|
+
order.push('2');
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
]);
|
|
744
|
+
|
|
745
|
+
expect(order).toEqual(['1', '2']);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test('should throw on task failure', async () => {
|
|
749
|
+
expect(async () => {
|
|
750
|
+
await runTasks([
|
|
751
|
+
{
|
|
752
|
+
text: 'Failing task',
|
|
753
|
+
task: async () => {
|
|
754
|
+
throw new Error('Task failed');
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
]);
|
|
758
|
+
}).toThrow();
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// ============================================================================
|
|
763
|
+
// Prompt Tests (Non-Interactive Mode)
|
|
764
|
+
// ============================================================================
|
|
765
|
+
|
|
766
|
+
import {
|
|
767
|
+
isInteractive,
|
|
768
|
+
prompt,
|
|
769
|
+
confirm,
|
|
770
|
+
select,
|
|
771
|
+
multiSelect,
|
|
772
|
+
number,
|
|
773
|
+
password,
|
|
774
|
+
} from '../../src/cli/core/prompt';
|
|
775
|
+
|
|
776
|
+
describe('Prompt Utilities (Non-Interactive Mode)', () => {
|
|
777
|
+
describe('isInteractive', () => {
|
|
778
|
+
test('should return boolean or undefined (falsy value)', () => {
|
|
779
|
+
const result = isInteractive();
|
|
780
|
+
// Note: isInteractive() returns process.stdin.isTTY && process.stdout.isTTY
|
|
781
|
+
// In non-TTY environments, this can be undefined (not false) due to && behavior
|
|
782
|
+
// We test that it's a falsy value in CI environments
|
|
783
|
+
expect(!result || typeof result === 'boolean').toBe(true);
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
describe('prompt (non-interactive fallback)', () => {
|
|
788
|
+
test('should return default value when not interactive', async () => {
|
|
789
|
+
// This test assumes non-interactive mode in CI
|
|
790
|
+
if (!isInteractive()) {
|
|
791
|
+
const result = await prompt('Enter value', { default: 'default-value' });
|
|
792
|
+
expect(result).toBe('default-value');
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test('should return empty string when no default and not interactive', async () => {
|
|
797
|
+
if (!isInteractive()) {
|
|
798
|
+
const result = await prompt('Enter value');
|
|
799
|
+
expect(result).toBe('');
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
describe('confirm (non-interactive fallback)', () => {
|
|
805
|
+
test('should return default value when not interactive', async () => {
|
|
806
|
+
if (!isInteractive()) {
|
|
807
|
+
const result = await confirm('Are you sure?', { default: true });
|
|
808
|
+
expect(result).toBe(true);
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test('should return false when no default and not interactive', async () => {
|
|
813
|
+
if (!isInteractive()) {
|
|
814
|
+
const result = await confirm('Are you sure?');
|
|
815
|
+
expect(result).toBe(false);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
describe('select (non-interactive fallback)', () => {
|
|
821
|
+
test('should return default value when not interactive', async () => {
|
|
822
|
+
if (!isInteractive()) {
|
|
823
|
+
const choices = [
|
|
824
|
+
{ value: 'a', name: 'Option A' },
|
|
825
|
+
{ value: 'b', name: 'Option B' },
|
|
826
|
+
];
|
|
827
|
+
const result = await select('Choose option', choices, { default: 'b' });
|
|
828
|
+
expect(result).toBe('b');
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test('should return first choice when no default and not interactive', async () => {
|
|
833
|
+
if (!isInteractive()) {
|
|
834
|
+
const choices = [
|
|
835
|
+
{ value: 'a', name: 'Option A' },
|
|
836
|
+
{ value: 'b', name: 'Option B' },
|
|
837
|
+
];
|
|
838
|
+
const result = await select('Choose option', choices);
|
|
839
|
+
expect(result).toBe('a');
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
describe('multiSelect (non-interactive fallback)', () => {
|
|
845
|
+
test('should return default values when not interactive', async () => {
|
|
846
|
+
if (!isInteractive()) {
|
|
847
|
+
const choices = [
|
|
848
|
+
{ value: 'a', name: 'Option A' },
|
|
849
|
+
{ value: 'b', name: 'Option B' },
|
|
850
|
+
];
|
|
851
|
+
const result = await multiSelect('Choose options', choices, { default: ['a'] });
|
|
852
|
+
expect(result).toEqual(['a']);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
test('should return empty array when no default and not interactive', async () => {
|
|
857
|
+
if (!isInteractive()) {
|
|
858
|
+
const choices = [
|
|
859
|
+
{ value: 'a', name: 'Option A' },
|
|
860
|
+
{ value: 'b', name: 'Option B' },
|
|
861
|
+
];
|
|
862
|
+
const result = await multiSelect('Choose options', choices);
|
|
863
|
+
expect(result).toEqual([]);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
describe('number (non-interactive fallback)', () => {
|
|
869
|
+
test('should return default value when not interactive', async () => {
|
|
870
|
+
if (!isInteractive()) {
|
|
871
|
+
const result = await number('Enter number', { default: '42' });
|
|
872
|
+
expect(result).toBe(42);
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
test('should return 0 when no default and not interactive', async () => {
|
|
877
|
+
if (!isInteractive()) {
|
|
878
|
+
const result = await number('Enter number');
|
|
879
|
+
expect(result).toBe(0);
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
describe('password (non-interactive fallback)', () => {
|
|
885
|
+
test('should return empty string when not interactive', async () => {
|
|
886
|
+
if (!isInteractive()) {
|
|
887
|
+
const result = await password('Enter password');
|
|
888
|
+
expect(result).toBe('');
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
});
|