@pablozaiden/terminatui 0.1.2 → 0.3.0-beta-1

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.
Files changed (175) hide show
  1. package/AGENTS.md +43 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +64 -43
  4. package/bun.lock +85 -0
  5. package/examples/tui-app/commands/config/app/get.ts +62 -0
  6. package/examples/tui-app/commands/config/app/index.ts +23 -0
  7. package/examples/tui-app/commands/config/app/set.ts +96 -0
  8. package/examples/tui-app/commands/config/index.ts +28 -0
  9. package/examples/tui-app/commands/config/user/get.ts +61 -0
  10. package/examples/tui-app/commands/config/user/index.ts +23 -0
  11. package/examples/tui-app/commands/config/user/set.ts +57 -0
  12. package/examples/tui-app/commands/greet.ts +14 -11
  13. package/examples/tui-app/commands/math.ts +6 -9
  14. package/examples/tui-app/commands/status.ts +24 -13
  15. package/examples/tui-app/index.ts +7 -3
  16. package/guides/01-hello-world.md +7 -2
  17. package/guides/02-adding-options.md +2 -2
  18. package/guides/03-multiple-commands.md +6 -8
  19. package/guides/04-subcommands.md +8 -8
  20. package/guides/05-interactive-tui.md +45 -30
  21. package/guides/06-config-validation.md +4 -12
  22. package/guides/07-async-cancellation.md +15 -69
  23. package/guides/08-complete-application.md +13 -179
  24. package/guides/README.md +7 -3
  25. package/package.json +4 -8
  26. package/src/__tests__/application.test.ts +87 -68
  27. package/src/__tests__/buildCliCommand.test.ts +99 -119
  28. package/src/__tests__/builtins.test.ts +27 -75
  29. package/src/__tests__/command.test.ts +100 -131
  30. package/src/__tests__/context.test.ts +1 -26
  31. package/src/__tests__/helpCore.test.ts +227 -0
  32. package/src/__tests__/parser.test.ts +98 -244
  33. package/src/__tests__/registry.test.ts +33 -160
  34. package/src/__tests__/schemaToFields.test.ts +75 -158
  35. package/src/builtins/help.ts +19 -4
  36. package/src/builtins/settings.ts +18 -32
  37. package/src/builtins/version.ts +4 -4
  38. package/src/cli/output/colors.ts +1 -1
  39. package/src/cli/parser.ts +26 -95
  40. package/src/core/application.ts +192 -110
  41. package/src/core/command.ts +26 -9
  42. package/src/core/context.ts +31 -20
  43. package/src/core/help.ts +24 -18
  44. package/src/core/knownCommands.ts +13 -0
  45. package/src/core/logger.ts +39 -42
  46. package/src/core/registry.ts +5 -12
  47. package/src/tui/TuiApplication.tsx +63 -120
  48. package/src/tui/TuiRoot.tsx +135 -0
  49. package/src/tui/adapters/factory.ts +19 -0
  50. package/src/tui/adapters/ink/InkRenderer.tsx +135 -0
  51. package/src/tui/adapters/ink/components/Button.tsx +12 -0
  52. package/src/tui/adapters/ink/components/Code.tsx +6 -0
  53. package/src/tui/adapters/ink/components/CodeHighlight.tsx +6 -0
  54. package/src/tui/adapters/ink/components/Container.tsx +5 -0
  55. package/src/tui/adapters/ink/components/Field.tsx +12 -0
  56. package/src/tui/adapters/ink/components/Label.tsx +24 -0
  57. package/src/tui/adapters/ink/components/MenuButton.tsx +12 -0
  58. package/src/tui/adapters/ink/components/MenuItem.tsx +17 -0
  59. package/src/tui/adapters/ink/components/Overlay.tsx +5 -0
  60. package/src/tui/adapters/ink/components/Panel.tsx +15 -0
  61. package/src/tui/adapters/ink/components/ScrollView.tsx +5 -0
  62. package/src/tui/adapters/ink/components/Select.tsx +44 -0
  63. package/src/tui/adapters/ink/components/Spacer.tsx +15 -0
  64. package/src/tui/adapters/ink/components/Spinner.tsx +5 -0
  65. package/src/tui/adapters/ink/components/TextInput.tsx +22 -0
  66. package/src/tui/adapters/ink/components/Value.tsx +7 -0
  67. package/src/tui/adapters/ink/keyboard.ts +97 -0
  68. package/src/tui/adapters/ink/utils.ts +16 -0
  69. package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +115 -0
  70. package/src/tui/adapters/opentui/components/Button.tsx +13 -0
  71. package/src/tui/adapters/opentui/components/Code.tsx +12 -0
  72. package/src/tui/adapters/opentui/components/CodeHighlight.tsx +24 -0
  73. package/src/tui/adapters/opentui/components/Container.tsx +56 -0
  74. package/src/tui/adapters/opentui/components/Field.tsx +18 -0
  75. package/src/tui/adapters/opentui/components/Label.tsx +15 -0
  76. package/src/tui/adapters/opentui/components/MenuButton.tsx +14 -0
  77. package/src/tui/adapters/opentui/components/MenuItem.tsx +29 -0
  78. package/src/tui/adapters/opentui/components/Overlay.tsx +21 -0
  79. package/src/tui/adapters/opentui/components/Panel.tsx +78 -0
  80. package/src/tui/adapters/opentui/components/ScrollView.tsx +85 -0
  81. package/src/tui/adapters/opentui/components/Select.tsx +59 -0
  82. package/src/tui/adapters/opentui/components/Spacer.tsx +5 -0
  83. package/src/tui/adapters/opentui/components/Spinner.tsx +12 -0
  84. package/src/tui/adapters/opentui/components/TextInput.tsx +13 -0
  85. package/src/tui/adapters/opentui/components/Value.tsx +13 -0
  86. package/src/tui/{hooks → adapters/opentui/hooks}/useSpinner.ts +2 -11
  87. package/src/tui/adapters/opentui/keyboard.ts +61 -0
  88. package/src/tui/adapters/types.ts +70 -0
  89. package/src/tui/components/ActionButton.tsx +0 -36
  90. package/src/tui/components/CommandSelector.tsx +52 -92
  91. package/src/tui/components/ConfigForm.tsx +68 -42
  92. package/src/tui/components/FieldRow.tsx +0 -30
  93. package/src/tui/components/Header.tsx +14 -13
  94. package/src/tui/components/JsonHighlight.tsx +10 -17
  95. package/src/tui/components/ModalBase.tsx +38 -0
  96. package/src/tui/components/ResultsPanel.tsx +27 -36
  97. package/src/tui/components/StatusBar.tsx +24 -39
  98. package/src/tui/components/logColors.ts +12 -0
  99. package/src/tui/context/ClipboardContext.tsx +87 -0
  100. package/src/tui/context/ExecutorContext.tsx +139 -0
  101. package/src/tui/context/KeyboardContext.tsx +85 -71
  102. package/src/tui/context/LogsContext.tsx +35 -0
  103. package/src/tui/context/NavigationContext.tsx +194 -0
  104. package/src/tui/context/RendererContext.tsx +20 -0
  105. package/src/tui/context/TuiAppContext.tsx +58 -0
  106. package/src/tui/hooks/useActiveKeyHandler.ts +75 -0
  107. package/src/tui/hooks/useBackHandler.ts +34 -0
  108. package/src/tui/hooks/useClipboard.ts +40 -25
  109. package/src/tui/hooks/useClipboardProvider.ts +42 -0
  110. package/src/tui/hooks/useGlobalKeyHandler.ts +54 -0
  111. package/src/tui/modals/CliModal.tsx +82 -0
  112. package/src/tui/modals/EditorModal.tsx +207 -0
  113. package/src/tui/modals/LogsModal.tsx +98 -0
  114. package/src/tui/registry.ts +102 -0
  115. package/src/tui/screens/CommandSelectScreen.tsx +162 -0
  116. package/src/tui/screens/ConfigScreen.tsx +160 -0
  117. package/src/tui/screens/ErrorScreen.tsx +58 -0
  118. package/src/tui/screens/ResultsScreen.tsx +60 -0
  119. package/src/tui/screens/RunningScreen.tsx +72 -0
  120. package/src/tui/screens/ScreenBase.ts +6 -0
  121. package/src/tui/semantic/Button.tsx +7 -0
  122. package/src/tui/semantic/Code.tsx +7 -0
  123. package/src/tui/semantic/CodeHighlight.tsx +7 -0
  124. package/src/tui/semantic/Container.tsx +7 -0
  125. package/src/tui/semantic/Field.tsx +7 -0
  126. package/src/tui/semantic/Label.tsx +7 -0
  127. package/src/tui/semantic/MenuButton.tsx +7 -0
  128. package/src/tui/semantic/MenuItem.tsx +7 -0
  129. package/src/tui/semantic/Overlay.tsx +7 -0
  130. package/src/tui/semantic/Panel.tsx +7 -0
  131. package/src/tui/semantic/ScrollView.tsx +9 -0
  132. package/src/tui/semantic/Select.tsx +7 -0
  133. package/src/tui/semantic/Spacer.tsx +7 -0
  134. package/src/tui/semantic/Spinner.tsx +7 -0
  135. package/src/tui/semantic/TextInput.tsx +7 -0
  136. package/src/tui/semantic/Value.tsx +7 -0
  137. package/src/tui/semantic/types.ts +195 -0
  138. package/src/tui/theme.ts +25 -14
  139. package/src/tui/utils/buildCliCommand.ts +1 -0
  140. package/src/tui/utils/getEnumKeys.ts +3 -0
  141. package/src/tui/utils/parameterPersistence.ts +1 -0
  142. package/src/types/command.ts +0 -60
  143. package/examples/tui-app/commands/index.ts +0 -3
  144. package/src/__tests__/colors.test.ts +0 -127
  145. package/src/__tests__/commandClass.test.ts +0 -130
  146. package/src/__tests__/help.test.ts +0 -412
  147. package/src/__tests__/registryNew.test.ts +0 -160
  148. package/src/__tests__/table.test.ts +0 -146
  149. package/src/__tests__/tui.test.ts +0 -26
  150. package/src/builtins/index.ts +0 -4
  151. package/src/cli/help.ts +0 -174
  152. package/src/cli/index.ts +0 -3
  153. package/src/cli/output/index.ts +0 -2
  154. package/src/cli/output/table.ts +0 -141
  155. package/src/commands/help.ts +0 -50
  156. package/src/commands/index.ts +0 -1
  157. package/src/components/index.ts +0 -147
  158. package/src/core/index.ts +0 -15
  159. package/src/hooks/index.ts +0 -131
  160. package/src/index.ts +0 -137
  161. package/src/registry/commandRegistry.ts +0 -77
  162. package/src/registry/index.ts +0 -1
  163. package/src/tui/TuiApp.tsx +0 -582
  164. package/src/tui/app.ts +0 -29
  165. package/src/tui/components/CliModal.tsx +0 -81
  166. package/src/tui/components/EditorModal.tsx +0 -177
  167. package/src/tui/components/LogsPanel.tsx +0 -86
  168. package/src/tui/components/index.ts +0 -13
  169. package/src/tui/context/index.ts +0 -7
  170. package/src/tui/hooks/index.ts +0 -35
  171. package/src/tui/hooks/useKeyboardHandler.ts +0 -91
  172. package/src/tui/hooks/useLogStream.ts +0 -96
  173. package/src/tui/index.ts +0 -65
  174. package/src/tui/utils/index.ts +0 -13
  175. package/src/types/index.ts +0 -1
