@tkeron/commands 0.3.1 → 0.4.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.
@@ -0,0 +1,492 @@
1
+ import { beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
2
+ import type {
3
+ CommandFactory,
4
+ Commands,
5
+ CommandsCollection,
6
+ } from "../src/types.js";
7
+ import {
8
+ getAddAlias,
9
+ getAddDescription,
10
+ getAddFooterText,
11
+ getAddHeaderText,
12
+ getAddOption,
13
+ getAddPositionedArgument,
14
+ getCommands,
15
+ getGetHelpLine,
16
+ getSetCallback,
17
+ initCommands,
18
+ initHelpAndVersion,
19
+ } from "../src/getCommandsFuncs.js";
20
+
21
+ describe("main", () => {
22
+ let commandsCollection: CommandsCollection;
23
+ let commands: Commands;
24
+ let commandFactory: CommandFactory;
25
+
26
+ beforeEach(() => {
27
+ commandsCollection = {};
28
+ commandFactory = <CommandFactory>(<unknown>{
29
+ commands: undefined,
30
+ name: undefined,
31
+ addAlias: undefined,
32
+ addOption: undefined,
33
+ addPositionedArgument: undefined,
34
+ addDescription: undefined,
35
+ setCallback: undefined,
36
+ });
37
+ commands = initCommands(commandsCollection, undefined, commandFactory);
38
+ });
39
+
40
+ it("getCommands", () => {
41
+ const commands = getCommands();
42
+ expect(commands).toBeTruthy();
43
+ });
44
+ it("initHelpAndVersion", () => {
45
+ expect(commandsCollection).not.toHaveProperty("help");
46
+ expect(commandsCollection).not.toHaveProperty("version");
47
+ const { helpCallback, versionCallback } = initHelpAndVersion(
48
+ commands,
49
+ commandsCollection,
50
+ );
51
+ expect(commandsCollection).toHaveProperty("help");
52
+ expect(commandsCollection).toHaveProperty("version");
53
+
54
+ const logMock = spyOn(console, "log").mockImplementation(() => {});
55
+ expect(helpCallback).not.toThrow();
56
+ expect(versionCallback).not.toThrow();
57
+ logMock.mockRestore();
58
+ });
59
+ it("getAddOption", () => {
60
+ commands.addCommand("test");
61
+ const addOption = getAddOption(commandFactory, commandsCollection);
62
+ addOption("op001");
63
+ const { test } = commandsCollection;
64
+ expect(test.options).toContain("op001");
65
+ });
66
+ it("getAddPositionedArgument", () => {
67
+ commands.addCommand("test");
68
+ const addPositionedArgument = getAddPositionedArgument(
69
+ commandFactory,
70
+ commandsCollection,
71
+ );
72
+
73
+ addPositionedArgument("pos001");
74
+ const { test } = commandsCollection;
75
+
76
+ expect(test.positionedArguments).toContain("pos001");
77
+ });
78
+ it("getAddHeaderText", () => {
79
+ const addHeaderText = getAddHeaderText(commands);
80
+ const txt = "test header text...";
81
+ addHeaderText(txt);
82
+ expect(commands.headerText).toBe(txt);
83
+ });
84
+ it("getAddFooterText", () => {
85
+ const addFooterText = getAddFooterText(commands);
86
+ const txt = "test footer text...";
87
+ addFooterText(txt);
88
+ expect(commands.footerText).toBe(txt);
89
+ });
90
+ it("getGetHelpLine", () => {
91
+ commands.addCommand("test");
92
+ const { test } = commandsCollection;
93
+ const getHelpLine = getGetHelpLine(test);
94
+ const hl50 = getHelpLine();
95
+ expect(hl50).toBe("test ............................................ \n");
96
+ });
97
+ it("should set commandFactory.commands when falsy", () => {
98
+ commandFactory.commands = <() => Commands>(<unknown>undefined);
99
+ initCommands(commandsCollection, commands, commandFactory);
100
+ expect(commandFactory.commands).toBeTruthy();
101
+ expect(commandFactory.commands).not.toThrow();
102
+ });
103
+ });
104
+
105
+ describe("getAddAlias", () => {
106
+ let commandsCollection: CommandsCollection;
107
+ let commands: Commands;
108
+ let commandFactory: CommandFactory;
109
+
110
+ beforeEach(() => {
111
+ commandsCollection = {};
112
+ commandFactory = <CommandFactory>(<unknown>{
113
+ commands: undefined,
114
+ name: undefined,
115
+ addAlias: undefined,
116
+ addOption: undefined,
117
+ addPositionedArgument: undefined,
118
+ addDescription: undefined,
119
+ setCallback: undefined,
120
+ });
121
+ commands = initCommands(commandsCollection, undefined, commandFactory);
122
+ });
123
+
124
+ it("should add alias to command aliases array", () => {
125
+ commands.addCommand("test");
126
+ const addAlias = getAddAlias(commandFactory, commandsCollection);
127
+ addAlias("t");
128
+ expect(commandsCollection.test.aliases).toContain("t");
129
+ });
130
+
131
+ it("should register alias as key in commandsCollection", () => {
132
+ commands.addCommand("test");
133
+ const addAlias = getAddAlias(commandFactory, commandsCollection);
134
+ addAlias("t");
135
+ expect(commandsCollection.t).toBe(commandsCollection.test);
136
+ });
137
+
138
+ it("should support multiple aliases", () => {
139
+ commands.addCommand("test");
140
+ const addAlias = getAddAlias(commandFactory, commandsCollection);
141
+ addAlias("t");
142
+ addAlias("--test");
143
+ expect(commandsCollection.test.aliases).toEqual(["t", "--test"]);
144
+ expect(commandsCollection.t).toBe(commandsCollection.test);
145
+ expect(commandsCollection["--test"]).toBe(commandsCollection.test);
146
+ });
147
+
148
+ it("should return commandFactory for chaining", () => {
149
+ commands.addCommand("test");
150
+ const addAlias = getAddAlias(commandFactory, commandsCollection);
151
+ const result = addAlias("t");
152
+ expect(result).toBe(commandFactory);
153
+ });
154
+ });
155
+
156
+ describe("getAddDescription", () => {
157
+ let commandsCollection: CommandsCollection;
158
+ let commands: Commands;
159
+ let commandFactory: CommandFactory;
160
+
161
+ beforeEach(() => {
162
+ commandsCollection = {};
163
+ commandFactory = <CommandFactory>(<unknown>{
164
+ commands: undefined,
165
+ name: undefined,
166
+ addAlias: undefined,
167
+ addOption: undefined,
168
+ addPositionedArgument: undefined,
169
+ addDescription: undefined,
170
+ setCallback: undefined,
171
+ });
172
+ commands = initCommands(commandsCollection, undefined, commandFactory);
173
+ });
174
+
175
+ it("should set description on command", () => {
176
+ commands.addCommand("test");
177
+ const addDescription = getAddDescription(
178
+ commandFactory,
179
+ commandsCollection,
180
+ );
181
+ addDescription("A test command");
182
+ expect(commandsCollection.test.description).toBe("A test command");
183
+ });
184
+
185
+ it("should return commandFactory for chaining", () => {
186
+ commands.addCommand("test");
187
+ const addDescription = getAddDescription(
188
+ commandFactory,
189
+ commandsCollection,
190
+ );
191
+ const result = addDescription("desc");
192
+ expect(result).toBe(commandFactory);
193
+ });
194
+ });
195
+
196
+ describe("getSetCallback", () => {
197
+ let commandsCollection: CommandsCollection;
198
+ let commands: Commands;
199
+ let commandFactory: CommandFactory;
200
+
201
+ beforeEach(() => {
202
+ commandsCollection = {};
203
+ commandFactory = <CommandFactory>(<unknown>{
204
+ commands: undefined,
205
+ name: undefined,
206
+ addAlias: undefined,
207
+ addOption: undefined,
208
+ addPositionedArgument: undefined,
209
+ addDescription: undefined,
210
+ setCallback: undefined,
211
+ });
212
+ commands = initCommands(commandsCollection, undefined, commandFactory);
213
+ });
214
+
215
+ it("should set callback on command", () => {
216
+ commands.addCommand("test");
217
+ const setCallback = getSetCallback(commandFactory, commandsCollection);
218
+ const cb = () => {};
219
+ setCallback(cb);
220
+ expect(commandsCollection.test.callback).toBe(cb);
221
+ });
222
+
223
+ it("should return commandFactory for chaining", () => {
224
+ commands.addCommand("test");
225
+ const setCallback = getSetCallback(commandFactory, commandsCollection);
226
+ const result = setCallback(() => {});
227
+ expect(result).toBe(commandFactory);
228
+ });
229
+ });
230
+
231
+ describe("getAddPositionedArgument deduplication", () => {
232
+ let commandsCollection: CommandsCollection;
233
+ let commands: Commands;
234
+ let commandFactory: CommandFactory;
235
+
236
+ beforeEach(() => {
237
+ commandsCollection = {};
238
+ commandFactory = <CommandFactory>(<unknown>{
239
+ commands: undefined,
240
+ name: undefined,
241
+ addAlias: undefined,
242
+ addOption: undefined,
243
+ addPositionedArgument: undefined,
244
+ addDescription: undefined,
245
+ setCallback: undefined,
246
+ });
247
+ commands = initCommands(commandsCollection, undefined, commandFactory);
248
+ });
249
+
250
+ it("should not add duplicate positioned arguments", () => {
251
+ commands.addCommand("test");
252
+ const addArg = getAddPositionedArgument(commandFactory, commandsCollection);
253
+ addArg("pos001");
254
+ addArg("pos001");
255
+ expect(commandsCollection.test.positionedArguments).toEqual(["pos001"]);
256
+ });
257
+
258
+ it("should allow different positioned arguments", () => {
259
+ commands.addCommand("test");
260
+ const addArg = getAddPositionedArgument(commandFactory, commandsCollection);
261
+ addArg("input");
262
+ addArg("output");
263
+ expect(commandsCollection.test.positionedArguments).toEqual([
264
+ "input",
265
+ "output",
266
+ ]);
267
+ });
268
+ });
269
+
270
+ describe("getCommands extended", () => {
271
+ it("should use custom programName and version", () => {
272
+ const commands = getCommands("myapp", "1.2.3");
273
+ expect(commands.programName).toBe("myapp");
274
+ expect(commands.version).toBe("1.2.3");
275
+ });
276
+
277
+ it("should initialize with empty header and footer", () => {
278
+ const commands = getCommands();
279
+ expect(commands.headerText).toBe("");
280
+ expect(commands.footerText).toBe("");
281
+ });
282
+
283
+ it("should register help command via start", () => {
284
+ const logMock = spyOn(console, "log").mockImplementation(() => {});
285
+ const commands = getCommands();
286
+ commands.start(["node", "app", "help"]);
287
+ expect(logMock).toBeCalledTimes(1);
288
+ logMock.mockRestore();
289
+ });
290
+
291
+ it("should register version command via start", () => {
292
+ const logMock = spyOn(console, "log").mockImplementation(() => {});
293
+ const commands = getCommands("testapp", "2.0.0");
294
+ commands.start(["node", "app", "version"]);
295
+ expect(logMock).toBeCalledTimes(1);
296
+ logMock.mockRestore();
297
+ });
298
+ });
299
+
300
+ describe("chaining integration", () => {
301
+ it("should support full command building chain", () => {
302
+ const logMock = spyOn(console, "log").mockImplementation(() => {});
303
+ const cb = mock();
304
+ const commands = getCommands("myapp", "1.0.0");
305
+ commands
306
+ .addCommand("build")
307
+ .addAlias("b")
308
+ .addOption("output", "./dist")
309
+ .addPositionedArgument("source")
310
+ .addDescription("Build the project")
311
+ .setCallback(cb);
312
+
313
+ commands.start(["node", "app", "build", "src", "output=./dist"]);
314
+ expect(cb).toBeCalledTimes(1);
315
+ expect(cb).toBeCalledWith({ source: "src", output: "./dist" });
316
+ logMock.mockRestore();
317
+ });
318
+
319
+ it("should support alias execution", () => {
320
+ const logMock = spyOn(console, "log").mockImplementation(() => {});
321
+ const cb = mock();
322
+ const commands = getCommands("myapp", "1.0.0");
323
+ commands
324
+ .addCommand("build")
325
+ .addAlias("b")
326
+ .addDescription("Build the project")
327
+ .setCallback(cb);
328
+
329
+ commands.start(["node", "app", "b"]);
330
+ expect(cb).toBeCalledTimes(1);
331
+ logMock.mockRestore();
332
+ });
333
+
334
+ it("should support header and footer text chaining", () => {
335
+ const commands = getCommands("myapp", "1.0.0");
336
+ const result = commands.addHeaderText("Header").addFooterText("Footer");
337
+ expect(result.headerText).toBe("Header");
338
+ expect(result.footerText).toBe("Footer");
339
+ });
340
+
341
+ it("should navigate back to commands from commandFactory", () => {
342
+ const commands = getCommands("myapp", "1.0.0");
343
+ const returned = commands
344
+ .addCommand("test")
345
+ .addDescription("test")
346
+ .setCallback(() => {})
347
+ .commands();
348
+ expect(returned).toBe(commands);
349
+ });
350
+ });
351
+
352
+ describe("addOption with OptionDefinition", () => {
353
+ let commandsCollection: CommandsCollection;
354
+ let commands: Commands;
355
+
356
+ beforeEach(() => {
357
+ commandsCollection = {};
358
+ commands = initCommands(commandsCollection);
359
+ });
360
+
361
+ it("should accept string (legacy API)", () => {
362
+ commands.addCommand("test").addOption("output", "./dist");
363
+ expect(commandsCollection.test.options).toContain("output");
364
+ expect(commandsCollection.test.optionsExamples).toContain("./dist");
365
+ });
366
+
367
+ it("should accept OptionDefinition object", () => {
368
+ commands.addCommand("test").addOption({
369
+ name: "output",
370
+ shortFlag: "o",
371
+ type: "string",
372
+ description: "Output directory",
373
+ default: "./dist",
374
+ });
375
+ expect(commandsCollection.test.optionDefinitions).toHaveLength(1);
376
+ expect(commandsCollection.test.optionDefinitions[0].name).toBe("output");
377
+ expect(commandsCollection.test.optionDefinitions[0].shortFlag).toBe("o");
378
+ });
379
+
380
+ it("should register option in optionDefinitions array", () => {
381
+ commands.addCommand("test").addOption({
382
+ name: "verbose",
383
+ type: "boolean",
384
+ description: "Verbose",
385
+ });
386
+ expect(commandsCollection.test.optionDefinitions).toHaveLength(1);
387
+ });
388
+
389
+ it("should support chaining with OptionDefinition", () => {
390
+ const result = commands
391
+ .addCommand("test")
392
+ .addOption({
393
+ name: "output",
394
+ type: "string",
395
+ description: "Output",
396
+ })
397
+ .addOption({
398
+ name: "verbose",
399
+ type: "boolean",
400
+ description: "Verbose",
401
+ });
402
+ expect(commandsCollection.test.optionDefinitions).toHaveLength(2);
403
+ expect(result.commands).toBeTruthy();
404
+ });
405
+ });
406
+
407
+ describe("addFlag helper", () => {
408
+ let commandsCollection: CommandsCollection;
409
+ let commands: Commands;
410
+
411
+ beforeEach(() => {
412
+ commandsCollection = {};
413
+ commands = initCommands(commandsCollection);
414
+ });
415
+
416
+ it("should add boolean flag", () => {
417
+ commands.addCommand("test").addFlag("verbose", "v", "Enable verbose");
418
+ const def = commandsCollection.test.optionDefinitions[0];
419
+ expect(def.name).toBe("verbose");
420
+ expect(def.type).toBe("boolean");
421
+ expect(def.shortFlag).toBe("v");
422
+ expect(def.description).toBe("Enable verbose");
423
+ });
424
+
425
+ it("should add flag without short flag", () => {
426
+ commands.addCommand("test").addFlag("verbose", undefined, "Enable verbose");
427
+ const def = commandsCollection.test.optionDefinitions[0];
428
+ expect(def.name).toBe("verbose");
429
+ expect(def.shortFlag).toBeUndefined();
430
+ });
431
+
432
+ it("should return commandFactory for chaining", () => {
433
+ const result = commands.addCommand("test").addFlag("verbose");
434
+ expect(result.commands).toBeTruthy();
435
+ });
436
+ });
437
+
438
+ describe("addStringOption helper", () => {
439
+ let commandsCollection: CommandsCollection;
440
+ let commands: Commands;
441
+
442
+ beforeEach(() => {
443
+ commandsCollection = {};
444
+ commands = initCommands(commandsCollection);
445
+ });
446
+
447
+ it("should add string option", () => {
448
+ commands
449
+ .addCommand("test")
450
+ .addStringOption("output", "o", "Output directory");
451
+ const def = commandsCollection.test.optionDefinitions[0];
452
+ expect(def.name).toBe("output");
453
+ expect(def.type).toBe("string");
454
+ expect(def.shortFlag).toBe("o");
455
+ });
456
+
457
+ it("should support config parameter", () => {
458
+ commands.addCommand("test").addStringOption("output", "o", "Output", {
459
+ default: "./dist",
460
+ required: true,
461
+ });
462
+ const def = commandsCollection.test.optionDefinitions[0];
463
+ expect(def.default).toBe("./dist");
464
+ expect(def.required).toBe(true);
465
+ });
466
+ });
467
+
468
+ describe("addNumberOption helper", () => {
469
+ let commandsCollection: CommandsCollection;
470
+ let commands: Commands;
471
+
472
+ beforeEach(() => {
473
+ commandsCollection = {};
474
+ commands = initCommands(commandsCollection);
475
+ });
476
+
477
+ it("should add number option", () => {
478
+ commands.addCommand("test").addNumberOption("port", "p", "Port number");
479
+ const def = commandsCollection.test.optionDefinitions[0];
480
+ expect(def.name).toBe("port");
481
+ expect(def.type).toBe("number");
482
+ });
483
+
484
+ it("should support config parameter", () => {
485
+ commands.addCommand("test").addNumberOption("port", "p", "Port", {
486
+ default: 3000,
487
+ required: false,
488
+ });
489
+ const def = commandsCollection.test.optionDefinitions[0];
490
+ expect(def.default).toBe(3000);
491
+ });
492
+ });