@flowscripter/dynamic-cli-framework 1.1.6 → 1.2.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.
- package/.github/workflows/release-bun-library.yml +1 -0
- package/README/core-commands.md +40 -0
- package/README/core-service-providers.md +198 -0
- package/README/development.md +60 -0
- package/README/implementation-details.md +474 -0
- package/README/key-concepts.md +751 -0
- package/README.md +12 -1413
- package/bun.lock +22 -18
- package/index.ts +97 -1
- package/package.json +8 -7
- package/src/api/BaseCLIFeatureOptions.ts +10 -0
- package/src/api/CLIConfig.ts +5 -0
- package/src/api/RunResult.ts +16 -0
- package/src/api/argument/Argument.ts +24 -0
- package/src/api/service/ServiceProvider.ts +3 -3
- package/src/api/service/core/ArgumentPrompterService.ts +10 -0
- package/src/api/service/core/AsciiBannerGeneratorService.ts +44 -7
- package/src/api/service/core/CompletionService.ts +36 -0
- package/src/api/service/core/DataDumpGeneratorService.ts +27 -0
- package/src/api/service/core/ImagePrinterService.ts +26 -0
- package/src/api/service/core/KeyValueService.ts +22 -5
- package/src/api/service/core/PrinterService.ts +127 -1
- package/src/api/service/core/PrompterService.ts +49 -0
- package/src/api/service/core/SecretService.ts +27 -0
- package/src/api/service/core/ShutdownService.ts +17 -1
- package/src/api/service/core/Table.ts +85 -0
- package/src/api/service/core/TableGeneratorService.ts +38 -0
- package/src/api/service/core/TreePrinterService.ts +11 -0
- package/src/cli/BaseCLI.ts +116 -44
- package/src/cli/DefaultRuntimeCLI.ts +10 -18
- package/src/command/CompletionCommand.ts +179 -0
- package/src/command/MultiCommandCliHelpCommand.ts +18 -2
- package/src/command/SingleCommandCliHelpCommand.ts +10 -1
- package/src/launcher.ts +16 -58
- package/src/runtime/command/CommandValidator.ts +3 -3
- package/src/runtime/runner.ts +102 -30
- package/src/runtime/scanner.ts +18 -15
- package/src/runtime/values/argumentValueValidation.ts +49 -5
- package/src/runtime/values/globalCommandValuePopulation.ts +1 -1
- package/src/runtime/values/subCommandValuePopulation.ts +9 -8
- package/src/service/argumentPrompter/ArgumentPrompterServiceProvider.ts +34 -0
- package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +330 -0
- package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +1 -1
- package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +254 -17
- package/src/service/banner/BannerServiceProvider.ts +2 -2
- package/src/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.ts +85 -0
- package/src/service/chiselAsciiBannerGenerator/chisel.flf.json +3 -0
- package/src/service/completion/CompletionServiceProvider.ts +212 -0
- package/src/service/completion/DefaultCompletionService.ts +364 -0
- package/src/service/completion/shell/ShellHandler.ts +16 -0
- package/src/service/completion/shell/bash.ts +47 -0
- package/src/service/completion/shell/fish.ts +43 -0
- package/src/service/completion/shell/powershell.ts +66 -0
- package/src/service/completion/shell/zsh.ts +50 -0
- package/src/service/configuration/ConfigurationServiceProvider.ts +138 -42
- package/src/service/configuration/DefaultKeyValueService.ts +53 -7
- package/src/service/configuration/DefaultSecretService.ts +85 -0
- package/src/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.ts +41 -0
- package/src/service/dataDumpGenerator/DefaultDataDumpGeneratorService.ts +80 -0
- package/src/service/imagePrinter/DefaultImagePrinterService.ts +23 -0
- package/src/service/imagePrinter/ImagePrinterServiceProvider.ts +32 -0
- package/src/service/prettyPrinter/PrettyPrinterServiceProvider.ts +1 -1
- package/src/service/printer/DefaultPrinterService.ts +147 -27
- package/src/service/printer/PrinterServiceProvider.ts +7 -7
- package/src/service/printer/command/NoHyperlinksCommand.ts +40 -0
- package/src/service/printer/terminal/Progress.ts +21 -4
- package/src/service/printer/terminal/Spinner.ts +32 -8
- package/src/service/printer/terminal/Theme.ts +1 -1
- package/src/service/prompter/DefaultPrompterService.ts +87 -0
- package/src/service/prompter/PrompterServiceProvider.ts +36 -0
- package/src/service/prompter/command/NoPromptCommand.ts +40 -0
- package/src/service/prompter/prompt/PromptContext.ts +47 -0
- package/src/service/prompter/prompt/promptAcknowledge.ts +28 -0
- package/src/service/prompter/prompt/promptMultiSelect.ts +99 -0
- package/src/service/prompter/prompt/promptOpenUrl.ts +97 -0
- package/src/service/prompter/prompt/promptSingleSelect.ts +102 -0
- package/src/service/prompter/prompt/promptText.ts +107 -0
- package/src/service/prompter/prompt/promptToggle.ts +28 -0
- package/src/service/shutdown/DefaultShutdownService.ts +13 -0
- package/src/service/shutdown/ShutdownServiceProvider.ts +28 -9
- package/src/service/shutdown/ShutdownState.ts +5 -0
- package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +1 -1
- package/src/service/tableGenerator/DefaultTableGeneratorService.ts +415 -0
- package/src/service/tableGenerator/TableGeneratorServiceProvider.ts +43 -0
- package/src/service/treePrinter/DefaultTreePrinterService.ts +49 -0
- package/src/service/treePrinter/TreePrinterServiceProvider.ts +40 -0
- package/src/terminal/Ansi.ts +27 -0
- package/src/terminal/ImageRenderer.ts +199 -0
- package/src/terminal/KeyReader.ts +23 -0
- package/src/terminal/PngDecoder.ts +143 -0
- package/src/{service/printer/terminal → terminal}/Styler.ts +3 -0
- package/src/{service/printer/terminal → terminal}/Terminal.ts +1 -0
- package/src/terminal/TtyKeyReader.ts +83 -0
- package/src/terminal/TtyStyler.ts +78 -0
- package/src/{service/printer/terminal → terminal}/TtyTerminal.ts +9 -1
- package/src/terminal/supportsHyperlinks.ts +110 -0
- package/src/util/configHelper.ts +2 -2
- package/src/util/envVarHelper.ts +2 -2
- package/src/util/helpHelper.ts +66 -122
- package/src/util/logger.ts +16 -14
- package/src/util/runnerHelper.ts +1 -1
- package/tests/cli/BaseCLI.test.ts +50 -54
- package/tests/command/CompletionCommand.test.ts +68 -0
- package/tests/command/UsageCommand.test.ts +3 -2
- package/tests/command/VersionCommand.test.ts +3 -2
- package/tests/fixtures/Context.ts +9 -2
- package/tests/fixtures/util.ts +1 -1
- package/tests/runtime/parser.test.ts +9 -7
- package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +3 -3
- package/tests/runtime/values/argumentValueValidation.test.ts +193 -0
- package/tests/service/argumentPrompter/ArgumentPrompterServiceProvider.test.ts +47 -0
- package/tests/service/argumentPrompter/DefaultArgumentPrompterService.test.ts +754 -0
- package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +5 -4
- package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +163 -3
- package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +1 -1
- package/tests/service/banner/BannerServiceProvider.test.ts +8 -4
- package/tests/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.test.ts +52 -0
- package/tests/service/completion/CompletionServiceProvider.test.ts +404 -0
- package/tests/service/completion/DefaultCompletionService.test.ts +278 -0
- package/tests/service/completion/shell/bash.test.ts +73 -0
- package/tests/service/completion/shell/fish.test.ts +62 -0
- package/tests/service/completion/shell/powershell.test.ts +59 -0
- package/tests/service/completion/shell/zsh.test.ts +60 -0
- package/tests/service/configuration/ConfigurationServiceProvider.test.ts +24 -12
- package/tests/service/configuration/DefaultKeyValueService.test.ts +118 -16
- package/tests/service/configuration/DefaultSecretService.test.ts +148 -0
- package/tests/service/configuration/command/ConfigCommand.test.ts +29 -0
- package/tests/service/configuration/command/DumpConfigCommand.test.ts +60 -0
- package/tests/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.test.ts +57 -0
- package/tests/service/dataDumpGenerator/DefaultDataDumpGeneratorService.test.ts +217 -0
- package/tests/service/imagePrinter/DefaultImagePrinterService.test.ts +96 -0
- package/tests/service/imagePrinter/ImagePrinterServiceProvider.test.ts +17 -0
- package/tests/service/prettyPrinter/DefaultPrettyPrinterService.test.ts +2 -2
- package/tests/service/prettyPrinter/PrettyPrinterServiceProvider.test.ts +5 -4
- package/tests/service/printer/DefaultPrinterService.test.ts +233 -2
- package/tests/service/printer/PrinterServiceProvider.test.ts +6 -5
- package/tests/service/printer/command/DarkModeCommand.test.ts +83 -0
- package/tests/service/printer/command/LogLevelCommand.test.ts +7 -5
- package/tests/service/printer/command/NoColorCommand.test.ts +83 -0
- package/tests/service/printer/command/NoHyperlinksCommand.test.ts +89 -0
- package/tests/service/printer/terminal/Progress.test.ts +64 -4
- package/tests/service/printer/terminal/Spinner.test.ts +44 -3
- package/tests/service/prompter/DefaultPrompterService.test.ts +615 -0
- package/tests/service/prompter/PrompterServiceProvider.test.ts +47 -0
- package/tests/service/prompter/command/NoPromptCommand.test.ts +58 -0
- package/tests/service/shutdown/ShutdownServiceProvider.test.ts +4 -2
- package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +3 -2
- package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +5 -4
- package/tests/service/tableGenerator/DefaultTableGeneratorService.test.ts +363 -0
- package/tests/service/tableGenerator/TableGeneratorServiceProvider.test.ts +47 -0
- package/tests/service/treePrinter/DefaultTreePrinterService.test.ts +144 -0
- package/tests/service/treePrinter/TreePrinterServiceProvider.test.ts +55 -0
- package/tests/terminal/ImageRenderer.test.ts +161 -0
- package/tests/terminal/PngDecoder.test.ts +223 -0
- package/tests/terminal/supportsHyperlinks.test.ts +149 -0
- package/tests/util/configHelper.test.ts +4 -4
- package/tests/util/logger.test.ts +23 -1
- package/tsconfig.json +31 -0
- package/src/service/printer/terminal/Ansi.ts +0 -10
- package/src/service/printer/terminal/TtyStyler.ts +0 -39
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import NoPromptCommand from "../../../../src/service/prompter/command/NoPromptCommand.ts";
|
|
3
|
+
import PrompterServiceProvider from "../../../../src/service/prompter/PrompterServiceProvider.ts";
|
|
4
|
+
import { ArgumentValueTypeName } from "../../../../src/api/argument/ArgumentValueTypes.ts";
|
|
5
|
+
import type PrompterService from "../../../../src/api/service/core/PrompterService.ts";
|
|
6
|
+
import type Context from "../../../../src/api/Context.ts";
|
|
7
|
+
|
|
8
|
+
function getMockPrompterService(): PrompterService {
|
|
9
|
+
return {
|
|
10
|
+
promptEnabled: true,
|
|
11
|
+
prompt: () => Promise.resolve({ name: "", value: "" }),
|
|
12
|
+
promptAll: () => Promise.resolve([]),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("NoPromptCommand tests", () => {
|
|
17
|
+
test("NoPromptCommand has correct properties", () => {
|
|
18
|
+
const prompterService = getMockPrompterService();
|
|
19
|
+
const provider = new PrompterServiceProvider(100, prompterService);
|
|
20
|
+
const command = new NoPromptCommand(provider, 100);
|
|
21
|
+
|
|
22
|
+
expect(command.name).toEqual("no-prompt");
|
|
23
|
+
expect(command.description).toEqual("Disable interactive prompting");
|
|
24
|
+
expect(command.enableConfiguration).toBeTrue();
|
|
25
|
+
expect(command.executePriority).toEqual(100);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("NoPromptCommand has correct argument", () => {
|
|
29
|
+
const prompterService = getMockPrompterService();
|
|
30
|
+
const provider = new PrompterServiceProvider(100, prompterService);
|
|
31
|
+
const command = new NoPromptCommand(provider, 100);
|
|
32
|
+
|
|
33
|
+
expect(command.argument.type).toEqual(ArgumentValueTypeName.BOOLEAN);
|
|
34
|
+
expect(command.argument.defaultValue).toEqual(false);
|
|
35
|
+
expect(command.argument.configurationKey).toEqual("NO_PROMPT");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("NoPromptCommand execute with true disables prompting", async () => {
|
|
39
|
+
const prompterService = getMockPrompterService();
|
|
40
|
+
const provider = new PrompterServiceProvider(100, prompterService);
|
|
41
|
+
const command = new NoPromptCommand(provider, 100);
|
|
42
|
+
|
|
43
|
+
await command.execute({} as Context, true);
|
|
44
|
+
|
|
45
|
+
expect(provider.prompterService.promptEnabled).toBeFalse();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("NoPromptCommand execute with false enables prompting", async () => {
|
|
49
|
+
const prompterService = getMockPrompterService();
|
|
50
|
+
prompterService.promptEnabled = false;
|
|
51
|
+
const provider = new PrompterServiceProvider(100, prompterService);
|
|
52
|
+
const command = new NoPromptCommand(provider, 100);
|
|
53
|
+
|
|
54
|
+
await command.execute({} as Context, false);
|
|
55
|
+
|
|
56
|
+
expect(provider.prompterService.promptEnabled).toBeTrue();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -3,9 +3,11 @@ import ShutdownServiceProvider from "../../../src/service/shutdown/ShutdownServi
|
|
|
3
3
|
import { getCLIConfig } from "../../fixtures/CLIConfig.ts";
|
|
4
4
|
|
|
5
5
|
describe("ShutdownServiceProvider tests", () => {
|
|
6
|
-
test("ShutdownServiceProvider
|
|
6
|
+
test("ShutdownServiceProvider getServiceInfo works", async () => {
|
|
7
7
|
const shutdownServiceProvider = new ShutdownServiceProvider(100);
|
|
8
|
-
const serviceInfo = await shutdownServiceProvider.
|
|
8
|
+
const serviceInfo = await shutdownServiceProvider.getServiceInfo(
|
|
9
|
+
getCLIConfig(),
|
|
10
|
+
);
|
|
9
11
|
expect(serviceInfo.commands.length).toEqual(0);
|
|
10
12
|
|
|
11
13
|
await ShutdownServiceProvider.shutdown();
|
|
@@ -3,8 +3,8 @@ import { expectBytesEquals } from "../../fixtures/util.ts";
|
|
|
3
3
|
import DefaultSyntaxHighlighterService from "../../../src/service/syntaxHighlighter/DefaultSyntaxHighlighterService.ts";
|
|
4
4
|
import yaml from "highlight.js/lib/languages/yaml";
|
|
5
5
|
import DefaultPrinterService from "../../../src/service/printer/DefaultPrinterService.ts";
|
|
6
|
-
import TtyStyler from "../../../src/
|
|
7
|
-
import TtyTerminal from "../../../src/
|
|
6
|
+
import TtyStyler from "../../../src/terminal/TtyStyler.ts";
|
|
7
|
+
import TtyTerminal from "../../../src/terminal/TtyTerminal.ts";
|
|
8
8
|
import StreamString from "../../fixtures/StreamString.ts";
|
|
9
9
|
|
|
10
10
|
describe("DefaultSyntaxHighlighterService tests", () => {
|
|
@@ -80,6 +80,7 @@ describe("DefaultSyntaxHighlighterService tests", () => {
|
|
|
80
80
|
dummyStderr.writableStream,
|
|
81
81
|
true,
|
|
82
82
|
true,
|
|
83
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
83
84
|
new TtyTerminal(dummyStderr.writeStream),
|
|
84
85
|
new TtyStyler(3),
|
|
85
86
|
);
|
|
@@ -5,11 +5,11 @@ import SyntaxHighlighterServiceProvider from "../../../src/service/syntaxHighlig
|
|
|
5
5
|
import { PRINTER_SERVICE_ID } from "../../../src/api/service/core/PrinterService.ts";
|
|
6
6
|
import DefaultPrinterService from "../../../src/service/printer/DefaultPrinterService.ts";
|
|
7
7
|
import StreamString from "../../fixtures/StreamString.ts";
|
|
8
|
-
import TtyTerminal from "../../../src/
|
|
9
|
-
import TtyStyler from "../../../src/
|
|
8
|
+
import TtyTerminal from "../../../src/terminal/TtyTerminal.ts";
|
|
9
|
+
import TtyStyler from "../../../src/terminal/TtyStyler.ts";
|
|
10
10
|
|
|
11
11
|
describe("SyntaxHighlighterServiceProvider tests", () => {
|
|
12
|
-
test("SyntaxHighlighterServiceProvider
|
|
12
|
+
test("SyntaxHighlighterServiceProvider getServiceInfo and initService works", async () => {
|
|
13
13
|
const dummyStdout = new StreamString();
|
|
14
14
|
const dummyStderr = new StreamString();
|
|
15
15
|
const syntaxHighlighterServiceProvider =
|
|
@@ -26,12 +26,13 @@ describe("SyntaxHighlighterServiceProvider tests", () => {
|
|
|
26
26
|
dummyStderr.writableStream,
|
|
27
27
|
true,
|
|
28
28
|
true,
|
|
29
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
29
30
|
new TtyTerminal(dummyStderr.writeStream),
|
|
30
31
|
new TtyStyler(3),
|
|
31
32
|
),
|
|
32
33
|
);
|
|
33
34
|
|
|
34
|
-
const serviceInfo = await syntaxHighlighterServiceProvider.
|
|
35
|
+
const serviceInfo = await syntaxHighlighterServiceProvider.getServiceInfo(
|
|
35
36
|
cliConfig,
|
|
36
37
|
);
|
|
37
38
|
expect(serviceInfo.commands.length).toEqual(0);
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import DefaultTableGeneratorService from "../../../src/service/tableGenerator/DefaultTableGeneratorService.ts";
|
|
3
|
+
import Table from "../../../src/api/service/core/Table.ts";
|
|
4
|
+
import { Align } from "../../../src/api/service/core/TableGeneratorService.ts";
|
|
5
|
+
|
|
6
|
+
describe("DefaultTableGeneratorService tests", () => {
|
|
7
|
+
function createService(): DefaultTableGeneratorService {
|
|
8
|
+
const service = new DefaultTableGeneratorService();
|
|
9
|
+
service.colorEnabled = false;
|
|
10
|
+
return service;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test("2x2 table with border renders correct box-drawing chars", () => {
|
|
14
|
+
const service = createService();
|
|
15
|
+
const table = new Table(2, 2, { maxWidth: 20, padding: 1 })
|
|
16
|
+
.cell(0, 0, "A")
|
|
17
|
+
.cell(0, 1, "B")
|
|
18
|
+
.cell(1, 0, "C")
|
|
19
|
+
.cell(1, 1, "D");
|
|
20
|
+
|
|
21
|
+
const result = service.render(table);
|
|
22
|
+
const lines = result.split("\n");
|
|
23
|
+
|
|
24
|
+
// Top border
|
|
25
|
+
expect(lines[0]).toMatch(/^┌─+┬─+┐$/);
|
|
26
|
+
// Content rows have │ separators
|
|
27
|
+
expect(lines[1]).toMatch(/^│.*│.*│$/);
|
|
28
|
+
// Middle border
|
|
29
|
+
expect(lines[2]).toMatch(/^├─+┼─+┤$/);
|
|
30
|
+
// Bottom border
|
|
31
|
+
expect(lines[4]).toMatch(/^└─+┴─+┘$/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("2x2 table without border renders space-separated columns", () => {
|
|
35
|
+
const service = createService();
|
|
36
|
+
const table = new Table(2, 2, { border: false, maxWidth: 20, padding: 1 })
|
|
37
|
+
.cell(0, 0, "A")
|
|
38
|
+
.cell(0, 1, "B")
|
|
39
|
+
.cell(1, 0, "C")
|
|
40
|
+
.cell(1, 1, "D");
|
|
41
|
+
|
|
42
|
+
const result = service.render(table);
|
|
43
|
+
const lines = result.split("\n");
|
|
44
|
+
|
|
45
|
+
expect(lines.length).toBe(2);
|
|
46
|
+
// No box-drawing characters
|
|
47
|
+
expect(result).not.toContain("│");
|
|
48
|
+
expect(result).not.toContain("─");
|
|
49
|
+
// Content rows should contain the cell values with spacing
|
|
50
|
+
expect(lines[0]).toContain("A");
|
|
51
|
+
expect(lines[0]).toContain("B");
|
|
52
|
+
// Between columns there should be padding*2 = 2 spaces as separator
|
|
53
|
+
// Format: pad + content + pad + (padding*2 spaces) + pad + content + pad
|
|
54
|
+
expect(lines[0]).toMatch(/A\s+B/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("equal flex weights produce equal columns", () => {
|
|
58
|
+
const service = createService();
|
|
59
|
+
const table = new Table(1, 2, { maxWidth: 40, padding: 0 })
|
|
60
|
+
.column(0, { flexWeight: 1 })
|
|
61
|
+
.column(1, { flexWeight: 1 })
|
|
62
|
+
.cell(0, 0, "X")
|
|
63
|
+
.cell(0, 1, "Y");
|
|
64
|
+
|
|
65
|
+
const result = service.render(table);
|
|
66
|
+
const lines = result.split("\n");
|
|
67
|
+
// With border, overhead = 3 (columnCount+1 = 3 border chars), padding = 0
|
|
68
|
+
// Content budget = 40 - 3 = 37
|
|
69
|
+
// Natural widths: 1 each = 2 total, surplus = 35
|
|
70
|
+
// Each flex col gets ~17-18 chars
|
|
71
|
+
// Find content row (second line)
|
|
72
|
+
const contentLine = lines[1]!;
|
|
73
|
+
// Extract the two cell widths from between │ chars
|
|
74
|
+
const parts = contentLine.split("│").filter((s) => s.length > 0);
|
|
75
|
+
expect(parts.length).toBe(2);
|
|
76
|
+
const diff = Math.abs(parts[0]!.length - parts[1]!.length);
|
|
77
|
+
expect(diff).toBeLessThanOrEqual(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("flex weight 2:1 gives proportional distribution", () => {
|
|
81
|
+
const service = createService();
|
|
82
|
+
const table = new Table(1, 2, { maxWidth: 40, padding: 0 })
|
|
83
|
+
.column(0, { flexWeight: 2 })
|
|
84
|
+
.column(1, { flexWeight: 1 })
|
|
85
|
+
.cell(0, 0, "X")
|
|
86
|
+
.cell(0, 1, "Y");
|
|
87
|
+
|
|
88
|
+
const result = service.render(table);
|
|
89
|
+
const lines = result.split("\n");
|
|
90
|
+
const contentLine = lines[1]!;
|
|
91
|
+
const parts = contentLine.split("│").filter((s) => s.length > 0);
|
|
92
|
+
expect(parts.length).toBe(2);
|
|
93
|
+
// Col 0 should be wider than col 1
|
|
94
|
+
expect(parts[0]!.length).toBeGreaterThan(parts[1]!.length);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("minWidth prevents shrinking below minimum", () => {
|
|
98
|
+
const service = createService();
|
|
99
|
+
// Use a very small maxWidth to force shrinking
|
|
100
|
+
const table = new Table(1, 2, { maxWidth: 20, padding: 0 })
|
|
101
|
+
.column(0, { flexWeight: 1, minWidth: 8 })
|
|
102
|
+
.column(1, { flexWeight: 1 })
|
|
103
|
+
.cell(0, 0, "ABCDEFGHIJ")
|
|
104
|
+
.cell(0, 1, "ABCDEFGHIJ");
|
|
105
|
+
|
|
106
|
+
const result = service.render(table);
|
|
107
|
+
const lines = result.split("\n");
|
|
108
|
+
const contentLine = lines[1]!;
|
|
109
|
+
const parts = contentLine.split("│").filter((s) => s.length > 0);
|
|
110
|
+
// Col 0 should be at least 8 wide
|
|
111
|
+
expect(parts[0]!.length).toBeGreaterThanOrEqual(8);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("maxWidth caps column width", () => {
|
|
115
|
+
const service = createService();
|
|
116
|
+
const table = new Table(1, 2, { maxWidth: 80, padding: 0 })
|
|
117
|
+
.column(0, { flexWeight: 1, maxWidth: 5 })
|
|
118
|
+
.column(1, { flexWeight: 1 })
|
|
119
|
+
.cell(0, 0, "AB")
|
|
120
|
+
.cell(0, 1, "CD");
|
|
121
|
+
|
|
122
|
+
const result = service.render(table);
|
|
123
|
+
const lines = result.split("\n");
|
|
124
|
+
const contentLine = lines[1]!;
|
|
125
|
+
const parts = contentLine.split("│").filter((s) => s.length > 0);
|
|
126
|
+
// Col 0 should not exceed maxWidth of 5
|
|
127
|
+
expect(parts[0]!.length).toBeLessThanOrEqual(5);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("long text wraps at word boundaries", () => {
|
|
131
|
+
const service = createService();
|
|
132
|
+
// Column maxWidth=10 forces wrapping of "hello world foo bar"
|
|
133
|
+
const table = new Table(1, 1, { maxWidth: 20, padding: 1 })
|
|
134
|
+
.column(0, { maxWidth: 10 })
|
|
135
|
+
.cell(0, 0, "hello world foo bar");
|
|
136
|
+
|
|
137
|
+
const result = service.render(table);
|
|
138
|
+
const lines = result.split("\n");
|
|
139
|
+
const contentLines = lines.filter((l) =>
|
|
140
|
+
l.startsWith("│") && !l.includes("─")
|
|
141
|
+
);
|
|
142
|
+
expect(contentLines.length).toBeGreaterThan(1);
|
|
143
|
+
// Each content line should have complete words (no mid-word breaks)
|
|
144
|
+
for (const line of contentLines) {
|
|
145
|
+
const content = line.replace(/│/g, "").trim();
|
|
146
|
+
if (content.length > 0) {
|
|
147
|
+
const words = content.split(/\s+/);
|
|
148
|
+
for (const word of words) {
|
|
149
|
+
expect(
|
|
150
|
+
["hello", "world", "foo", "bar", ""].includes(word),
|
|
151
|
+
).toBe(true);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("single long word hard-breaks", () => {
|
|
158
|
+
const service = createService();
|
|
159
|
+
// Column maxWidth=5 forces hard break of "ABCDEFGHIJKLMNOP"
|
|
160
|
+
const table = new Table(1, 1, { maxWidth: 12, padding: 0 })
|
|
161
|
+
.column(0, { maxWidth: 5 })
|
|
162
|
+
.cell(0, 0, "ABCDEFGHIJKLMNOP");
|
|
163
|
+
|
|
164
|
+
const result = service.render(table);
|
|
165
|
+
const lines = result.split("\n");
|
|
166
|
+
const contentLines = lines.filter((l) =>
|
|
167
|
+
l.startsWith("│") && !l.includes("─")
|
|
168
|
+
);
|
|
169
|
+
expect(contentLines.length).toBeGreaterThan(1);
|
|
170
|
+
// Verify all characters are present
|
|
171
|
+
const allContent = contentLines
|
|
172
|
+
.map((l) => l.replace(/│/g, "").replace(/ /g, ""))
|
|
173
|
+
.join("");
|
|
174
|
+
expect(allContent).toBe("ABCDEFGHIJKLMNOP");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("LEFT/RIGHT/CENTER alignment produces correct padding", () => {
|
|
178
|
+
const service = createService();
|
|
179
|
+
// Use minWidth=10 to force column wider than content "AB" (2 chars)
|
|
180
|
+
// LEFT (default)
|
|
181
|
+
const tableL = new Table(1, 1, { maxWidth: 20, padding: 0 })
|
|
182
|
+
.column(0, { minWidth: 10 })
|
|
183
|
+
.cell(0, 0, "AB");
|
|
184
|
+
let result = service.render(tableL);
|
|
185
|
+
let contentLine = result.split("\n")[1]!;
|
|
186
|
+
let content = contentLine.slice(1, -1); // strip border chars
|
|
187
|
+
expect(content).toBe("AB" + " ".repeat(8));
|
|
188
|
+
|
|
189
|
+
// RIGHT
|
|
190
|
+
const tableR = new Table(1, 1, {
|
|
191
|
+
maxWidth: 20,
|
|
192
|
+
padding: 0,
|
|
193
|
+
align: Align.RIGHT,
|
|
194
|
+
})
|
|
195
|
+
.column(0, { minWidth: 10 })
|
|
196
|
+
.cell(0, 0, "AB");
|
|
197
|
+
result = service.render(tableR);
|
|
198
|
+
contentLine = result.split("\n")[1]!;
|
|
199
|
+
content = contentLine.slice(1, -1);
|
|
200
|
+
expect(content).toBe(" ".repeat(8) + "AB");
|
|
201
|
+
|
|
202
|
+
// CENTER
|
|
203
|
+
const tableC = new Table(1, 1, {
|
|
204
|
+
maxWidth: 20,
|
|
205
|
+
padding: 0,
|
|
206
|
+
align: Align.CENTER,
|
|
207
|
+
})
|
|
208
|
+
.column(0, { minWidth: 10 })
|
|
209
|
+
.cell(0, 0, "AB");
|
|
210
|
+
result = service.render(tableC);
|
|
211
|
+
contentLine = result.split("\n")[1]!;
|
|
212
|
+
content = contentLine.slice(1, -1);
|
|
213
|
+
expect(content).toBe(" ".repeat(4) + "AB" + " ".repeat(4));
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("alignment cascade: cell overrides row overrides column overrides table", () => {
|
|
217
|
+
const service = createService();
|
|
218
|
+
// Use minWidth=10 so column is wider than content "AB" (2 chars)
|
|
219
|
+
const table = new Table(3, 1, {
|
|
220
|
+
maxWidth: 20,
|
|
221
|
+
padding: 0,
|
|
222
|
+
align: Align.LEFT,
|
|
223
|
+
})
|
|
224
|
+
.column(0, { align: Align.RIGHT, minWidth: 10 })
|
|
225
|
+
.row(1, { align: Align.CENTER })
|
|
226
|
+
.cell(0, 0, "AB")
|
|
227
|
+
.cell(1, 0, "AB")
|
|
228
|
+
.cell(2, 0, "AB", { align: Align.LEFT });
|
|
229
|
+
|
|
230
|
+
const result = service.render(table);
|
|
231
|
+
const lines = result.split("\n");
|
|
232
|
+
|
|
233
|
+
// Row 0: column override (RIGHT)
|
|
234
|
+
const row0 = lines[1]!.slice(1, -1);
|
|
235
|
+
expect(row0).toBe(" ".repeat(8) + "AB");
|
|
236
|
+
|
|
237
|
+
// Row 1: row override (CENTER) takes precedence over column
|
|
238
|
+
const row1 = lines[3]!.slice(1, -1);
|
|
239
|
+
expect(row1).toBe(" ".repeat(4) + "AB" + " ".repeat(4));
|
|
240
|
+
|
|
241
|
+
// Row 2: cell override (LEFT) takes precedence over column
|
|
242
|
+
const row2 = lines[5]!.slice(1, -1);
|
|
243
|
+
expect(row2).toBe("AB" + " ".repeat(8));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("empty cells render as blank space", () => {
|
|
247
|
+
const service = createService();
|
|
248
|
+
const table = new Table(1, 2, { maxWidth: 20, padding: 1 })
|
|
249
|
+
.cell(0, 0, "Hi");
|
|
250
|
+
// cell(0, 1) not set
|
|
251
|
+
|
|
252
|
+
const result = service.render(table);
|
|
253
|
+
const lines = result.split("\n");
|
|
254
|
+
const contentLine = lines[1]!;
|
|
255
|
+
// Should render without error
|
|
256
|
+
expect(contentLine).toContain("Hi");
|
|
257
|
+
// The missing cell should render as blank space
|
|
258
|
+
const parts = contentLine.split("│").filter((s) => s.length > 0);
|
|
259
|
+
expect(parts.length).toBe(2);
|
|
260
|
+
expect(parts[1]!.trim()).toBe("");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("multi-line cell content extends row height", () => {
|
|
264
|
+
const service = createService();
|
|
265
|
+
const table = new Table(1, 2, { maxWidth: 40, padding: 1 })
|
|
266
|
+
.cell(0, 0, "line1\nline2\nline3")
|
|
267
|
+
.cell(0, 1, "single");
|
|
268
|
+
|
|
269
|
+
const result = service.render(table);
|
|
270
|
+
const lines = result.split("\n");
|
|
271
|
+
// Filter content lines (between top and bottom borders)
|
|
272
|
+
const contentLines = lines.filter((l) =>
|
|
273
|
+
l.startsWith("│") && !l.includes("─")
|
|
274
|
+
);
|
|
275
|
+
// Row should have 3 sub-lines due to multi-line content
|
|
276
|
+
expect(contentLines.length).toBe(3);
|
|
277
|
+
expect(contentLines[0]).toContain("line1");
|
|
278
|
+
expect(contentLines[0]).toContain("single");
|
|
279
|
+
expect(contentLines[1]).toContain("line2");
|
|
280
|
+
expect(contentLines[2]).toContain("line3");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("border coloring applies color function to border characters", () => {
|
|
284
|
+
const service = new DefaultTableGeneratorService();
|
|
285
|
+
service.colorEnabled = true;
|
|
286
|
+
service.colorFunction = (text, hex) => `[fg:${hex}]${text}[/fg]`;
|
|
287
|
+
service.backgroundColorFunction = (text, hex) => `[bg:${hex}]${text}[/bg]`;
|
|
288
|
+
|
|
289
|
+
const table = new Table(1, 1, {
|
|
290
|
+
maxWidth: 20,
|
|
291
|
+
padding: 0,
|
|
292
|
+
borderColor: "#ff0000",
|
|
293
|
+
borderBackgroundColor: "#0000ff",
|
|
294
|
+
}).cell(0, 0, "X");
|
|
295
|
+
|
|
296
|
+
const result = service.render(table);
|
|
297
|
+
// Border chars should be wrapped with both color functions
|
|
298
|
+
expect(result).toContain("[fg:#ff0000]");
|
|
299
|
+
expect(result).toContain("[bg:#0000ff]");
|
|
300
|
+
// Content should NOT be colored by border color function
|
|
301
|
+
// The "X" itself should appear without color wrappers
|
|
302
|
+
expect(result).toContain("X");
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("1x1 table renders correctly", () => {
|
|
306
|
+
const service = createService();
|
|
307
|
+
const table = new Table(1, 1, { maxWidth: 10, padding: 0 })
|
|
308
|
+
.cell(0, 0, "Hi");
|
|
309
|
+
|
|
310
|
+
const result = service.render(table);
|
|
311
|
+
const lines = result.split("\n");
|
|
312
|
+
// Should have 3 lines: top border, content, bottom border
|
|
313
|
+
expect(lines.length).toBe(3);
|
|
314
|
+
expect(lines[0]).toMatch(/^┌─+┐$/);
|
|
315
|
+
expect(lines[1]).toMatch(/^│.*│$/);
|
|
316
|
+
expect(lines[1]).toContain("Hi");
|
|
317
|
+
expect(lines[2]).toMatch(/^└─+┘$/);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("padding=0 works", () => {
|
|
321
|
+
const service = createService();
|
|
322
|
+
const table = new Table(1, 1, { maxWidth: 10, padding: 0 })
|
|
323
|
+
.cell(0, 0, "AB");
|
|
324
|
+
|
|
325
|
+
const result = service.render(table);
|
|
326
|
+
const lines = result.split("\n");
|
|
327
|
+
const contentLine = lines[1]!;
|
|
328
|
+
// With padding=0, content sits directly against borders
|
|
329
|
+
// │AB........│
|
|
330
|
+
expect(contentLine.startsWith("│")).toBe(true);
|
|
331
|
+
expect(contentLine.endsWith("│")).toBe(true);
|
|
332
|
+
// No padding space between border and content
|
|
333
|
+
expect(contentLine[1]).toBe("A");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("out-of-bounds row index throws", () => {
|
|
337
|
+
const table = new Table(2, 2);
|
|
338
|
+
expect(() => table.row(5, { align: Align.LEFT })).toThrow(
|
|
339
|
+
/out of bounds/,
|
|
340
|
+
);
|
|
341
|
+
expect(() => table.row(-1, { align: Align.LEFT })).toThrow(
|
|
342
|
+
/out of bounds/,
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("out-of-bounds column index throws", () => {
|
|
347
|
+
const table = new Table(2, 2);
|
|
348
|
+
expect(() => table.column(5, { flexWeight: 1 })).toThrow(
|
|
349
|
+
/out of bounds/,
|
|
350
|
+
);
|
|
351
|
+
expect(() => table.column(-1, { flexWeight: 1 })).toThrow(
|
|
352
|
+
/out of bounds/,
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("out-of-bounds cell index throws", () => {
|
|
357
|
+
const table = new Table(2, 2);
|
|
358
|
+
expect(() => table.cell(5, 0, "X")).toThrow(/out of bounds/);
|
|
359
|
+
expect(() => table.cell(0, 5, "X")).toThrow(/out of bounds/);
|
|
360
|
+
expect(() => table.cell(-1, 0, "X")).toThrow(/out of bounds/);
|
|
361
|
+
expect(() => table.cell(0, -1, "X")).toThrow(/out of bounds/);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import DefaultContext from "../../../src/runtime/DefaultContext.ts";
|
|
3
|
+
import { getCLIConfig } from "../../fixtures/CLIConfig.ts";
|
|
4
|
+
import TableGeneratorServiceProvider from "../../../src/service/tableGenerator/TableGeneratorServiceProvider.ts";
|
|
5
|
+
import { PRINTER_SERVICE_ID } from "../../../src/api/service/core/PrinterService.ts";
|
|
6
|
+
import DefaultPrinterService from "../../../src/service/printer/DefaultPrinterService.ts";
|
|
7
|
+
import StreamString from "../../fixtures/StreamString.ts";
|
|
8
|
+
import TtyTerminal from "../../../src/terminal/TtyTerminal.ts";
|
|
9
|
+
import TtyStyler from "../../../src/terminal/TtyStyler.ts";
|
|
10
|
+
|
|
11
|
+
describe("TableGeneratorServiceProvider tests", () => {
|
|
12
|
+
test("getServiceInfo returns service and empty commands", async () => {
|
|
13
|
+
const provider = new TableGeneratorServiceProvider(100);
|
|
14
|
+
const cliConfig = getCLIConfig();
|
|
15
|
+
const serviceInfo = await provider.getServiceInfo(cliConfig);
|
|
16
|
+
expect(serviceInfo.commands.length).toEqual(0);
|
|
17
|
+
expect(serviceInfo.service).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("initService sets colorEnabled and colorFunction from PrinterService", async () => {
|
|
21
|
+
const dummyStdout = new StreamString();
|
|
22
|
+
const dummyStderr = new StreamString();
|
|
23
|
+
const provider = new TableGeneratorServiceProvider(100);
|
|
24
|
+
const cliConfig = getCLIConfig();
|
|
25
|
+
const context = new DefaultContext(cliConfig);
|
|
26
|
+
context.addServiceInstance(
|
|
27
|
+
PRINTER_SERVICE_ID,
|
|
28
|
+
new DefaultPrinterService(
|
|
29
|
+
dummyStdout.writableStream,
|
|
30
|
+
dummyStderr.writableStream,
|
|
31
|
+
true,
|
|
32
|
+
true,
|
|
33
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
34
|
+
new TtyTerminal(dummyStderr.writeStream),
|
|
35
|
+
new TtyStyler(3),
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
const serviceInfo = await provider.getServiceInfo(cliConfig);
|
|
39
|
+
expect(serviceInfo.commands.length).toEqual(0);
|
|
40
|
+
await provider.initService(context);
|
|
41
|
+
const service = serviceInfo
|
|
42
|
+
.service as import("../../../src/service/tableGenerator/DefaultTableGeneratorService.ts").default;
|
|
43
|
+
expect(service.colorEnabled).toBe(true);
|
|
44
|
+
expect(typeof service.colorFunction).toBe("function");
|
|
45
|
+
expect(typeof service.backgroundColorFunction).toBe("function");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import DefaultTreePrinterService from "../../../src/service/treePrinter/DefaultTreePrinterService.ts";
|
|
3
|
+
|
|
4
|
+
describe("DefaultTreePrinterService tests", () => {
|
|
5
|
+
test("Single root, no children", () => {
|
|
6
|
+
const service = new DefaultTreePrinterService();
|
|
7
|
+
service.colorEnabled = false;
|
|
8
|
+
|
|
9
|
+
expect(service.print({ label: "Root" })).toEqual("Root");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("Root with string children uses correct connectors", () => {
|
|
13
|
+
const service = new DefaultTreePrinterService();
|
|
14
|
+
service.colorEnabled = false;
|
|
15
|
+
|
|
16
|
+
const result = service.print({
|
|
17
|
+
label: "Root",
|
|
18
|
+
children: ["Alpha", "Beta", "Gamma"],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const lines = result.split("\n");
|
|
22
|
+
expect(lines[0]).toEqual("Root");
|
|
23
|
+
expect(lines[1]).toEqual("├── Alpha");
|
|
24
|
+
expect(lines[2]).toEqual("├── Beta");
|
|
25
|
+
expect(lines[3]).toEqual("└── Gamma");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("Root with TreeNode children uses correct connectors", () => {
|
|
29
|
+
const service = new DefaultTreePrinterService();
|
|
30
|
+
service.colorEnabled = false;
|
|
31
|
+
|
|
32
|
+
const result = service.print({
|
|
33
|
+
label: "Root",
|
|
34
|
+
children: [
|
|
35
|
+
{ label: "Child1", children: ["Leaf1", "Leaf2"] },
|
|
36
|
+
{ label: "Child2" },
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const lines = result.split("\n");
|
|
41
|
+
expect(lines[0]).toEqual("Root");
|
|
42
|
+
expect(lines[1]).toEqual("├── Child1");
|
|
43
|
+
expect(lines[2]).toEqual("│ ├── Leaf1");
|
|
44
|
+
expect(lines[3]).toEqual("│ └── Leaf2");
|
|
45
|
+
expect(lines[4]).toEqual("└── Child2");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("Mixed children (strings and nodes at same level)", () => {
|
|
49
|
+
const service = new DefaultTreePrinterService();
|
|
50
|
+
service.colorEnabled = false;
|
|
51
|
+
|
|
52
|
+
const result = service.print({
|
|
53
|
+
label: "Root",
|
|
54
|
+
children: [
|
|
55
|
+
"StringChild",
|
|
56
|
+
{ label: "NodeChild", children: ["Nested"] },
|
|
57
|
+
"Last",
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const lines = result.split("\n");
|
|
62
|
+
expect(lines[0]).toEqual("Root");
|
|
63
|
+
expect(lines[1]).toEqual("├── StringChild");
|
|
64
|
+
expect(lines[2]).toEqual("├── NodeChild");
|
|
65
|
+
expect(lines[3]).toEqual("│ └── Nested");
|
|
66
|
+
expect(lines[4]).toEqual("└── Last");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("Deep nesting propagates continuation chars correctly", () => {
|
|
70
|
+
const service = new DefaultTreePrinterService();
|
|
71
|
+
service.colorEnabled = false;
|
|
72
|
+
|
|
73
|
+
const result = service.print({
|
|
74
|
+
label: "Root",
|
|
75
|
+
children: [
|
|
76
|
+
{
|
|
77
|
+
label: "L1",
|
|
78
|
+
children: [
|
|
79
|
+
{
|
|
80
|
+
label: "L2",
|
|
81
|
+
children: ["L3a", "L3b"],
|
|
82
|
+
},
|
|
83
|
+
"L2sibling",
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
"RootSibling",
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const lines = result.split("\n");
|
|
91
|
+
expect(lines[0]).toEqual("Root");
|
|
92
|
+
expect(lines[1]).toEqual("├── L1");
|
|
93
|
+
expect(lines[2]).toEqual("│ ├── L2");
|
|
94
|
+
expect(lines[3]).toEqual("│ │ ├── L3a");
|
|
95
|
+
expect(lines[4]).toEqual("│ │ └── L3b");
|
|
96
|
+
expect(lines[5]).toEqual("│ └── L2sibling");
|
|
97
|
+
expect(lines[6]).toEqual("└── RootSibling");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("Last child uses └── and others use ├──", () => {
|
|
101
|
+
const service = new DefaultTreePrinterService();
|
|
102
|
+
service.colorEnabled = false;
|
|
103
|
+
|
|
104
|
+
const result = service.print({
|
|
105
|
+
label: "Root",
|
|
106
|
+
children: ["A", "B", "C"],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const lines = result.split("\n");
|
|
110
|
+
expect(lines[1]!.startsWith("├── ")).toBe(true);
|
|
111
|
+
expect(lines[2]!.startsWith("├── ")).toBe(true);
|
|
112
|
+
expect(lines[3]!.startsWith("└── ")).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("colorEnabled=true wraps labels via colorFunction", () => {
|
|
116
|
+
const service = new DefaultTreePrinterService();
|
|
117
|
+
service.colorEnabled = true;
|
|
118
|
+
service.colorFunction = (text, _color) => `[${text}]`;
|
|
119
|
+
|
|
120
|
+
const result = service.print({
|
|
121
|
+
label: "Root",
|
|
122
|
+
children: ["Child"],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const lines = result.split("\n");
|
|
126
|
+
expect(lines[0]).toEqual("[Root]");
|
|
127
|
+
expect(lines[1]).toEqual("└── [Child]");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("colorEnabled=false returns plain text", () => {
|
|
131
|
+
const service = new DefaultTreePrinterService();
|
|
132
|
+
service.colorEnabled = false;
|
|
133
|
+
service.colorFunction = (text, _color) => `[${text}]`;
|
|
134
|
+
|
|
135
|
+
const result = service.print({
|
|
136
|
+
label: "Root",
|
|
137
|
+
children: ["Child"],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const lines = result.split("\n");
|
|
141
|
+
expect(lines[0]).toEqual("Root");
|
|
142
|
+
expect(lines[1]).toEqual("└── Child");
|
|
143
|
+
});
|
|
144
|
+
});
|