@selesai/code 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/config.d.ts +5 -3
- package/dist/config.js +6 -4
- package/dist/core/skills.d.ts +2 -0
- package/dist/core/skills.js +1 -0
- package/dist/defaults/settings.json +0 -3
- package/dist/extensions/auto-session-name.test.ts +157 -0
- package/dist/extensions/auto-session-name.ts +96 -0
- package/dist/extensions/cost-reconcile.test.ts +76 -0
- package/dist/extensions/cost-reconcile.ts +278 -0
- package/dist/extensions/enable-readonly-tools.test.ts +11 -0
- package/dist/extensions/enable-readonly-tools.ts +7 -3
- package/dist/extensions/package.json +3 -0
- package/dist/extensions/pi-tool-display/CHANGELOG.md +329 -0
- package/dist/extensions/pi-tool-display/LICENSE +21 -0
- package/dist/extensions/pi-tool-display/README.md +417 -0
- package/dist/extensions/pi-tool-display/config/config.example.json +40 -0
- package/dist/extensions/pi-tool-display/index.ts +3 -0
- package/dist/extensions/pi-tool-display/package-lock.json +2555 -0
- package/dist/extensions/pi-tool-display/package.json +86 -0
- package/dist/extensions/pi-tool-display/src/agent-dir.ts +35 -0
- package/dist/extensions/pi-tool-display/src/ansi-utils.ts +116 -0
- package/dist/extensions/pi-tool-display/src/bash-display.ts +213 -0
- package/dist/extensions/pi-tool-display/src/capabilities.ts +88 -0
- package/dist/extensions/pi-tool-display/src/config-modal.ts +541 -0
- package/dist/extensions/pi-tool-display/src/config-store.ts +304 -0
- package/dist/extensions/pi-tool-display/src/debug-logger.ts +150 -0
- package/dist/extensions/pi-tool-display/src/diff-presentation.ts +72 -0
- package/dist/extensions/pi-tool-display/src/diff-renderer.ts +2517 -0
- package/dist/extensions/pi-tool-display/src/disposable.ts +34 -0
- package/dist/extensions/pi-tool-display/src/extension-lifecycle.ts +14 -0
- package/dist/extensions/pi-tool-display/src/index.ts +109 -0
- package/dist/extensions/pi-tool-display/src/line-width-safety.ts +93 -0
- package/dist/extensions/pi-tool-display/src/modal-icons.ts +57 -0
- package/dist/extensions/pi-tool-display/src/pending-diff-preview.ts +370 -0
- package/dist/extensions/pi-tool-display/src/presets.ts +117 -0
- package/dist/extensions/pi-tool-display/src/render-utils.ts +186 -0
- package/dist/extensions/pi-tool-display/src/settings-inspector-modal.ts +475 -0
- package/dist/extensions/pi-tool-display/src/thinking-label.ts +343 -0
- package/dist/extensions/pi-tool-display/src/tool-call-box.ts +181 -0
- package/dist/extensions/pi-tool-display/src/tool-metadata.ts +139 -0
- package/dist/extensions/pi-tool-display/src/tool-overrides.ts +2168 -0
- package/dist/extensions/pi-tool-display/src/types.ts +109 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-markdown.ts +64 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-native.ts +66 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-patch.ts +72 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-renderer.ts +419 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-utils.ts +106 -0
- package/dist/extensions/pi-tool-display/src/write-display-utils.ts +31 -0
- package/dist/extensions/pi-tool-display/src/zellij-modal.ts +1027 -0
- package/dist/extensions/pi-tool-display/tests/active-backlog-red.test.ts +65 -0
- package/dist/extensions/pi-tool-display/tests/ansi-utils.test.ts +345 -0
- package/dist/extensions/pi-tool-display/tests/bash-display.test.ts +442 -0
- package/dist/extensions/pi-tool-display/tests/capabilities-edge.test.ts +360 -0
- package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +54 -0
- package/dist/extensions/pi-tool-display/tests/config-modal.test.ts +360 -0
- package/dist/extensions/pi-tool-display/tests/config-store.test.ts +87 -0
- package/dist/extensions/pi-tool-display/tests/custom-tool-overrides.test.ts +415 -0
- package/dist/extensions/pi-tool-display/tests/debug-logger-edge.test.ts +630 -0
- package/dist/extensions/pi-tool-display/tests/debug-logger.test.ts +62 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-ansi.test.ts +55 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-edge.test.ts +886 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-numbering.test.ts +135 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-width.test.ts +454 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-wrap.test.ts +169 -0
- package/dist/extensions/pi-tool-display/tests/index-integration.test.ts +354 -0
- package/dist/extensions/pi-tool-display/tests/pending-diff-preview.test.ts +79 -0
- package/dist/extensions/pi-tool-display/tests/presets-edge.test.ts +229 -0
- package/dist/extensions/pi-tool-display/tests/presets.test.ts +26 -0
- package/dist/extensions/pi-tool-display/tests/reload-behavior.test.ts +1241 -0
- package/dist/extensions/pi-tool-display/tests/render-utils.test.ts +710 -0
- package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +450 -0
- package/dist/extensions/pi-tool-display/tests/thinking-label-edge.test.ts +904 -0
- package/dist/extensions/pi-tool-display/tests/thinking-label.test.ts +72 -0
- package/dist/extensions/pi-tool-display/tests/tool-call-box.test.ts +105 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-config.test.ts +613 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +499 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-registration.test.ts +315 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-write-meta.test.ts +68 -0
- package/dist/extensions/pi-tool-display/tests/tool-ui-utils.test.ts +413 -0
- package/dist/extensions/pi-tool-display/tests/user-message-box-performance-red.test.ts +176 -0
- package/dist/extensions/pi-tool-display/tests/user-message-box.test.ts +908 -0
- package/dist/extensions/pi-tool-display/tests/zellij-modal.test.ts +848 -0
- package/dist/extensions/pi-tool-display/tool-display-api-consumer.d.ts +30 -0
- package/dist/extensions/pi-tool-display/tool-display-api-consumer.js +40 -0
- package/dist/extensions/pi-zentui/extensions/zentui/format.ts +46 -1
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +10 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +3 -3
- package/dist/extensions/pi-zentui/test/format.test.ts +50 -2
- package/dist/modes/interactive/components/startup-box.d.ts +38 -0
- package/dist/modes/interactive/components/startup-box.js +90 -0
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.js +85 -122
- package/dist/skills/agent-browser/SKILL.md +1 -0
- package/dist/skills/batch-grill-me/SKILL.md +1 -0
- package/dist/skills/brandkit/SKILL.md +1 -0
- package/dist/skills/design-references/SKILL.md +1 -0
- package/dist/skills/design-taste-frontend/SKILL.md +1 -0
- package/dist/skills/design-taste-frontend-v1/SKILL.md +1 -0
- package/dist/skills/full-output-enforcement/SKILL.md +1 -0
- package/dist/skills/gpt-taste/SKILL.md +1 -0
- package/dist/skills/grill-me/SKILL.md +1 -0
- package/dist/skills/handoff/SKILL.md +1 -0
- package/dist/skills/handoff-text/SKILL.md +1 -0
- package/dist/skills/high-end-visual-design/SKILL.md +1 -0
- package/dist/skills/image-to-code/SKILL.md +1 -0
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +1 -0
- package/dist/skills/imagegen-frontend-web/SKILL.md +1 -0
- package/dist/skills/implanger/SKILL.md +1 -0
- package/dist/skills/improve-codebase/SKILL.md +1 -0
- package/dist/skills/industrial-brutalist-ui/SKILL.md +1 -0
- package/dist/skills/minimalist-ui/SKILL.md +1 -0
- package/dist/skills/pi-subagents/SKILL.md +1 -0
- package/dist/skills/planger/SKILL.md +1 -0
- package/dist/skills/ponytail/SKILL.md +1 -0
- package/dist/skills/ponytail-audit/SKILL.md +1 -0
- package/dist/skills/ponytail-debt/SKILL.md +1 -0
- package/dist/skills/ponytail-gain/SKILL.md +1 -0
- package/dist/skills/ponytail-help/SKILL.md +1 -0
- package/dist/skills/ponytail-review/SKILL.md +1 -0
- package/dist/skills/redesign-existing-projects/SKILL.md +1 -0
- package/dist/skills/selesai-handoff/SKILL.md +1 -0
- package/dist/skills/stitch-design-taste/SKILL.md +1 -0
- package/dist/skills/unlazy/SKILL.md +1 -0
- package/dist/skills/web-design-guidelines/SKILL.md +1 -0
- package/dist/skills/workflow/SKILL.md +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { Box, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
4
|
+
import {
|
|
5
|
+
normalizeDiffRenderWidth,
|
|
6
|
+
resolveDiffPresentationMode,
|
|
7
|
+
buildDiffSummaryText,
|
|
8
|
+
} from "../src/diff-presentation.ts";
|
|
9
|
+
import {
|
|
10
|
+
renderEditDiffResult,
|
|
11
|
+
renderWriteDiffResult,
|
|
12
|
+
} from "../src/diff-renderer.ts";
|
|
13
|
+
import type { ToolDisplayConfig } from "../src/types.ts";
|
|
14
|
+
|
|
15
|
+
// ─── Test helpers ──────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const passThroughTheme = {
|
|
18
|
+
fg: (_color: string, text: string): string => text,
|
|
19
|
+
bold: (text: string): string => text,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const defaultConfig: Partial<ToolDisplayConfig> = {
|
|
23
|
+
diffViewMode: "auto",
|
|
24
|
+
diffSplitMinWidth: 80,
|
|
25
|
+
diffCollapsedLines: 24,
|
|
26
|
+
diffWordWrap: false,
|
|
27
|
+
diffIndicatorMode: "bars",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function renderInsideToolBox(component: Component, width: number): string[] {
|
|
31
|
+
const box = new Box(1, 1);
|
|
32
|
+
box.addChild(component);
|
|
33
|
+
return box.render(width);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── normalizeDiffRenderWidth extreme values ───────────────────────────────
|
|
37
|
+
|
|
38
|
+
test("normalizeDiffRenderWidth returns 0 for negative values", () => {
|
|
39
|
+
assert.equal(normalizeDiffRenderWidth(-1), 0);
|
|
40
|
+
assert.equal(normalizeDiffRenderWidth(-100), 0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("normalizeDiffRenderWidth returns 0 for NaN", () => {
|
|
44
|
+
assert.equal(normalizeDiffRenderWidth(NaN), 0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("normalizeDiffRenderWidth returns 0 for Infinity", () => {
|
|
48
|
+
assert.equal(normalizeDiffRenderWidth(Infinity), 0);
|
|
49
|
+
assert.equal(normalizeDiffRenderWidth(-Infinity), 0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("normalizeDiffRenderWidth returns 0 for zero", () => {
|
|
53
|
+
assert.equal(normalizeDiffRenderWidth(0), 0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("normalizeDiffRenderWidth floors to integer", () => {
|
|
57
|
+
assert.equal(normalizeDiffRenderWidth(10.7), 10);
|
|
58
|
+
assert.equal(normalizeDiffRenderWidth(99.99), 99);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("normalizeDiffRenderWidth clamps negative floor result to 0", () => {
|
|
62
|
+
// normalizeDiffRenderWidth(-0.5): Number.isFinite(-0.5) → true, Math.floor(-0.5) → -1, Math.max(0, -1) → 0
|
|
63
|
+
assert.equal(normalizeDiffRenderWidth(-0.5), 0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("normalizeDiffRenderWidth returns positive integers as-is", () => {
|
|
67
|
+
assert.equal(normalizeDiffRenderWidth(1), 1);
|
|
68
|
+
assert.equal(normalizeDiffRenderWidth(80), 80);
|
|
69
|
+
assert.equal(normalizeDiffRenderWidth(999), 999);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ─── resolveDiffPresentationMode all branches ──────────────────────────────
|
|
73
|
+
|
|
74
|
+
test("resolveDiffPresentationMode returns summary when width < 8", () => {
|
|
75
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 80 };
|
|
76
|
+
assert.equal(resolveDiffPresentationMode(config, 0, true), "summary");
|
|
77
|
+
assert.equal(resolveDiffPresentationMode(config, 7, true), "summary");
|
|
78
|
+
assert.equal(resolveDiffPresentationMode(config, -1, true), "summary");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("resolveDiffPresentationMode returns compact when 8 <= width < 18", () => {
|
|
82
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 80 };
|
|
83
|
+
assert.equal(resolveDiffPresentationMode(config, 8, true), "compact");
|
|
84
|
+
assert.equal(resolveDiffPresentationMode(config, 12, true), "compact");
|
|
85
|
+
assert.equal(resolveDiffPresentationMode(config, 17, true), "compact");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("resolveDiffPresentationMode returns unified when mode is unified", () => {
|
|
89
|
+
const config = { diffViewMode: "unified" as const, diffSplitMinWidth: 80 };
|
|
90
|
+
assert.equal(resolveDiffPresentationMode(config, 80, true), "unified");
|
|
91
|
+
assert.equal(resolveDiffPresentationMode(config, 200, true), "unified");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("resolveDiffPresentationMode split falls back to unified when canRenderSplitLayout is false", () => {
|
|
95
|
+
const config = { diffViewMode: "split" as const, diffSplitMinWidth: 80 };
|
|
96
|
+
assert.equal(resolveDiffPresentationMode(config, 80, false), "unified");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("resolveDiffPresentationMode returns split when mode is split and canRenderSplitLayout", () => {
|
|
100
|
+
const config = { diffViewMode: "split" as const, diffSplitMinWidth: 80 };
|
|
101
|
+
assert.equal(resolveDiffPresentationMode(config, 80, true), "split");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("resolveDiffPresentationMode auto mode returns split when width >= diffSplitMinWidth and canRenderSplitLayout", () => {
|
|
105
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 100 };
|
|
106
|
+
assert.equal(resolveDiffPresentationMode(config, 100, true), "split");
|
|
107
|
+
assert.equal(resolveDiffPresentationMode(config, 120, true), "split");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("resolveDiffPresentationMode auto mode returns unified when width >= 18 but < diffSplitMinWidth", () => {
|
|
111
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 120 };
|
|
112
|
+
assert.equal(resolveDiffPresentationMode(config, 80, true), "unified");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("resolveDiffPresentationMode auto mode returns unified when canRenderSplitLayout is false", () => {
|
|
116
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 80 };
|
|
117
|
+
assert.equal(resolveDiffPresentationMode(config, 80, false), "unified");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("resolveDiffPresentationMode auto mode returns unified when width >= diffSplitMinWidth but canRenderSplitLayout is false", () => {
|
|
121
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 80 };
|
|
122
|
+
assert.equal(resolveDiffPresentationMode(config, 120, false), "unified");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("resolveDiffPresentationMode handles NaN width safely", () => {
|
|
126
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 80 };
|
|
127
|
+
assert.equal(resolveDiffPresentationMode(config, NaN, true), "summary");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("resolveDiffPresentationMode handles Infinity width safely", () => {
|
|
131
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 80 };
|
|
132
|
+
assert.equal(resolveDiffPresentationMode(config, Infinity, true), "summary");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ─── Empty diff ────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
test("renderEditDiffResult handles entirely empty diff string", () => {
|
|
138
|
+
const component = renderEditDiffResult(
|
|
139
|
+
{ diff: "" },
|
|
140
|
+
{ expanded: true },
|
|
141
|
+
{ diffViewMode: "auto", diffSplitMinWidth: 80, diffCollapsedLines: 24, diffWordWrap: false } as any,
|
|
142
|
+
passThroughTheme,
|
|
143
|
+
"",
|
|
144
|
+
);
|
|
145
|
+
const lines = renderInsideToolBox(component, 80);
|
|
146
|
+
assert.ok(lines.length > 0);
|
|
147
|
+
assert.ok(lines.some((line) => line.includes("no diff payload")));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("renderEditDiffResult handles diff with only whitespace", () => {
|
|
151
|
+
const component = renderEditDiffResult(
|
|
152
|
+
{ diff: " \n \n" },
|
|
153
|
+
{ expanded: true },
|
|
154
|
+
defaultConfig as any,
|
|
155
|
+
passThroughTheme,
|
|
156
|
+
"",
|
|
157
|
+
);
|
|
158
|
+
const lines = renderInsideToolBox(component, 80);
|
|
159
|
+
// Whitespace-only diff is treated as empty → shows no diff payload
|
|
160
|
+
assert.ok(lines.some((line) => line.includes("no diff")));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("renderEditDiffResult handles diff with no actual changes (only file headers)", () => {
|
|
164
|
+
const component = renderEditDiffResult(
|
|
165
|
+
{
|
|
166
|
+
diff: [
|
|
167
|
+
"diff --git a/file.txt b/file.txt",
|
|
168
|
+
"index abc123..def456 100644",
|
|
169
|
+
"--- a/file.txt",
|
|
170
|
+
"+++ b/file.txt",
|
|
171
|
+
].join("\n"),
|
|
172
|
+
},
|
|
173
|
+
{ expanded: true },
|
|
174
|
+
defaultConfig as any,
|
|
175
|
+
passThroughTheme,
|
|
176
|
+
"",
|
|
177
|
+
);
|
|
178
|
+
const lines = renderInsideToolBox(component, 80);
|
|
179
|
+
// Should render with 0 added/0 removed
|
|
180
|
+
assert.ok(lines.length > 0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("renderEditDiffResult handles empty diff with fallback text", () => {
|
|
184
|
+
const component = renderEditDiffResult(
|
|
185
|
+
{ diff: "" },
|
|
186
|
+
{ expanded: true },
|
|
187
|
+
defaultConfig as any,
|
|
188
|
+
passThroughTheme,
|
|
189
|
+
"↳ file updated",
|
|
190
|
+
);
|
|
191
|
+
const lines = renderInsideToolBox(component, 80);
|
|
192
|
+
assert.ok(lines.some((line) => line.includes("file updated")));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ─── Malformed diff input ──────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
test("renderEditDiffResult handles undefined diff in details", () => {
|
|
198
|
+
const component = renderEditDiffResult(
|
|
199
|
+
{},
|
|
200
|
+
{ expanded: true },
|
|
201
|
+
defaultConfig as any,
|
|
202
|
+
passThroughTheme,
|
|
203
|
+
"",
|
|
204
|
+
);
|
|
205
|
+
const lines = renderInsideToolBox(component, 80);
|
|
206
|
+
assert.ok(lines.length > 0);
|
|
207
|
+
assert.ok(lines.some((line) => line.includes("no diff payload")));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("renderEditDiffResult handles null details", () => {
|
|
211
|
+
const component = renderEditDiffResult(
|
|
212
|
+
null,
|
|
213
|
+
{ expanded: true },
|
|
214
|
+
defaultConfig as any,
|
|
215
|
+
passThroughTheme,
|
|
216
|
+
"",
|
|
217
|
+
);
|
|
218
|
+
const lines = renderInsideToolBox(component, 80);
|
|
219
|
+
assert.ok(lines.length > 0);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("renderEditDiffResult handles non-object details", () => {
|
|
223
|
+
// string instead of object
|
|
224
|
+
const component = renderEditDiffResult(
|
|
225
|
+
"not an object" as any,
|
|
226
|
+
{ expanded: true },
|
|
227
|
+
defaultConfig as any,
|
|
228
|
+
passThroughTheme,
|
|
229
|
+
"",
|
|
230
|
+
);
|
|
231
|
+
const lines = renderInsideToolBox(component, 80);
|
|
232
|
+
assert.ok(lines.length > 0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("renderEditDiffResult handles array as details", () => {
|
|
236
|
+
const component = renderEditDiffResult(
|
|
237
|
+
[] as any,
|
|
238
|
+
{ expanded: true },
|
|
239
|
+
defaultConfig as any,
|
|
240
|
+
passThroughTheme,
|
|
241
|
+
"",
|
|
242
|
+
);
|
|
243
|
+
const lines = renderInsideToolBox(component, 80);
|
|
244
|
+
assert.ok(lines.length > 0);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("renderEditDiffResult handles diff property with non-string value", () => {
|
|
248
|
+
const component = renderEditDiffResult(
|
|
249
|
+
{ diff: 42 },
|
|
250
|
+
{ expanded: true },
|
|
251
|
+
defaultConfig as any,
|
|
252
|
+
passThroughTheme,
|
|
253
|
+
"",
|
|
254
|
+
);
|
|
255
|
+
const lines = renderInsideToolBox(component, 80);
|
|
256
|
+
// safeGetDiff would return "" for non-string diff
|
|
257
|
+
assert.ok(lines.some((line) => line.includes("no diff payload")));
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("renderEditDiffResult handles git binary diff patch gracefully", () => {
|
|
261
|
+
const binaryDiff = [
|
|
262
|
+
"diff --git a/image.png b/image.png",
|
|
263
|
+
"index abc..def 100644",
|
|
264
|
+
"Binary files a/image.png and b/image.png differ",
|
|
265
|
+
].join("\n");
|
|
266
|
+
const component = renderEditDiffResult(
|
|
267
|
+
{ diff: binaryDiff },
|
|
268
|
+
{ expanded: true, filePath: "image.png" },
|
|
269
|
+
defaultConfig as any,
|
|
270
|
+
passThroughTheme,
|
|
271
|
+
"",
|
|
272
|
+
);
|
|
273
|
+
const lines = renderInsideToolBox(component, 80);
|
|
274
|
+
// Should render without crashing (binary diff lines are treated as meta/context)
|
|
275
|
+
assert.ok(lines.length > 0);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ─── Diff with unicode, BOM, CRLF ──────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
test("renderEditDiffResult renders diff with unicode characters", () => {
|
|
281
|
+
const diff = [
|
|
282
|
+
"--- a/unicode.txt",
|
|
283
|
+
"+++ b/unicode.txt",
|
|
284
|
+
"@@ -1 +1 @@",
|
|
285
|
+
"-hello",
|
|
286
|
+
"+héllo wörld 你好 🎉",
|
|
287
|
+
].join("\n");
|
|
288
|
+
const component = renderEditDiffResult(
|
|
289
|
+
{ diff },
|
|
290
|
+
{ expanded: true, filePath: "unicode.txt" },
|
|
291
|
+
defaultConfig as any,
|
|
292
|
+
passThroughTheme,
|
|
293
|
+
"",
|
|
294
|
+
);
|
|
295
|
+
const lines = renderInsideToolBox(component, 80);
|
|
296
|
+
assert.ok(lines.length > 0);
|
|
297
|
+
const joined = lines.join("");
|
|
298
|
+
assert.ok(joined.includes("héllo") || joined.includes("hello"));
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("renderEditDiffResult renders diff with BOM prefix gracefully", () => {
|
|
302
|
+
const diff = "\uFEFF--- a/bom.txt\n+++ b/bom.txt\n@@ -1 +1 @@\n-old\n+new\n";
|
|
303
|
+
const component = renderEditDiffResult(
|
|
304
|
+
{ diff },
|
|
305
|
+
{ expanded: true, filePath: "bom.txt" },
|
|
306
|
+
defaultConfig as any,
|
|
307
|
+
passThroughTheme,
|
|
308
|
+
"",
|
|
309
|
+
);
|
|
310
|
+
const lines = renderInsideToolBox(component, 80);
|
|
311
|
+
// BOM is data before diff markers; parseDiff should handle it or not crash
|
|
312
|
+
assert.ok(lines.length > 0);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("renderEditDiffResult handles CRLF line endings in diff", () => {
|
|
316
|
+
const diff = "--- a/crlf.txt\r\n+++ b/crlf.txt\r\n@@ -1 +1 @@\r\n-old line\r\n+new line\r\n";
|
|
317
|
+
const component = renderEditDiffResult(
|
|
318
|
+
{ diff },
|
|
319
|
+
{ expanded: true, filePath: "crlf.txt" },
|
|
320
|
+
defaultConfig as any,
|
|
321
|
+
passThroughTheme,
|
|
322
|
+
"",
|
|
323
|
+
);
|
|
324
|
+
const lines = renderInsideToolBox(component, 80);
|
|
325
|
+
assert.ok(lines.length > 0);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("renderEditDiffResult renders diff with mixed indentation (tabs, spaces)", () => {
|
|
329
|
+
const diff = [
|
|
330
|
+
"--- a/file.txt",
|
|
331
|
+
"+++ b/file.txt",
|
|
332
|
+
"@@ -1,3 +1,3 @@",
|
|
333
|
+
"- indented",
|
|
334
|
+
"- tabbed",
|
|
335
|
+
"+\t\t changed",
|
|
336
|
+
"+\t\t path",
|
|
337
|
+
"",
|
|
338
|
+
].join("\n");
|
|
339
|
+
const component = renderEditDiffResult(
|
|
340
|
+
{ diff },
|
|
341
|
+
{ expanded: true, filePath: "file.txt" },
|
|
342
|
+
defaultConfig as any,
|
|
343
|
+
passThroughTheme,
|
|
344
|
+
"",
|
|
345
|
+
);
|
|
346
|
+
const lines = renderInsideToolBox(component, 80);
|
|
347
|
+
assert.ok(lines.length > 0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ─── Split diff when canRenderSplitLayout is false ─────────────────────────
|
|
351
|
+
|
|
352
|
+
test("renderEditDiffResult falls back to unified when split layout cannot render", () => {
|
|
353
|
+
// Width 40 is below 51 (2*24 + 3), so canRenderSplitLayout returns false.
|
|
354
|
+
// resolveDiffPresentationMode with diffViewMode="split" returns "unified"
|
|
355
|
+
const diff = [
|
|
356
|
+
"--- a/demo.ts",
|
|
357
|
+
"+++ b/demo.ts",
|
|
358
|
+
"@@ -1,3 +1,4 @@",
|
|
359
|
+
" const a = 1;",
|
|
360
|
+
"-const b = 2;",
|
|
361
|
+
"+const b = 42;",
|
|
362
|
+
" const c = 3;",
|
|
363
|
+
"+const d = 4;",
|
|
364
|
+
].join("\n");
|
|
365
|
+
const component = renderEditDiffResult(
|
|
366
|
+
{ diff },
|
|
367
|
+
{ expanded: true, filePath: "demo.ts" },
|
|
368
|
+
{
|
|
369
|
+
...defaultConfig,
|
|
370
|
+
diffViewMode: "split",
|
|
371
|
+
} as any,
|
|
372
|
+
passThroughTheme,
|
|
373
|
+
"",
|
|
374
|
+
);
|
|
375
|
+
const lines = renderInsideToolBox(component, 40);
|
|
376
|
+
// Should render without crash, showing unified view
|
|
377
|
+
assert.ok(lines.length > 0);
|
|
378
|
+
// In split mode top border shows "─┬─"; unified doesn't use that
|
|
379
|
+
const joined = lines.join("");
|
|
380
|
+
assert.ok(
|
|
381
|
+
!joined.includes("─┬─"),
|
|
382
|
+
`Split top border should not appear in unified fallback: ${joined}`,
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("renderEditDiffResult narrow width auto mode falls back from split to unified", () => {
|
|
387
|
+
const diff = [
|
|
388
|
+
"--- a/a.ts",
|
|
389
|
+
"+++ b/a.ts",
|
|
390
|
+
"@@ -1 +1 @@",
|
|
391
|
+
"-old",
|
|
392
|
+
"+new",
|
|
393
|
+
].join("\n");
|
|
394
|
+
const component = renderEditDiffResult(
|
|
395
|
+
{ diff },
|
|
396
|
+
{ expanded: true, filePath: "a.ts" },
|
|
397
|
+
{
|
|
398
|
+
diffViewMode: "auto",
|
|
399
|
+
diffSplitMinWidth: 200, // Requires 200+ width for split
|
|
400
|
+
diffCollapsedLines: 24,
|
|
401
|
+
diffWordWrap: false,
|
|
402
|
+
} as any,
|
|
403
|
+
passThroughTheme,
|
|
404
|
+
"",
|
|
405
|
+
);
|
|
406
|
+
const lines = renderInsideToolBox(component, 80);
|
|
407
|
+
assert.ok(lines.length > 0);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// ─── Syntax highlighting failure ───────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
test("renderEditDiffResult handles unknown language gracefully", () => {
|
|
413
|
+
const diff = [
|
|
414
|
+
"--- a/file.unknown_extension_xyz",
|
|
415
|
+
"+++ b/file.unknown_extension_xyz",
|
|
416
|
+
"@@ -1 +1 @@",
|
|
417
|
+
"-print('hello');",
|
|
418
|
+
"+print('world');",
|
|
419
|
+
].join("\n");
|
|
420
|
+
const component = renderEditDiffResult(
|
|
421
|
+
{ diff },
|
|
422
|
+
{ expanded: true, filePath: "file.unknown_extension_xyz" },
|
|
423
|
+
defaultConfig as any,
|
|
424
|
+
passThroughTheme,
|
|
425
|
+
"",
|
|
426
|
+
);
|
|
427
|
+
const lines = renderInsideToolBox(component, 80);
|
|
428
|
+
assert.ok(lines.length > 0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("renderEditDiffResult handles null filePath (no highlighting attempted)", () => {
|
|
432
|
+
const diff = [
|
|
433
|
+
"--- a/f.txt",
|
|
434
|
+
"+++ b/f.txt",
|
|
435
|
+
"@@ -1 +1 @@",
|
|
436
|
+
"-old content",
|
|
437
|
+
"+new content",
|
|
438
|
+
].join("\n");
|
|
439
|
+
const component = renderEditDiffResult(
|
|
440
|
+
{ diff },
|
|
441
|
+
{ expanded: true, filePath: undefined },
|
|
442
|
+
defaultConfig as any,
|
|
443
|
+
passThroughTheme,
|
|
444
|
+
"",
|
|
445
|
+
);
|
|
446
|
+
const lines = renderInsideToolBox(component, 80);
|
|
447
|
+
assert.ok(lines.length > 0);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("renderEditDiffResult handles empty filePath string", () => {
|
|
451
|
+
const diff = [
|
|
452
|
+
"--- a/f.txt",
|
|
453
|
+
"+++ b/f.txt",
|
|
454
|
+
"@@ -1 +1 @@",
|
|
455
|
+
"-a",
|
|
456
|
+
"+b",
|
|
457
|
+
].join("\n");
|
|
458
|
+
const component = renderEditDiffResult(
|
|
459
|
+
{ diff },
|
|
460
|
+
{ expanded: true, filePath: "" },
|
|
461
|
+
defaultConfig as any,
|
|
462
|
+
passThroughTheme,
|
|
463
|
+
"",
|
|
464
|
+
);
|
|
465
|
+
const lines = renderInsideToolBox(component, 80);
|
|
466
|
+
assert.ok(lines.length > 0);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// ─── Large overwrite guard behavior in write diffs ──────────────────────────
|
|
470
|
+
|
|
471
|
+
test("renderWriteDiffResult triggers overwrite guard when previousLineCount * nextLineCount exceeds matrix limit", () => {
|
|
472
|
+
// Matrix limit is 1,000,000 cells. Using 1001 previous lines × 1000 next lines = 1,001,000 > 1M.
|
|
473
|
+
const previousLines = Array.from({ length: 1001 }, (_, i) => `previous line ${i}`);
|
|
474
|
+
const nextLines = Array.from({ length: 1000 }, (_, i) => `next line ${i}`);
|
|
475
|
+
const previousContent = previousLines.join("\n");
|
|
476
|
+
const content = nextLines.join("\n");
|
|
477
|
+
|
|
478
|
+
const component = renderWriteDiffResult(
|
|
479
|
+
content,
|
|
480
|
+
{
|
|
481
|
+
expanded: true,
|
|
482
|
+
filePath: "large.txt",
|
|
483
|
+
previousContent,
|
|
484
|
+
fileExistedBeforeWrite: true,
|
|
485
|
+
},
|
|
486
|
+
defaultConfig as any,
|
|
487
|
+
passThroughTheme,
|
|
488
|
+
"",
|
|
489
|
+
);
|
|
490
|
+
const lines = renderInsideToolBox(component, 80);
|
|
491
|
+
assert.ok(lines.length > 0);
|
|
492
|
+
const joined = lines.join("");
|
|
493
|
+
assert.ok(
|
|
494
|
+
joined.includes("overwrite diff omitted") || joined.includes("large"),
|
|
495
|
+
`Expected overwrite guard message in output: ${joined}`,
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("renderWriteDiffResult triggers guard when previous lines exceed MAX_WRITE_OVERWRITE_LINES", () => {
|
|
500
|
+
// MAX_WRITE_OVERWRITE_DIFF_LINES = 4000
|
|
501
|
+
const previousLines = Array.from({ length: 4001 }, (_, i) => `line ${i}`);
|
|
502
|
+
const nextLines = ["single line"];
|
|
503
|
+
const previousContent = previousLines.join("\n");
|
|
504
|
+
|
|
505
|
+
const component = renderWriteDiffResult(
|
|
506
|
+
"single line",
|
|
507
|
+
{
|
|
508
|
+
expanded: true,
|
|
509
|
+
filePath: "big.txt",
|
|
510
|
+
previousContent,
|
|
511
|
+
fileExistedBeforeWrite: true,
|
|
512
|
+
},
|
|
513
|
+
defaultConfig as any,
|
|
514
|
+
passThroughTheme,
|
|
515
|
+
"",
|
|
516
|
+
);
|
|
517
|
+
const lines = renderInsideToolBox(component, 80);
|
|
518
|
+
assert.ok(lines.length > 0);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test("renderWriteDiffResult does not trigger guard for small diffs", () => {
|
|
522
|
+
const previousContent = "line1\nline2\nline3";
|
|
523
|
+
const component = renderWriteDiffResult(
|
|
524
|
+
"line1\nline2\nline3\nline4",
|
|
525
|
+
{
|
|
526
|
+
expanded: true,
|
|
527
|
+
filePath: "small.txt",
|
|
528
|
+
previousContent,
|
|
529
|
+
fileExistedBeforeWrite: true,
|
|
530
|
+
},
|
|
531
|
+
defaultConfig as any,
|
|
532
|
+
passThroughTheme,
|
|
533
|
+
"",
|
|
534
|
+
);
|
|
535
|
+
const lines = renderInsideToolBox(component, 80);
|
|
536
|
+
assert.ok(lines.length > 0);
|
|
537
|
+
const joined = lines.join("");
|
|
538
|
+
assert.ok(
|
|
539
|
+
!joined.includes("overwrite diff omitted"),
|
|
540
|
+
`Small diff should not trigger overwrite guard: ${joined}`,
|
|
541
|
+
);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("renderWriteDiffResult does not trigger guard for new file (not overwrite)", () => {
|
|
545
|
+
// fileExistedBeforeWrite is false, so guard is skipped
|
|
546
|
+
const component = renderWriteDiffResult(
|
|
547
|
+
"hello\nworld",
|
|
548
|
+
{
|
|
549
|
+
expanded: true,
|
|
550
|
+
filePath: "new.txt",
|
|
551
|
+
previousContent: undefined,
|
|
552
|
+
fileExistedBeforeWrite: false,
|
|
553
|
+
},
|
|
554
|
+
defaultConfig as any,
|
|
555
|
+
passThroughTheme,
|
|
556
|
+
"",
|
|
557
|
+
);
|
|
558
|
+
const lines = renderInsideToolBox(component, 80);
|
|
559
|
+
assert.ok(lines.length > 0);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test("renderWriteDiffResult reports created file header", () => {
|
|
563
|
+
const component = renderWriteDiffResult(
|
|
564
|
+
"new content",
|
|
565
|
+
{
|
|
566
|
+
expanded: true,
|
|
567
|
+
filePath: "fresh.txt",
|
|
568
|
+
fileExistedBeforeWrite: false,
|
|
569
|
+
},
|
|
570
|
+
defaultConfig as any,
|
|
571
|
+
passThroughTheme,
|
|
572
|
+
"",
|
|
573
|
+
);
|
|
574
|
+
const lines = renderInsideToolBox(component, 80);
|
|
575
|
+
const joined = lines.join("");
|
|
576
|
+
assert.ok(joined.includes("created") || joined.includes("↳"));
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("renderWriteDiffResult reports overwritten file header", () => {
|
|
580
|
+
const component = renderWriteDiffResult(
|
|
581
|
+
"new",
|
|
582
|
+
{
|
|
583
|
+
expanded: true,
|
|
584
|
+
filePath: "over.txt",
|
|
585
|
+
previousContent: "old",
|
|
586
|
+
fileExistedBeforeWrite: true,
|
|
587
|
+
},
|
|
588
|
+
defaultConfig as any,
|
|
589
|
+
passThroughTheme,
|
|
590
|
+
"",
|
|
591
|
+
);
|
|
592
|
+
const lines = renderInsideToolBox(component, 80);
|
|
593
|
+
const joined = lines.join("");
|
|
594
|
+
assert.ok(joined.includes("overwritten") || joined.includes("↳"));
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// ─── renderWriteDiffResult with undefined / null content ────────────────────
|
|
598
|
+
|
|
599
|
+
test("renderWriteDiffResult handles undefined content", () => {
|
|
600
|
+
const component = renderWriteDiffResult(
|
|
601
|
+
undefined,
|
|
602
|
+
{ expanded: true, filePath: "f.txt" },
|
|
603
|
+
defaultConfig as any,
|
|
604
|
+
passThroughTheme,
|
|
605
|
+
"",
|
|
606
|
+
);
|
|
607
|
+
const lines = renderInsideToolBox(component, 80);
|
|
608
|
+
assert.ok(lines.some((l) => l.includes("write completed")));
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test("renderWriteDiffResult handles non-string content", () => {
|
|
612
|
+
const component = renderWriteDiffResult(
|
|
613
|
+
42 as any,
|
|
614
|
+
{ expanded: true, filePath: "f.txt" },
|
|
615
|
+
defaultConfig as any,
|
|
616
|
+
passThroughTheme,
|
|
617
|
+
"",
|
|
618
|
+
);
|
|
619
|
+
const lines = renderInsideToolBox(component, 80);
|
|
620
|
+
assert.ok(lines.some((l) => l.includes("write completed")));
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("renderWriteDiffResult uses fallback text when content is missing", () => {
|
|
624
|
+
const component = renderWriteDiffResult(
|
|
625
|
+
undefined,
|
|
626
|
+
{ expanded: true, filePath: "f.txt" },
|
|
627
|
+
defaultConfig as any,
|
|
628
|
+
passThroughTheme,
|
|
629
|
+
"↳ file saved successfully",
|
|
630
|
+
);
|
|
631
|
+
const lines = renderInsideToolBox(component, 80);
|
|
632
|
+
assert.ok(lines.some((l) => l.includes("saved successfully")));
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// ─── renderEditDiffResult with very narrow widths ──────────────────────────
|
|
636
|
+
|
|
637
|
+
test("renderEditDiffResult renders summary at very narrow width", () => {
|
|
638
|
+
const diff = [
|
|
639
|
+
"--- a/a.ts",
|
|
640
|
+
"+++ b/a.ts",
|
|
641
|
+
"@@ -1,2 +1,2 @@",
|
|
642
|
+
"-old1",
|
|
643
|
+
"-old2",
|
|
644
|
+
"+new1",
|
|
645
|
+
"+new2",
|
|
646
|
+
].join("\n");
|
|
647
|
+
const component = renderEditDiffResult(
|
|
648
|
+
{ diff },
|
|
649
|
+
{ expanded: true, filePath: "a.ts" },
|
|
650
|
+
defaultConfig as any,
|
|
651
|
+
passThroughTheme,
|
|
652
|
+
"",
|
|
653
|
+
);
|
|
654
|
+
const lines = renderInsideToolBox(component, 5);
|
|
655
|
+
assert.ok(lines.length > 0);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test("renderEditDiffResult renders compact mode at narrow width (8..17)", () => {
|
|
659
|
+
const diff = [
|
|
660
|
+
"--- a/a.ts",
|
|
661
|
+
"+++ b/a.ts",
|
|
662
|
+
"@@ -1 +1 @@",
|
|
663
|
+
"-old",
|
|
664
|
+
"+new",
|
|
665
|
+
].join("\n");
|
|
666
|
+
const component = renderEditDiffResult(
|
|
667
|
+
{ diff },
|
|
668
|
+
{ expanded: true, filePath: "a.ts" },
|
|
669
|
+
defaultConfig as any,
|
|
670
|
+
passThroughTheme,
|
|
671
|
+
"",
|
|
672
|
+
);
|
|
673
|
+
const lines = renderInsideToolBox(component, 12);
|
|
674
|
+
assert.ok(lines.length > 0);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// ─── write diff with empty content ─────────────────────────────────────────
|
|
678
|
+
|
|
679
|
+
test("renderWriteDiffResult handles empty string content", () => {
|
|
680
|
+
const component = renderWriteDiffResult(
|
|
681
|
+
"",
|
|
682
|
+
{ expanded: true, filePath: "empty.txt" },
|
|
683
|
+
defaultConfig as any,
|
|
684
|
+
passThroughTheme,
|
|
685
|
+
"",
|
|
686
|
+
);
|
|
687
|
+
const lines = renderInsideToolBox(component, 80);
|
|
688
|
+
assert.ok(lines.length > 0);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
test("renderWriteDiffResult handles overwrite with identical content", () => {
|
|
692
|
+
const previousContent = "same\ncontent";
|
|
693
|
+
const component = renderWriteDiffResult(
|
|
694
|
+
"same\ncontent",
|
|
695
|
+
{
|
|
696
|
+
expanded: true,
|
|
697
|
+
filePath: "same.txt",
|
|
698
|
+
previousContent,
|
|
699
|
+
fileExistedBeforeWrite: true,
|
|
700
|
+
},
|
|
701
|
+
defaultConfig as any,
|
|
702
|
+
passThroughTheme,
|
|
703
|
+
"",
|
|
704
|
+
);
|
|
705
|
+
const lines = renderInsideToolBox(component, 80);
|
|
706
|
+
assert.ok(lines.length > 0);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// ─── Edit diff with hashline anchors (canonical format) ─────────────────────
|
|
710
|
+
|
|
711
|
+
test("renderEditDiffResult renders canonical numbered diff with hashline anchors", () => {
|
|
712
|
+
const diff = [
|
|
713
|
+
" 1|import { foo } from './foo';",
|
|
714
|
+
"- 2|const oldVar = true;",
|
|
715
|
+
"+ 2|const newVar = true;",
|
|
716
|
+
" 3|export function test() {",
|
|
717
|
+
].join("\n");
|
|
718
|
+
const component = renderEditDiffResult(
|
|
719
|
+
{ diff },
|
|
720
|
+
{ expanded: true, filePath: "demo.ts" },
|
|
721
|
+
defaultConfig as any,
|
|
722
|
+
passThroughTheme,
|
|
723
|
+
"",
|
|
724
|
+
);
|
|
725
|
+
const lines = renderInsideToolBox(component, 80);
|
|
726
|
+
assert.ok(lines.length > 0);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// ─── edit diff with only context lines ─────────────────────────────────────
|
|
730
|
+
|
|
731
|
+
test("renderEditDiffResult handles diff with only context lines", () => {
|
|
732
|
+
const diff = [
|
|
733
|
+
"--- a/ctx.txt",
|
|
734
|
+
"+++ b/ctx.txt",
|
|
735
|
+
"@@ -1,3 +1,3 @@",
|
|
736
|
+
" unchanged1",
|
|
737
|
+
" unchanged2",
|
|
738
|
+
" unchanged3",
|
|
739
|
+
].join("\n");
|
|
740
|
+
const component = renderEditDiffResult(
|
|
741
|
+
{ diff },
|
|
742
|
+
{ expanded: true, filePath: "ctx.txt" },
|
|
743
|
+
defaultConfig as any,
|
|
744
|
+
passThroughTheme,
|
|
745
|
+
"",
|
|
746
|
+
);
|
|
747
|
+
const lines = renderInsideToolBox(component, 80);
|
|
748
|
+
assert.ok(lines.length > 0);
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// ─── resolveDiffPresentationMode with explicit diffSplitMinWidth extremes ───
|
|
752
|
+
|
|
753
|
+
test("resolveDiffPresentationMode honors large diffSplitMinWidth for auto mode", () => {
|
|
754
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 9999 };
|
|
755
|
+
// Width 1000 < 9999, so returns "unified"
|
|
756
|
+
assert.equal(resolveDiffPresentationMode(config, 1000, true), "unified");
|
|
757
|
+
// Width 10000 >= 9999, returns "split"
|
|
758
|
+
assert.equal(resolveDiffPresentationMode(config, 10000, true), "split");
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test("resolveDiffPresentationMode honors zero diffSplitMinWidth", () => {
|
|
762
|
+
// With diffSplitMinWidth = 0, auto mode always qualifies for split if canRenderSplitLayout
|
|
763
|
+
const config = { diffViewMode: "auto" as const, diffSplitMinWidth: 0 };
|
|
764
|
+
assert.equal(resolveDiffPresentationMode(config, 51, true), "split");
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// ─── buildDiffSummaryText edge cases ───────────────────────────────────────
|
|
768
|
+
|
|
769
|
+
test("buildDiffSummaryText returns empty string for zero width", () => {
|
|
770
|
+
const stats = { added: 5, removed: 3, hunks: 2, files: 1 };
|
|
771
|
+
assert.equal(buildDiffSummaryText(stats, 0), "");
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("buildDiffSummaryText returns empty string for negative width", () => {
|
|
775
|
+
const stats = { added: 5, removed: 3, hunks: 2, files: 1 };
|
|
776
|
+
assert.equal(buildDiffSummaryText(stats, -1), "");
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test("buildDiffSummaryText returns minimal text for very narrow width", () => {
|
|
780
|
+
const stats = { added: 10, removed: 5, hunks: 3, files: 2 };
|
|
781
|
+
const result = buildDiffSummaryText(stats, 1);
|
|
782
|
+
assert.equal(result, "…");
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
test("buildDiffSummaryText returns progressively shorter candidates for limited width", () => {
|
|
786
|
+
const stats = { added: 100, removed: 50, hunks: 5, files: 3 };
|
|
787
|
+
// Narrow but not too narrow
|
|
788
|
+
const narrow = buildDiffSummaryText(stats, 10);
|
|
789
|
+
assert.ok(typeof narrow === "string");
|
|
790
|
+
assert.ok(narrow.length <= 10);
|
|
791
|
+
|
|
792
|
+
const medium = buildDiffSummaryText(stats, 30);
|
|
793
|
+
assert.ok(medium.length > 0);
|
|
794
|
+
|
|
795
|
+
const wide = buildDiffSummaryText(stats, 120);
|
|
796
|
+
assert.ok(wide.startsWith("↳ diff"));
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
test("buildDiffSummaryText handles zero stats", () => {
|
|
800
|
+
const stats = { added: 0, removed: 0, hunks: 0, files: 0 };
|
|
801
|
+
const result = buildDiffSummaryText(stats, 80);
|
|
802
|
+
// With zero stats, the first candidate "↳ diff +0 -0 • 0 hunks • 0 files" fits
|
|
803
|
+
assert.ok(result.includes("+0"));
|
|
804
|
+
assert.ok(result.includes("-0"));
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// ─── renderWriteDiffResult with custom header label ────────────────────────
|
|
808
|
+
|
|
809
|
+
test("renderWriteDiffResult uses custom header label", () => {
|
|
810
|
+
const component = renderWriteDiffResult(
|
|
811
|
+
"content",
|
|
812
|
+
{
|
|
813
|
+
expanded: true,
|
|
814
|
+
filePath: "custom.txt",
|
|
815
|
+
headerLabel: "custom-label",
|
|
816
|
+
fileExistedBeforeWrite: false,
|
|
817
|
+
},
|
|
818
|
+
defaultConfig as any,
|
|
819
|
+
passThroughTheme,
|
|
820
|
+
"",
|
|
821
|
+
);
|
|
822
|
+
const lines = renderInsideToolBox(component, 80);
|
|
823
|
+
const joined = lines.join("");
|
|
824
|
+
assert.ok(joined.includes("custom-label"), `Expected custom label in output: ${joined}`);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
test("renderWriteDiffResult falls back to 'created' when no header label for new file", () => {
|
|
828
|
+
const component = renderWriteDiffResult(
|
|
829
|
+
"content",
|
|
830
|
+
{
|
|
831
|
+
expanded: true,
|
|
832
|
+
filePath: "nothdr.txt",
|
|
833
|
+
fileExistedBeforeWrite: false,
|
|
834
|
+
},
|
|
835
|
+
defaultConfig as any,
|
|
836
|
+
passThroughTheme,
|
|
837
|
+
"",
|
|
838
|
+
);
|
|
839
|
+
const lines = renderInsideToolBox(component, 80);
|
|
840
|
+
const joined = lines.join("");
|
|
841
|
+
assert.ok(joined.includes("created") || joined.includes("↳"));
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// ─── renderEditDiffResult with summary mode at width 0 ────────────────────
|
|
845
|
+
|
|
846
|
+
test("renderEditDiffResult handles width 0 without crashing", () => {
|
|
847
|
+
const diff = [
|
|
848
|
+
"--- a/a.ts",
|
|
849
|
+
"+++ b/a.ts",
|
|
850
|
+
"@@ -1 +1 @@",
|
|
851
|
+
"-old",
|
|
852
|
+
"+new",
|
|
853
|
+
].join("\n");
|
|
854
|
+
const component = renderEditDiffResult(
|
|
855
|
+
{ diff },
|
|
856
|
+
{ expanded: true, filePath: "a.ts" },
|
|
857
|
+
defaultConfig as any,
|
|
858
|
+
passThroughTheme,
|
|
859
|
+
"",
|
|
860
|
+
);
|
|
861
|
+
const lines = renderInsideToolBox(component, 0);
|
|
862
|
+
assert.ok(lines.length > 0);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
test("renderEditDiffResult handles width 1 (minimum summary)", () => {
|
|
866
|
+
const diff = [
|
|
867
|
+
"--- a/a.ts",
|
|
868
|
+
"+++ b/a.ts",
|
|
869
|
+
"@@ -1,5 +1,5 @@",
|
|
870
|
+
" line1",
|
|
871
|
+
"-remove1",
|
|
872
|
+
"-remove2",
|
|
873
|
+
"+add1",
|
|
874
|
+
"+add2",
|
|
875
|
+
" line5",
|
|
876
|
+
].join("\n");
|
|
877
|
+
const component = renderEditDiffResult(
|
|
878
|
+
{ diff },
|
|
879
|
+
{ expanded: true, filePath: "a.ts" },
|
|
880
|
+
defaultConfig as any,
|
|
881
|
+
passThroughTheme,
|
|
882
|
+
"",
|
|
883
|
+
);
|
|
884
|
+
const lines = renderInsideToolBox(component, 1);
|
|
885
|
+
assert.ok(lines.length > 0);
|
|
886
|
+
});
|