@dungle-scrubs/tallow 0.8.21 → 0.8.23
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/dist/cli.js +35 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +2 -0
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +82 -0
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/sdk.d.ts +17 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +68 -1
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-relay.d.ts +40 -7
- package/dist/workspace-transition-relay.d.ts.map +1 -1
- package/dist/workspace-transition-relay.js +81 -16
- package/dist/workspace-transition-relay.js.map +1 -1
- package/extensions/__integration__/background-task-widget-ownership.test.ts +216 -0
- package/extensions/__integration__/claude-hooks-compat.test.ts +156 -0
- package/extensions/__integration__/slash-command-bridge.test.ts +169 -23
- package/extensions/_shared/atomic-write.ts +1 -1
- package/extensions/_shared/bordered-box.ts +102 -0
- package/extensions/_shared/interop-events.ts +5 -0
- package/extensions/_shared/pid-registry.ts +1 -1
- package/extensions/agent-commands-tool/index.ts +4 -1
- package/extensions/background-task-tool/__tests__/lifecycle.test.ts +50 -25
- package/extensions/background-task-tool/index.ts +139 -221
- package/extensions/bash-tool-enhanced/index.ts +1 -75
- package/extensions/cd-tool/index.ts +2 -2
- package/extensions/context-fork/spawn.ts +4 -1
- package/extensions/health/index.ts +6 -6
- package/extensions/hooks/__tests__/claude-compat.test.ts +35 -0
- package/extensions/hooks/__tests__/subprocess-hardening.test.ts +73 -0
- package/extensions/hooks/index.ts +27 -4
- package/extensions/loop/__tests__/loop.test.ts +168 -4
- package/extensions/loop/extension.json +6 -5
- package/extensions/loop/index.ts +242 -31
- package/extensions/plan-mode-tool/__tests__/agent-end-execution.test.ts +373 -0
- package/extensions/plan-mode-tool/index.ts +103 -41
- package/extensions/prompt-suggestions/__tests__/editor-compatibility.test.ts +42 -0
- package/extensions/prompt-suggestions/index.ts +41 -6
- package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +267 -671
- package/extensions/slash-command-bridge/extension.json +1 -1
- package/extensions/slash-command-bridge/index.ts +230 -116
- package/extensions/subagent-tool/index.ts +2 -2
- package/extensions/subagent-tool/process.ts +4 -5
- package/extensions/tasks/commands/register-tasks-extension.ts +41 -0
- package/extensions/teams-tool/__tests__/peer-messaging.test.ts +29 -24
- package/extensions/teams-tool/dashboard.ts +3 -5
- package/extensions/teams-tool/dispatch/auto-dispatch.ts +18 -1
- package/extensions/teams-tool/tools/teammate-tools.ts +9 -6
- package/extensions/wezterm-pane-control/__tests__/index.test.ts +88 -4
- package/extensions/wezterm-pane-control/index.ts +113 -8
- package/package.json +6 -4
- package/packages/tallow-tui/README.md +51 -0
- package/packages/tallow-tui/dist/autocomplete.d.ts +48 -0
- package/packages/tallow-tui/dist/autocomplete.d.ts.map +1 -0
- package/packages/tallow-tui/dist/autocomplete.js +564 -0
- package/packages/tallow-tui/dist/autocomplete.js.map +1 -0
- package/packages/tallow-tui/dist/border-styles.d.ts +32 -0
- package/packages/tallow-tui/dist/border-styles.d.ts.map +1 -0
- package/packages/tallow-tui/dist/border-styles.js +46 -0
- package/packages/tallow-tui/dist/border-styles.js.map +1 -0
- package/packages/tallow-tui/dist/components/bordered-box.d.ts +52 -0
- package/packages/tallow-tui/dist/components/bordered-box.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/bordered-box.js +89 -0
- package/packages/tallow-tui/dist/components/bordered-box.js.map +1 -0
- package/packages/tallow-tui/dist/components/box.d.ts +22 -0
- package/packages/tallow-tui/dist/components/box.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/box.js +104 -0
- package/packages/tallow-tui/dist/components/box.js.map +1 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.d.ts +22 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.js +35 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.js.map +1 -0
- package/packages/tallow-tui/dist/components/editor.d.ts +240 -0
- package/packages/tallow-tui/dist/components/editor.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/editor.js +1766 -0
- package/packages/tallow-tui/dist/components/editor.js.map +1 -0
- package/packages/tallow-tui/dist/components/image.d.ts +126 -0
- package/packages/tallow-tui/dist/components/image.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/image.js +245 -0
- package/packages/tallow-tui/dist/components/image.js.map +1 -0
- package/packages/tallow-tui/dist/components/input.d.ts +37 -0
- package/packages/tallow-tui/dist/components/input.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/input.js +439 -0
- package/packages/tallow-tui/dist/components/input.js.map +1 -0
- package/packages/tallow-tui/dist/components/loader.d.ts +88 -0
- package/packages/tallow-tui/dist/components/loader.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/loader.js +146 -0
- package/packages/tallow-tui/dist/components/loader.js.map +1 -0
- package/packages/tallow-tui/dist/components/markdown.d.ts +95 -0
- package/packages/tallow-tui/dist/components/markdown.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/markdown.js +633 -0
- package/packages/tallow-tui/dist/components/markdown.js.map +1 -0
- package/packages/tallow-tui/dist/components/select-list.d.ts +32 -0
- package/packages/tallow-tui/dist/components/select-list.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/select-list.js +156 -0
- package/packages/tallow-tui/dist/components/select-list.js.map +1 -0
- package/packages/tallow-tui/dist/components/settings-list.d.ts +50 -0
- package/packages/tallow-tui/dist/components/settings-list.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/settings-list.js +189 -0
- package/packages/tallow-tui/dist/components/settings-list.js.map +1 -0
- package/packages/tallow-tui/dist/components/spacer.d.ts +12 -0
- package/packages/tallow-tui/dist/components/spacer.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/spacer.js +23 -0
- package/packages/tallow-tui/dist/components/spacer.js.map +1 -0
- package/packages/tallow-tui/dist/components/text.d.ts +19 -0
- package/packages/tallow-tui/dist/components/text.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/text.js +91 -0
- package/packages/tallow-tui/dist/components/text.js.map +1 -0
- package/packages/tallow-tui/dist/components/truncated-text.d.ts +13 -0
- package/packages/tallow-tui/dist/components/truncated-text.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/truncated-text.js +51 -0
- package/packages/tallow-tui/dist/components/truncated-text.js.map +1 -0
- package/packages/tallow-tui/dist/editor-component.d.ts +50 -0
- package/packages/tallow-tui/dist/editor-component.d.ts.map +1 -0
- package/packages/tallow-tui/dist/editor-component.js +2 -0
- package/packages/tallow-tui/dist/editor-component.js.map +1 -0
- package/packages/tallow-tui/dist/fuzzy.d.ts +16 -0
- package/packages/tallow-tui/dist/fuzzy.d.ts.map +1 -0
- package/packages/tallow-tui/dist/fuzzy.js +107 -0
- package/packages/tallow-tui/dist/fuzzy.js.map +1 -0
- package/packages/tallow-tui/dist/index.d.ts +25 -0
- package/packages/tallow-tui/dist/index.d.ts.map +1 -0
- package/packages/tallow-tui/dist/index.js +35 -0
- package/packages/tallow-tui/dist/index.js.map +1 -0
- package/packages/tallow-tui/dist/keybindings.d.ts +39 -0
- package/packages/tallow-tui/dist/keybindings.d.ts.map +1 -0
- package/packages/tallow-tui/dist/keybindings.js +114 -0
- package/packages/tallow-tui/dist/keybindings.js.map +1 -0
- package/packages/tallow-tui/dist/keys.d.ts +168 -0
- package/packages/tallow-tui/dist/keys.d.ts.map +1 -0
- package/packages/tallow-tui/dist/keys.js +971 -0
- package/packages/tallow-tui/dist/keys.js.map +1 -0
- package/packages/tallow-tui/dist/kill-ring.d.ts +28 -0
- package/packages/tallow-tui/dist/kill-ring.d.ts.map +1 -0
- package/packages/tallow-tui/dist/kill-ring.js +44 -0
- package/packages/tallow-tui/dist/kill-ring.js.map +1 -0
- package/packages/tallow-tui/dist/stdin-buffer.d.ts +48 -0
- package/packages/tallow-tui/dist/stdin-buffer.d.ts.map +1 -0
- package/packages/tallow-tui/dist/stdin-buffer.js +317 -0
- package/packages/tallow-tui/dist/stdin-buffer.js.map +1 -0
- package/packages/tallow-tui/dist/terminal-image.d.ts +161 -0
- package/packages/tallow-tui/dist/terminal-image.d.ts.map +1 -0
- package/packages/tallow-tui/dist/terminal-image.js +460 -0
- package/packages/tallow-tui/dist/terminal-image.js.map +1 -0
- package/packages/tallow-tui/dist/terminal.d.ts +102 -0
- package/packages/tallow-tui/dist/terminal.d.ts.map +1 -0
- package/packages/tallow-tui/dist/terminal.js +263 -0
- package/packages/tallow-tui/dist/terminal.js.map +1 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.d.ts +14 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.d.ts.map +1 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.js +55 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.js.map +1 -0
- package/packages/tallow-tui/dist/tui.d.ts +239 -0
- package/packages/tallow-tui/dist/tui.d.ts.map +1 -0
- package/packages/tallow-tui/dist/tui.js +1058 -0
- package/packages/tallow-tui/dist/tui.js.map +1 -0
- package/packages/tallow-tui/dist/undo-stack.d.ts +17 -0
- package/packages/tallow-tui/dist/undo-stack.d.ts.map +1 -0
- package/packages/tallow-tui/dist/undo-stack.js +25 -0
- package/packages/tallow-tui/dist/undo-stack.js.map +1 -0
- package/packages/tallow-tui/dist/utils.d.ts +96 -0
- package/packages/tallow-tui/dist/utils.d.ts.map +1 -0
- package/packages/tallow-tui/dist/utils.js +843 -0
- package/packages/tallow-tui/dist/utils.js.map +1 -0
- package/packages/tallow-tui/package.json +24 -0
- package/packages/tallow-tui/src/__tests__/__snapshots__/render.test.ts.snap +121 -0
- package/packages/tallow-tui/src/__tests__/editor-border.test.ts +72 -0
- package/packages/tallow-tui/src/__tests__/editor-change-listener.test.ts +121 -0
- package/packages/tallow-tui/src/__tests__/editor-ghost-text.test.ts +112 -0
- package/packages/tallow-tui/src/__tests__/fuzzy.test.ts +91 -0
- package/packages/tallow-tui/src/__tests__/image-component.test.ts +113 -0
- package/packages/tallow-tui/src/__tests__/keys.test.ts +141 -0
- package/packages/tallow-tui/src/__tests__/render.test.ts +179 -0
- package/packages/tallow-tui/src/__tests__/stdin-buffer.test.ts +82 -0
- package/packages/tallow-tui/src/__tests__/terminal-image.test.ts +363 -0
- package/packages/tallow-tui/src/__tests__/tui-diff-regression.test.ts +454 -0
- package/packages/tallow-tui/src/__tests__/tui-render-scheduling.test.ts +256 -0
- package/packages/tallow-tui/src/__tests__/utils.test.ts +259 -0
- package/packages/tallow-tui/src/autocomplete.ts +716 -0
- package/packages/tallow-tui/src/border-styles.ts +60 -0
- package/packages/tallow-tui/src/components/bordered-box.ts +113 -0
- package/packages/tallow-tui/src/components/box.ts +137 -0
- package/packages/tallow-tui/src/components/cancellable-loader.ts +40 -0
- package/packages/tallow-tui/src/components/editor.ts +2143 -0
- package/packages/tallow-tui/src/components/image.ts +315 -0
- package/packages/tallow-tui/src/components/input.ts +522 -0
- package/packages/tallow-tui/src/components/loader.ts +187 -0
- package/packages/tallow-tui/src/components/markdown.ts +780 -0
- package/packages/tallow-tui/src/components/select-list.ts +197 -0
- package/packages/tallow-tui/src/components/settings-list.ts +264 -0
- package/packages/tallow-tui/src/components/spacer.ts +28 -0
- package/packages/tallow-tui/src/components/text.ts +113 -0
- package/packages/tallow-tui/src/components/truncated-text.ts +65 -0
- package/packages/tallow-tui/src/editor-component.ts +92 -0
- package/packages/tallow-tui/src/fuzzy.ts +133 -0
- package/packages/tallow-tui/src/index.ts +118 -0
- package/packages/tallow-tui/src/keybindings.ts +183 -0
- package/packages/tallow-tui/src/keys.ts +1189 -0
- package/packages/tallow-tui/src/kill-ring.ts +46 -0
- package/packages/tallow-tui/src/stdin-buffer.ts +386 -0
- package/packages/tallow-tui/src/terminal-image.ts +619 -0
- package/packages/tallow-tui/src/terminal.ts +350 -0
- package/packages/tallow-tui/src/test-utils/capability-env.ts +56 -0
- package/packages/tallow-tui/src/tui.ts +1336 -0
- package/packages/tallow-tui/src/undo-stack.ts +28 -0
- package/packages/tallow-tui/src/utils.ts +948 -0
- package/packages/tallow-tui/tsconfig.build.json +21 -0
- package/runtime/agent-runner.ts +20 -0
- package/runtime/atomic-write.ts +8 -0
- package/runtime/otel.ts +12 -0
- package/runtime/resolve-module.ts +23 -0
- package/runtime/runtime-path-provider.ts +12 -0
- package/runtime/runtime-provenance.ts +17 -0
- package/runtime/workspace-transition-relay.ts +21 -0
- package/runtime/workspace-transition.ts +29 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tallow-tui core utility functions: visibleWidth, truncateToWidth,
|
|
3
|
+
* wrapTextWithAnsi, hyperlink, and fileLink.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "bun:test";
|
|
6
|
+
import { fileLink, hyperlink, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils.js";
|
|
7
|
+
|
|
8
|
+
// ── visibleWidth ─────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
describe("visibleWidth", () => {
|
|
11
|
+
it("counts ASCII characters", () => {
|
|
12
|
+
expect(visibleWidth("hello")).toBe(5);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns 0 for empty string", () => {
|
|
16
|
+
expect(visibleWidth("")).toBe(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("ignores SGR color codes", () => {
|
|
20
|
+
expect(visibleWidth("\x1b[31mred\x1b[0m")).toBe(3);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("ignores nested ANSI codes", () => {
|
|
24
|
+
expect(visibleWidth("\x1b[1m\x1b[31mbold red\x1b[0m")).toBe(8);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("ignores OSC 8 hyperlink sequences", () => {
|
|
28
|
+
expect(visibleWidth("\x1b]8;;http://example.com\x07link\x1b]8;;\x07")).toBe(4);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("counts CJK characters as double-width", () => {
|
|
32
|
+
expect(visibleWidth("你好")).toBe(4);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("handles mixed ASCII and CJK", () => {
|
|
36
|
+
expect(visibleWidth("hi你好")).toBe(6);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("counts emoji as double-width", () => {
|
|
40
|
+
expect(visibleWidth("👋")).toBe(2);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("counts tabs as 3 spaces", () => {
|
|
44
|
+
expect(visibleWidth("\t")).toBe(3);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("counts skin tone modifiers as single glyph", () => {
|
|
48
|
+
expect(visibleWidth("👋🏽")).toBe(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("handles string with only ANSI codes", () => {
|
|
52
|
+
expect(visibleWidth("\x1b[31m\x1b[0m")).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("handles multiple emoji in a row", () => {
|
|
56
|
+
const w = visibleWidth("🔥🎉✅");
|
|
57
|
+
expect(w).toBeGreaterThanOrEqual(4); // at least 2 wide emoji
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ── OSC sequence handling ────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
it("strips terminated OSC 1337 SetUserVar (BEL)", () => {
|
|
63
|
+
expect(visibleWidth("\x1b]1337;SetUserVar=pi_status=ZG9uZQ==\x07(pass)")).toBe(6);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("strips terminated OSC 1337 SetUserVar (ST)", () => {
|
|
67
|
+
expect(visibleWidth("\x1b]1337;SetUserVar=pi_status=ZG9uZQ==\x1b\\(pass)")).toBe(6);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("strips unterminated OSC without crashing (may over-strip)", () => {
|
|
71
|
+
// Without a terminator, visible text after the OSC body is ambiguous.
|
|
72
|
+
// The regex consumes everything until the next ESC or end-of-string.
|
|
73
|
+
// Over-stripping is acceptable — crashing is not.
|
|
74
|
+
const width = visibleWidth("\x1b]1337;SetUserVar=pi_status=ZG9uZQ==(pass)");
|
|
75
|
+
expect(width).toBeLessThanOrEqual(6);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("strips chained unterminated OSC sequences without crashing", () => {
|
|
79
|
+
// Each unterminated OSC consumes up to the next ESC. The last one
|
|
80
|
+
// consumes to end-of-string, eating any trailing visible text.
|
|
81
|
+
const line =
|
|
82
|
+
"\x1b]1337;SetUserVar=pi_status=" +
|
|
83
|
+
"\x1b]1337;SetUserVar=pi_status=d29ya2luZw==" +
|
|
84
|
+
"\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
|
|
85
|
+
"(pass)";
|
|
86
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(6);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("unterminated OSC stops at next ESC (preserves text after next escape)", () => {
|
|
90
|
+
// Unterminated OSC consumes to next ESC, but text AFTER the next CSI is preserved.
|
|
91
|
+
const line =
|
|
92
|
+
"\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
|
|
93
|
+
"\x1b[39m" + // CSI resets color — next ESC stops the unterminated OSC
|
|
94
|
+
"visible";
|
|
95
|
+
expect(visibleWidth(line)).toBe(7);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("handles real crash pattern: chained unterminated OSC between CSI sequences", () => {
|
|
99
|
+
// Reproduces the actual crash: CSI color + unterminated OSC chain + CSI reset
|
|
100
|
+
const line =
|
|
101
|
+
"\x1b[48;2;10;10;20m" +
|
|
102
|
+
"\x1b[38;2;152;152;152m" +
|
|
103
|
+
"\x1b]1337;SetUserVar=pi_status=" +
|
|
104
|
+
"\x1b]1337;SetUserVar=pi_status=d29ya2luZw==" +
|
|
105
|
+
"\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
|
|
106
|
+
"\x1b]1337;SetUserVar=pi_status=d29ya2luZw==" +
|
|
107
|
+
"\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
|
|
108
|
+
"(pass)" +
|
|
109
|
+
"\x1b[49m\x1b[0m";
|
|
110
|
+
// Must not exceed any reasonable terminal width — the original crash was 182
|
|
111
|
+
expect(visibleWidth(line)).toBeLessThan(20);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("strips unterminated APC sequences without crashing", () => {
|
|
115
|
+
const width = visibleWidth("\x1b_some-apc-data(pass)");
|
|
116
|
+
expect(width).toBeLessThanOrEqual(6);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("strips arbitrary unterminated OSC numbers", () => {
|
|
120
|
+
const width = visibleWidth("\x1b]999;custom=datavisible");
|
|
121
|
+
expect(width).toBeLessThanOrEqual(7);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ── truncateToWidth ──────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
describe("truncateToWidth", () => {
|
|
128
|
+
it("returns string unchanged when within width", () => {
|
|
129
|
+
expect(truncateToWidth("hello", 10, "…")).toBe("hello");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("truncates and appends ellipsis", () => {
|
|
133
|
+
const result = truncateToWidth("hello world", 8, "…");
|
|
134
|
+
expect(visibleWidth(result)).toBeLessThanOrEqual(8);
|
|
135
|
+
expect(result).toContain("…");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("handles CJK truncation at character boundary", () => {
|
|
139
|
+
const result = truncateToWidth("你好世界", 5, "…");
|
|
140
|
+
expect(visibleWidth(result)).toBeLessThanOrEqual(5);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("preserves ANSI codes in truncated output", () => {
|
|
144
|
+
const result = truncateToWidth("\x1b[31mhello world\x1b[0m", 8, "…");
|
|
145
|
+
expect(result).toContain("\x1b[31m");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("handles exact width match", () => {
|
|
149
|
+
const result = truncateToWidth("hello", 5, "…");
|
|
150
|
+
expect(result).toBe("hello");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("handles width of 1 with ellipsis", () => {
|
|
154
|
+
const result = truncateToWidth("hello", 1, "…");
|
|
155
|
+
expect(visibleWidth(result)).toBeLessThanOrEqual(1);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("handles OSC 8 hyperlinks", () => {
|
|
159
|
+
const linked = "\x1b]8;;file:///test\x07long-filename.ts\x1b]8;;\x07";
|
|
160
|
+
const result = truncateToWidth(linked, 10, "…");
|
|
161
|
+
expect(visibleWidth(result)).toBeLessThanOrEqual(10);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("preserves ANSI styling through ellipsis", () => {
|
|
165
|
+
const red = "\x1b[31m";
|
|
166
|
+
const reset = "\x1b[0m";
|
|
167
|
+
const styled = `${red}${"a".repeat(20)}${reset}`;
|
|
168
|
+
const result = truncateToWidth(styled, 10, "…");
|
|
169
|
+
// Ellipsis should appear BEFORE reset so it inherits the line's styling
|
|
170
|
+
expect(result).toContain("…\x1b[0m");
|
|
171
|
+
expect(result).not.toContain("\x1b[0m…");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("places reset after ellipsis for unstyled text too", () => {
|
|
175
|
+
const result = truncateToWidth("a".repeat(20), 10, "…");
|
|
176
|
+
// Even for unstyled text, the reset-after-ellipsis order is correct
|
|
177
|
+
expect(result).toContain("…\x1b[0m");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ── wrapTextWithAnsi ─────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
describe("wrapTextWithAnsi", () => {
|
|
184
|
+
it("wraps plain text at word boundaries", () => {
|
|
185
|
+
const lines = wrapTextWithAnsi("hello world foo", 10);
|
|
186
|
+
expect(lines.length).toBeGreaterThan(1);
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(10);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("preserves ANSI codes across line breaks", () => {
|
|
193
|
+
const lines = wrapTextWithAnsi("\x1b[31mhello world\x1b[0m", 8);
|
|
194
|
+
expect(lines.length).toBeGreaterThan(1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("handles long words that exceed width", () => {
|
|
198
|
+
const lines = wrapTextWithAnsi("superlongword", 5);
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(5);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("handles empty string", () => {
|
|
205
|
+
expect(wrapTextWithAnsi("", 80)).toEqual([""]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("handles CJK wrapping", () => {
|
|
209
|
+
const lines = wrapTextWithAnsi("你好世界测试", 5);
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(5);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("does not wrap when text fits", () => {
|
|
216
|
+
const lines = wrapTextWithAnsi("short", 80);
|
|
217
|
+
expect(lines).toHaveLength(1);
|
|
218
|
+
expect(lines[0]).toBe("short");
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ── hyperlink ────────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
describe("hyperlink", () => {
|
|
225
|
+
it("wraps text in OSC 8 sequences", () => {
|
|
226
|
+
const result = hyperlink("https://example.com", "click");
|
|
227
|
+
expect(result).toBe("\x1b]8;;https://example.com\x07click\x1b]8;;\x07");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("has zero visible width overhead", () => {
|
|
231
|
+
expect(visibleWidth(hyperlink("https://x.com", "text"))).toBe(4);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("handles empty text", () => {
|
|
235
|
+
const result = hyperlink("https://x.com", "");
|
|
236
|
+
expect(visibleWidth(result)).toBe(0);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ── fileLink ─────────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
describe("fileLink", () => {
|
|
243
|
+
it("creates file:// URL from path", () => {
|
|
244
|
+
const result = fileLink("/path/to/file.ts");
|
|
245
|
+
expect(result).toContain("file:///path/to/file.ts");
|
|
246
|
+
expect(visibleWidth(result)).toBe("/path/to/file.ts".length);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("percent-encodes spaces in path", () => {
|
|
250
|
+
const result = fileLink("/path/with spaces/file.ts");
|
|
251
|
+
expect(result).toContain("file:///path/with%20spaces/file.ts");
|
|
252
|
+
expect(visibleWidth(result)).toBe("/path/with spaces/file.ts".length);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("uses custom display text", () => {
|
|
256
|
+
const result = fileLink("/long/path/file.ts", "file.ts");
|
|
257
|
+
expect(visibleWidth(result)).toBe(7);
|
|
258
|
+
});
|
|
259
|
+
});
|