@@ -1,412 +0,0 @@
1
- import { test, expect, describe } from "bun:test";
2
- import {
3
- generateHelp,
4
- formatUsage,
5
- formatCommands,
6
- formatOptions,
7
- formatExamples,
8
- getCommandSummary,
9
- formatGlobalOptions,
10
- generateCommandHelp,
11
- } from "../cli/help.ts";
12
- import { defineCommand } from "../types/command.ts";
13
-
14
- describe("Help Generation", () => {
15
- describe("formatUsage", () => {
16
- test("formats basic usage", () => {
17
- const cmd = defineCommand({
18
- name: "test",
19
- description: "Test command",
20
- execute: () => {},
21
- });
22
-
23
- const usage = formatUsage(cmd, "myapp");
24
- expect(usage).toContain("myapp");
25
- expect(usage).toContain("test");
26
- });
27
-
28
- test("includes [command] for commands with subcommands", () => {
29
- const cmd = defineCommand({
30
- name: "parent",
31
- description: "Parent command",
32
- subcommands: {
33
- child: defineCommand({
34
- name: "child",
35
- description: "Child",
36
- execute: () => {},
37
- }),
38
- },
39
- execute: () => {},
40
- });
41
-
42
- const usage = formatUsage(cmd);
43
- expect(usage).toContain("[command]");
44
- });
45
-
46
- test("includes [options] for commands with options", () => {
47
- const cmd = defineCommand({
48
- name: "test",
49
- description: "Test command",
50
- options: {
51
- verbose: { type: "boolean", description: "Verbose" },
52
- },
53
- execute: () => {},
54
- });
55
-
56
- const usage = formatUsage(cmd);
57
- expect(usage).toContain("[options]");
58
- });
59
- });
60
-
61
- describe("formatCommands", () => {
62
- test("formats subcommands list", () => {
63
- const cmd = defineCommand({
64
- name: "parent",
65
- description: "Parent",
66
- subcommands: {
67
- child: defineCommand({
68
- name: "child",
69
- description: "Child command",
70
- execute: () => {},
71
- }),
72
- },
73
- execute: () => {},
74
- });
75
-
76
- const commands = formatCommands(cmd);
77
- expect(commands).toContain("child");
78
- expect(commands).toContain("Child command");
79
- });
80
-
81
- test("excludes hidden commands", () => {
82
- const cmd = defineCommand({
83
- name: "parent",
84
- description: "Parent",
85
- subcommands: {
86
- visible: defineCommand({
87
- name: "visible",
88
- description: "Visible",
89
- execute: () => {},
90
- }),
91
- hidden: defineCommand({
92
- name: "hidden",
93
- description: "Hidden",
94
- hidden: true,
95
- execute: () => {},
96
- }),
97
- },
98
- execute: () => {},
99
- });
100
-
101
- const commands = formatCommands(cmd);
102
- expect(commands).toContain("visible");
103
- expect(commands).not.toMatch(/\bhidden\b/);
104
- });
105
-
106
- test("shows command aliases", () => {
107
- const cmd = defineCommand({
108
- name: "parent",
109
- description: "Parent",
110
- subcommands: {
111
- list: defineCommand({
112
- name: "list",
113
- description: "List items",
114
- aliases: ["ls", "l"],
115
- execute: () => {},
116
- }),
117
- },
118
- execute: () => {},
119
- });
120
-
121
- const commands = formatCommands(cmd);
122
- expect(commands).toContain("ls");
123
- expect(commands).toContain("l");
124
- });
125
-
126
- test("returns empty for no subcommands", () => {
127
- const cmd = defineCommand({
128
- name: "test",
129
- description: "Test",
130
- execute: () => {},
131
- });
132
-
133
- const commands = formatCommands(cmd);
134
- expect(commands).toBe("");
135
- });
136
- });
137
-
138
- describe("formatOptions", () => {
139
- test("formats options with descriptions", () => {
140
- const cmd = defineCommand({
141
- name: "test",
142
- description: "Test",
143
- options: {
144
- verbose: { type: "boolean", description: "Enable verbose" },
145
- },
146
- execute: () => {},
147
- });
148
-
149
- const options = formatOptions(cmd);
150
- expect(options).toContain("--verbose");
151
- expect(options).toContain("Enable verbose");
152
- });
153
-
154
- test("shows option aliases", () => {
155
- const cmd = defineCommand({
156
- name: "test",
157
- description: "Test",
158
- options: {
159
- verbose: { type: "boolean", alias: "v", description: "Verbose" },
160
- },
161
- execute: () => {},
162
- });
163
-
164
- const options = formatOptions(cmd);
165
- expect(options).toContain("-v");
166
- });
167
-
168
- test("shows default values", () => {
169
- const cmd = defineCommand({
170
- name: "test",
171
- description: "Test",
172
- options: {
173
- count: { type: "number", default: 10, description: "Count" },
174
- },
175
- execute: () => {},
176
- });
177
-
178
- const options = formatOptions(cmd);
179
- expect(options).toContain("10");
180
- });
181
-
182
- test("shows required marker", () => {
183
- const cmd = defineCommand({
184
- name: "test",
185
- description: "Test",
186
- options: {
187
- name: { type: "string", required: true, description: "Name" },
188
- },
189
- execute: () => {},
190
- });
191
-
192
- const options = formatOptions(cmd);
193
- expect(options).toContain("*");
194
- });
195
-
196
- test("shows enum values", () => {
197
- const cmd = defineCommand({
198
- name: "test",
199
- description: "Test",
200
- options: {
201
- level: {
202
- type: "string",
203
- enum: ["low", "high"],
204
- description: "Level",
205
- },
206
- },
207
- execute: () => {},
208
- });
209
-
210
- const options = formatOptions(cmd);
211
- expect(options).toContain("low");
212
- expect(options).toContain("high");
213
- });
214
-
215
- test("returns empty for no options", () => {
216
- const cmd = defineCommand({
217
- name: "test",
218
- description: "Test",
219
- execute: () => {},
220
- });
221
-
222
- const options = formatOptions(cmd);
223
- expect(options).toBe("");
224
- });
225
- });
226
-
227
- describe("formatExamples", () => {
228
- test("formats examples list", () => {
229
- const cmd = defineCommand({
230
- name: "test",
231
- description: "Test",
232
- examples: [
233
- { command: "test --verbose", description: "Run with verbose" },
234
- ],
235
- execute: () => {},
236
- });
237
-
238
- const examples = formatExamples(cmd);
239
- expect(examples).toContain("test --verbose");
240
- expect(examples).toContain("Run with verbose");
241
- });
242
-
243
- test("returns empty for no examples", () => {
244
- const cmd = defineCommand({
245
- name: "test",
246
- description: "Test",
247
- execute: () => {},
248
- });
249
-
250
- const examples = formatExamples(cmd);
251
- expect(examples).toBe("");
252
- });
253
- });
254
-
255
- describe("generateHelp", () => {
256
- test("generates help with app name and version", () => {
257
- const cmd = defineCommand({
258
- name: "root",
259
- description: "Root command",
260
- execute: () => {},
261
- });
262
-
263
- const help = generateHelp(cmd, { appName: "myapp", version: "1.0.0" });
264
- expect(help).toContain("myapp");
265
- expect(help).toContain("1.0.0");
266
- });
267
-
268
- test("includes usage section", () => {
269
- const cmd = defineCommand({
270
- name: "test",
271
- description: "Test",
272
- execute: () => {},
273
- });
274
-
275
- const help = generateHelp(cmd);
276
- expect(help).toContain("Usage:");
277
- });
278
-
279
- test("includes command description", () => {
280
- const cmd = defineCommand({
281
- name: "test",
282
- description: "A test command for testing",
283
- execute: () => {},
284
- });
285
-
286
- const help = generateHelp(cmd);
287
- expect(help).toContain("A test command for testing");
288
- });
289
-
290
- test("includes options section", () => {
291
- const cmd = defineCommand({
292
- name: "test",
293
- description: "Test",
294
- options: {
295
- verbose: { type: "boolean", description: "Verbose mode" },
296
- },
297
- execute: () => {},
298
- });
299
-
300
- const help = generateHelp(cmd);
301
- expect(help).toContain("Options:");
302
- expect(help).toContain("--verbose");
303
- });
304
-
305
- test("generates root help with commands", () => {
306
- const cmd = defineCommand({
307
- name: "root",
308
- description: "Root",
309
- subcommands: {
310
- run: defineCommand({
311
- name: "run",
312
- description: "Run something",
313
- execute: () => {},
314
- }),
315
- },
316
- execute: () => {},
317
- });
318
-
319
- const help = generateHelp(cmd);
320
- expect(help).toContain("Commands:");
321
- expect(help).toContain("run");
322
- });
323
- });
324
-
325
- describe("getCommandSummary", () => {
326
- test("returns command summary", () => {
327
- const cmd = defineCommand({
328
- name: "test",
329
- description: "A test command",
330
- execute: () => {},
331
- });
332
-
333
- const summary = getCommandSummary(cmd);
334
- expect(summary).toContain("test");
335
- expect(summary).toContain("A test command");
336
- });
337
-
338
- test("includes aliases in summary", () => {
339
- const cmd = defineCommand({
340
- name: "list",
341
- description: "List items",
342
- aliases: ["ls"],
343
- execute: () => {},
344
- });
345
-
346
- const summary = getCommandSummary(cmd);
347
- expect(summary).toContain("ls");
348
- });
349
- });
350
-
351
- describe("formatGlobalOptions", () => {
352
- test("includes --log-level option", () => {
353
- const result = formatGlobalOptions();
354
- expect(result).toContain("--log-level");
355
- expect(result).toContain("Set log level");
356
- });
357
-
358
- test("includes log level choices", () => {
359
- const result = formatGlobalOptions();
360
- expect(result).toContain("silly");
361
- expect(result).toContain("trace");
362
- expect(result).toContain("debug");
363
- expect(result).toContain("info");
364
- expect(result).toContain("warn");
365
- expect(result).toContain("error");
366
- expect(result).toContain("fatal");
367
- });
368
-
369
- test("includes --detailed-logs option", () => {
370
- const result = formatGlobalOptions();
371
- expect(result).toContain("--detailed-logs");
372
- });
373
-
374
- test("includes --no-detailed-logs option", () => {
375
- const result = formatGlobalOptions();
376
- expect(result).toContain("--no-detailed-logs");
377
- });
378
- });
379
-
380
- describe("generateCommandHelp with global options", () => {
381
- test("includes Global Options section", () => {
382
- const cmd = defineCommand({
383
- name: "test",
384
- description: "Test command",
385
- execute: () => {},
386
- });
387
-
388
- const help = generateCommandHelp(cmd, "myapp");
389
- expect(help).toContain("Global Options:");
390
- expect(help).toContain("--log-level");
391
- expect(help).toContain("--detailed-logs");
392
- });
393
-
394
- test("global options appear after command options", () => {
395
- const cmd = defineCommand({
396
- name: "test",
397
- description: "Test command",
398
- options: {
399
- verbose: { type: "boolean", description: "Verbose output" },
400
- },
401
- execute: () => {},
402
- });
403
-
404
- const help = generateCommandHelp(cmd, "myapp");
405
- const optionsIndex = help.indexOf("Options:");
406
- const globalOptionsIndex = help.indexOf("Global Options:");
407
-
408
- expect(optionsIndex).toBeGreaterThan(-1);
409
- expect(globalOptionsIndex).toBeGreaterThan(optionsIndex);
410
- });
411
- });
412
- });
@@ -1,160 +0,0 @@
1
- import { describe, test, expect, beforeEach } from "bun:test";
2
- import { CommandRegistry } from "../core/registry.ts";
3
- import { Command } from "../core/command.ts";
4
- import type { AppContext } from "../core/context.ts";
5
- import type { OptionSchema } from "../types/command.ts";
6
-
7
- // Test command implementations
8
- class TestCommand extends Command<OptionSchema> {
9
- constructor(
10
- public readonly name: string,
11
- public readonly description: string = "Test command"
12
- ) {
13
- super();
14
- }
15
- readonly options = {};
16
-
17
- override async execute(_ctx: AppContext): Promise<void> {}
18
- }
19
-
20
- describe("CommandRegistry (new)", () => {
21
- let registry: CommandRegistry;
22
-
23
- beforeEach(() => {
24
- registry = new CommandRegistry();
25
- });
26
-
27
- describe("register", () => {
28
- test("registers a command", () => {
29
- const cmd = new TestCommand("test");
30
- registry.register(cmd);
31
- expect(registry.has("test")).toBe(true);
32
- });
33
-
34
- test("throws on duplicate registration", () => {
35
- const cmd = new TestCommand("test");
36
- registry.register(cmd);
37
- expect(() => registry.register(cmd)).toThrow(
38
- "Command 'test' is already registered"
39
- );
40
- });
41
- });
42
-
43
- describe("registerAll", () => {
44
- test("registers multiple commands", () => {
45
- const cmd1 = new TestCommand("cmd1");
46
- const cmd2 = new TestCommand("cmd2");
47
- registry.registerAll([cmd1, cmd2]);
48
- expect(registry.has("cmd1")).toBe(true);
49
- expect(registry.has("cmd2")).toBe(true);
50
- });
51
- });
52
-
53
- describe("get", () => {
54
- test("returns command by name", () => {
55
- const cmd = new TestCommand("test");
56
- registry.register(cmd);
57
- expect(registry.get("test")).toBe(cmd);
58
- });
59
-
60
- test("returns undefined for unknown command", () => {
61
- expect(registry.get("unknown")).toBeUndefined();
62
- });
63
- });
64
-
65
- describe("has", () => {
66
- test("returns true for registered command", () => {
67
- registry.register(new TestCommand("test"));
68
- expect(registry.has("test")).toBe(true);
69
- });
70
-
71
- test("returns false for unknown command", () => {
72
- expect(registry.has("unknown")).toBe(false);
73
- });
74
- });
75
-
76
- describe("list", () => {
77
- test("returns empty array for empty registry", () => {
78
- expect(registry.list()).toEqual([]);
79
- });
80
-
81
- test("returns all registered commands", () => {
82
- const cmd1 = new TestCommand("cmd1");
83
- const cmd2 = new TestCommand("cmd2");
84
- registry.registerAll([cmd1, cmd2]);
85
- expect(registry.list()).toContain(cmd1);
86
- expect(registry.list()).toContain(cmd2);
87
- });
88
- });
89
-
90
- describe("names", () => {
91
- test("returns command names", () => {
92
- registry.register(new TestCommand("cmd1"));
93
- registry.register(new TestCommand("cmd2"));
94
- expect(registry.names()).toContain("cmd1");
95
- expect(registry.names()).toContain("cmd2");
96
- });
97
- });
98
-
99
- describe("resolve", () => {
100
- test("returns undefined for empty path", () => {
101
- const result = registry.resolve([]);
102
- expect(result.command).toBeUndefined();
103
- });
104
-
105
- test("resolves single command", () => {
106
- const cmd = new TestCommand("test");
107
- registry.register(cmd);
108
- const result = registry.resolve(["test"]);
109
- expect(result.command).toBe(cmd);
110
- expect(result.resolvedPath).toEqual(["test"]);
111
- expect(result.remainingPath).toEqual([]);
112
- });
113
-
114
- test("returns undefined for unknown command", () => {
115
- const result = registry.resolve(["unknown"]);
116
- expect(result.command).toBeUndefined();
117
- expect(result.remainingPath).toEqual(["unknown"]);
118
- });
119
-
120
- test("resolves nested subcommands", () => {
121
- const subCmd = new TestCommand("sub");
122
- const cmd = new TestCommand("parent");
123
- cmd.subCommands = [subCmd];
124
- registry.register(cmd);
125
-
126
- const result = registry.resolve(["parent", "sub"]);
127
- expect(result.command).toBe(subCmd);
128
- expect(result.resolvedPath).toEqual(["parent", "sub"]);
129
- expect(result.remainingPath).toEqual([]);
130
- });
131
-
132
- test("returns remaining path for unresolved parts", () => {
133
- const cmd = new TestCommand("parent");
134
- registry.register(cmd);
135
-
136
- const result = registry.resolve(["parent", "unknown"]);
137
- expect(result.command).toBe(cmd);
138
- expect(result.resolvedPath).toEqual(["parent"]);
139
- expect(result.remainingPath).toEqual(["unknown"]);
140
- });
141
- });
142
-
143
- describe("clear", () => {
144
- test("clears all commands", () => {
145
- registry.register(new TestCommand("test"));
146
- registry.clear();
147
- expect(registry.size).toBe(0);
148
- });
149
- });
150
-
151
- describe("size", () => {
152
- test("returns number of registered commands", () => {
153
- expect(registry.size).toBe(0);
154
- registry.register(new TestCommand("cmd1"));
155
- expect(registry.size).toBe(1);
156
- registry.register(new TestCommand("cmd2"));
157
- expect(registry.size).toBe(2);
158
- });
159
- });
160
- });
@@ -1,146 +0,0 @@
1
- import { test, expect, describe } from "bun:test";
2
- import { table, keyValueList, bulletList, numberedList } from "../cli/output/table.ts";
3
-
4
- describe("table", () => {
5
- test("creates table with data and columns", () => {
6
- const data = [
7
- { name: "Alice", age: 30 },
8
- { name: "Bob", age: 25 },
9
- ];
10
- const result = table(data, { columns: ["name", "age"] });
11
-
12
- expect(result).toContain("name");
13
- expect(result).toContain("Alice");
14
- expect(result).toContain("Bob");
15
- });
16
-
17
- test("handles empty data", () => {
18
- const result = table([]);
19
- expect(result).toBe("");
20
- });
21
-
22
- test("handles single row", () => {
23
- const data = [{ col: "value" }];
24
- const result = table(data);
25
- expect(result).toContain("value");
26
- });
27
-
28
- test("handles multiple columns", () => {
29
- const data = [{ a: "1", b: "2", c: "3" }];
30
- const result = table(data);
31
- expect(result).toContain("1");
32
- expect(result).toContain("2");
33
- expect(result).toContain("3");
34
- });
35
-
36
- test("handles custom column config", () => {
37
- const data = [{ value: 123 }];
38
- const result = table(data, {
39
- columns: [{ key: "value", header: "Amount" }],
40
- });
41
- expect(result).toContain("Amount");
42
- });
43
-
44
- test("handles custom formatter", () => {
45
- const data = [{ price: 100 }];
46
- const result = table(data, {
47
- columns: [
48
- {
49
- key: "price",
50
- formatter: (v) => `$${v}`,
51
- },
52
- ],
53
- });
54
- expect(result).toContain("$100");
55
- });
56
-
57
- test("hides headers when showHeaders is false", () => {
58
- const data = [{ name: "Test" }];
59
- const result = table(data, { showHeaders: false });
60
- expect(result).not.toContain("---");
61
- });
62
-
63
- test("handles undefined values", () => {
64
- const data = [{ a: undefined }];
65
- const result = table(data);
66
- expect(typeof result).toBe("string");
67
- });
68
- });
69
-
70
- describe("keyValueList", () => {
71
- test("formats key-value pairs", () => {
72
- const data = { name: "Test", version: "1.0" };
73
- const result = keyValueList(data);
74
- expect(result).toContain("name");
75
- expect(result).toContain("Test");
76
- expect(result).toContain("version");
77
- expect(result).toContain("1.0");
78
- });
79
-
80
- test("handles empty object", () => {
81
- const result = keyValueList({});
82
- expect(result).toBe("");
83
- });
84
-
85
- test("handles custom separator", () => {
86
- const data = { key: "value" };
87
- const result = keyValueList(data, { separator: " =" });
88
- expect(result).toContain("=");
89
- });
90
- });
91
-
92
- describe("bulletList", () => {
93
- test("formats bullet list", () => {
94
- const items = ["one", "two", "three"];
95
- const result = bulletList(items);
96
- expect(result).toContain("•");
97
- expect(result).toContain("one");
98
- expect(result).toContain("two");
99
- expect(result).toContain("three");
100
- });
101
-
102
- test("handles empty array", () => {
103
- const result = bulletList([]);
104
- expect(result).toBe("");
105
- });
106
-
107
- test("handles custom bullet", () => {
108
- const items = ["item"];
109
- const result = bulletList(items, { bullet: "-" });
110
- expect(result).toContain("-");
111
- });
112
-
113
- test("handles indent", () => {
114
- const items = ["item"];
115
- const result = bulletList(items, { indent: 2 });
116
- expect(result.startsWith(" ")).toBe(true);
117
- });
118
- });
119
-
120
- describe("numberedList", () => {
121
- test("formats numbered list", () => {
122
- const items = ["first", "second"];
123
- const result = numberedList(items);
124
- expect(result).toContain("1.");
125
- expect(result).toContain("2.");
126
- expect(result).toContain("first");
127
- expect(result).toContain("second");
128
- });
129
-
130
- test("handles empty array", () => {
131
- const result = numberedList([]);
132
- expect(result).toBe("");
133
- });
134
-
135
- test("handles custom start number", () => {
136
- const items = ["item"];
137
- const result = numberedList(items, { start: 5 });
138
- expect(result).toContain("5.");
139
- });
140
-
141
- test("handles indent", () => {
142
- const items = ["item"];
143
- const result = numberedList(items, { indent: 2 });
144
- expect(result.startsWith(" ")).toBe(true);
145
- });
146
- });