@heyhuynhgiabuu/pi-pretty 0.4.2 → 0.4.4
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/package.json +1 -1
- package/release-notes/v0.4.4.md +28 -0
- package/src/index.ts +17 -110
- package/test/bash-rendering.test.ts +58 -0
- package/test/fff-integration.test.ts +29 -1
- package/test/image-rendering.test.ts +7 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyhuynhgiabuu/pi-pretty",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Pretty terminal output for pi — syntax-highlighted file reads, colored bash output, tree-view directory listings, and more.",
|
|
5
5
|
"author": "huynhgiabuu",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# pi-pretty 0.4.4
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
This patch release fixes terminal background tearing/misaligned cells by enforcing the pi-tui line-width contract for background-filled tool output.
|
|
5
|
+
|
|
6
|
+
## What changed
|
|
7
|
+
- Clamp every background-filled tool line to the active terminal width before adding background padding.
|
|
8
|
+
- Use `@mariozechner/pi-tui` `truncateToWidth()` and `visibleWidth()` so ANSI escapes and wide Unicode cells are measured correctly.
|
|
9
|
+
- Preserve the configured tool background after ANSI reset sequences even when truncation occurs.
|
|
10
|
+
- Remove the 80-column minimum width floor so narrow terminals do not receive over-wide rendered lines.
|
|
11
|
+
- Replace custom read-output truncation with pi-tui's ANSI-aware truncation helper.
|
|
12
|
+
- Add regression coverage for expanded ANSI tool headers, wide characters, and narrow terminal widths.
|
|
13
|
+
|
|
14
|
+
## Files
|
|
15
|
+
- `src/index.ts`
|
|
16
|
+
- `test/bash-rendering.test.ts`
|
|
17
|
+
- `package.json`
|
|
18
|
+
- `package-lock.json`
|
|
19
|
+
|
|
20
|
+
## Verification
|
|
21
|
+
- `npm run lint` ✅
|
|
22
|
+
- `npm run typecheck` ✅
|
|
23
|
+
- `npm test` ✅ (69 tests)
|
|
24
|
+
|
|
25
|
+
## Upgrade notes
|
|
26
|
+
No configuration changes are required.
|
|
27
|
+
|
|
28
|
+
This release specifically follows the pi-tui requirement that rendered component lines must not exceed the available width, preventing terminal auto-wrap artifacts in red/error and themed tool backgrounds.
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import type {
|
|
|
41
41
|
ReadToolInput,
|
|
42
42
|
ToolRenderResultOptions,
|
|
43
43
|
} from "@mariozechner/pi-coding-agent";
|
|
44
|
+
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
44
45
|
import { codeToANSI } from "@shikijs/cli";
|
|
45
46
|
import type { BundledLanguage, BundledTheme } from "shiki";
|
|
46
47
|
|
|
@@ -134,13 +135,11 @@ function getThemeBgAnsi(theme: BgTheme, key: string): string | null {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
/** Read themed tool backgrounds and update BG_BASE / BG_ERROR + RST.
|
|
137
|
-
*
|
|
138
|
-
let _bgBaseResolved = false;
|
|
138
|
+
* Recompute on each render so runtime theme changes are respected. */
|
|
139
139
|
function resolveBaseBackground(theme: BgTheme | null | undefined): void {
|
|
140
|
-
if (
|
|
141
|
-
_bgBaseResolved = true;
|
|
140
|
+
if (!theme?.getBgAnsi) return;
|
|
142
141
|
|
|
143
|
-
BG_BASE = getThemeBgAnsi(theme, "
|
|
142
|
+
BG_BASE = getThemeBgAnsi(theme, "toolBg") ?? getThemeBgAnsi(theme, "background") ?? BG_DEFAULT;
|
|
144
143
|
BG_ERROR = getThemeBgAnsi(theme, "toolErrorBg") ?? BG_BASE;
|
|
145
144
|
RST = `\x1b[0m${BG_BASE}`;
|
|
146
145
|
}
|
|
@@ -150,7 +149,6 @@ function renderToolError(error: string, theme: FgTheme): string {
|
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
const ESC_RE = "\u001b";
|
|
153
|
-
const ANSI_RE = new RegExp(`${ESC_RE}\\[[0-9;]*m`, "g");
|
|
154
152
|
const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([0-9;]*)m`, "g");
|
|
155
153
|
|
|
156
154
|
// ---------------------------------------------------------------------------
|
|
@@ -176,10 +174,6 @@ function normalizeShikiContrast(ansi: string): string {
|
|
|
176
174
|
// Utilities
|
|
177
175
|
// ---------------------------------------------------------------------------
|
|
178
176
|
|
|
179
|
-
function strip(s: string): string {
|
|
180
|
-
return s.replace(ANSI_RE, "");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
177
|
function normalizeLineEndings(text: string): string {
|
|
184
178
|
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
185
179
|
}
|
|
@@ -197,8 +191,9 @@ function fillToolBackground(text: string, bg = BG_BASE): string {
|
|
|
197
191
|
.split("\n")
|
|
198
192
|
.map((line) => {
|
|
199
193
|
const normalized = preserveToolBackground(line, bg);
|
|
200
|
-
const
|
|
201
|
-
|
|
194
|
+
const fitted = preserveToolBackground(truncateToWidth(normalized, width, ""), bg);
|
|
195
|
+
const padding = Math.max(0, width - visibleWidth(fitted));
|
|
196
|
+
return `${bg}${fitted}${" ".repeat(padding)}${RST}`;
|
|
202
197
|
})
|
|
203
198
|
.join("\n");
|
|
204
199
|
}
|
|
@@ -207,7 +202,7 @@ function termW(): number {
|
|
|
207
202
|
const stderrWithColumns = process.stderr as NodeJS.WriteStream & { columns?: number };
|
|
208
203
|
const raw =
|
|
209
204
|
process.stdout.columns || stderrWithColumns.columns || Number.parseInt(process.env.COLUMNS ?? "", 10) || 200;
|
|
210
|
-
return Math.max(
|
|
205
|
+
return Math.max(1, Math.min(raw - 4, 210));
|
|
211
206
|
}
|
|
212
207
|
|
|
213
208
|
function shortPath(cwd: string, home: string, p: string): string {
|
|
@@ -440,47 +435,6 @@ export const __imageInternals = {
|
|
|
440
435
|
},
|
|
441
436
|
};
|
|
442
437
|
|
|
443
|
-
/**
|
|
444
|
-
* Render base64 image inline using iTerm2 inline image protocol.
|
|
445
|
-
* Protocol: ESC ] 1337 ; File=[args] : base64data BEL
|
|
446
|
-
*/
|
|
447
|
-
function renderIterm2Image(base64Data: string, opts: { width?: string; name?: string } = {}): string {
|
|
448
|
-
const args: string[] = ["inline=1", "preserveAspectRatio=1"];
|
|
449
|
-
if (opts.width) args.push(`width=${opts.width}`);
|
|
450
|
-
if (opts.name) args.push(`name=${Buffer.from(opts.name).toString("base64")}`);
|
|
451
|
-
const byteSize = Math.ceil((base64Data.length * 3) / 4);
|
|
452
|
-
args.push(`size=${byteSize}`);
|
|
453
|
-
const seq = `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
|
|
454
|
-
return tmuxWrap(seq);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Render base64 image inline using Kitty graphics protocol.
|
|
459
|
-
* Protocol: ESC _G <key>=<value>,...; <base64data> ESC \
|
|
460
|
-
* Chunked in 4096-byte pieces as required by protocol.
|
|
461
|
-
* Supported by: Kitty, Ghostty
|
|
462
|
-
*/
|
|
463
|
-
function renderKittyImage(base64Data: string, opts: { cols?: number } = {}): string {
|
|
464
|
-
const chunks: string[] = [];
|
|
465
|
-
const CHUNK_SIZE = 4096;
|
|
466
|
-
|
|
467
|
-
for (let i = 0; i < base64Data.length; i += CHUNK_SIZE) {
|
|
468
|
-
const chunk = base64Data.slice(i, i + CHUNK_SIZE);
|
|
469
|
-
const isFirst = i === 0;
|
|
470
|
-
const isLast = i + CHUNK_SIZE >= base64Data.length;
|
|
471
|
-
const more = isLast ? 0 : 1;
|
|
472
|
-
|
|
473
|
-
if (isFirst) {
|
|
474
|
-
const colPart = opts.cols ? `,c=${opts.cols}` : "";
|
|
475
|
-
chunks.push(tmuxWrap(`\x1b_Ga=T,f=100,t=d,m=${more}${colPart};${chunk}\x1b\\`));
|
|
476
|
-
} else {
|
|
477
|
-
chunks.push(tmuxWrap(`\x1b_Gm=${more};${chunk}\x1b\\`));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return chunks.join("");
|
|
482
|
-
}
|
|
483
|
-
|
|
484
438
|
/**
|
|
485
439
|
* Get human-readable file size
|
|
486
440
|
*/
|
|
@@ -664,7 +618,7 @@ async function renderFileContent(
|
|
|
664
618
|
const endLine = startLine + show.length - 1;
|
|
665
619
|
const nw = Math.max(3, String(endLine).length);
|
|
666
620
|
const gw = nw + 3; // num + " │ "
|
|
667
|
-
const cw = Math.max(
|
|
621
|
+
const cw = Math.max(1, tw - gw);
|
|
668
622
|
|
|
669
623
|
const out: string[] = [];
|
|
670
624
|
out.push(rule(tw));
|
|
@@ -672,25 +626,7 @@ async function renderFileContent(
|
|
|
672
626
|
for (let i = 0; i < hl.length; i++) {
|
|
673
627
|
const ln = startLine + i;
|
|
674
628
|
const code = hl[i] ?? show[i] ?? "";
|
|
675
|
-
const
|
|
676
|
-
// Truncate if wider than available
|
|
677
|
-
let display = code;
|
|
678
|
-
if (plain.length > cw) {
|
|
679
|
-
let vis = 0;
|
|
680
|
-
let j = 0;
|
|
681
|
-
while (j < code.length && vis < cw - 1) {
|
|
682
|
-
if (code[j] === "\x1b") {
|
|
683
|
-
const e = code.indexOf("m", j);
|
|
684
|
-
if (e !== -1) {
|
|
685
|
-
j = e + 1;
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
vis++;
|
|
690
|
-
j++;
|
|
691
|
-
}
|
|
692
|
-
display = `${code.slice(0, j)}${RST}${FG_DIM}›${RST}`;
|
|
693
|
-
}
|
|
629
|
+
const display = truncateToWidth(code, cw, `${FG_DIM}›`);
|
|
694
630
|
out.push(`${lnum(ln, nw)} ${FG_RULE}│${RST} ${display}${RST}`);
|
|
695
631
|
}
|
|
696
632
|
|
|
@@ -1156,8 +1092,9 @@ export default function piPrettyExtension(pi: PiPrettyApi, deps?: PiPrettyDeps):
|
|
|
1156
1092
|
if (_fffPartialIndex) {
|
|
1157
1093
|
ctx.ui?.notify?.("FFF: scan timed out — using partial index. Run /fff-rescan when ready.", "warning");
|
|
1158
1094
|
} else {
|
|
1159
|
-
ctx.ui
|
|
1160
|
-
|
|
1095
|
+
const ui = ctx.ui;
|
|
1096
|
+
ui?.setStatus?.("fff", "FFF indexed");
|
|
1097
|
+
setTimeout(() => ui?.setStatus?.("fff", undefined), 3000);
|
|
1161
1098
|
}
|
|
1162
1099
|
} catch (error: unknown) {
|
|
1163
1100
|
ctx.ui?.notify?.(`FFF init failed: ${getErrorMessage(error)}`, "error");
|
|
@@ -1242,45 +1179,15 @@ export default function piPrettyExtension(pi: PiPrettyApi, deps?: PiPrettyDeps):
|
|
|
1242
1179
|
|
|
1243
1180
|
const d = result.details as RenderDetails | undefined;
|
|
1244
1181
|
|
|
1245
|
-
// Image
|
|
1182
|
+
// Image reads keep the original image content so Pi's native TUI renderer
|
|
1183
|
+
// can display it exactly once. pi-pretty only renders metadata here;
|
|
1184
|
+
// rendering another inline image caused duplicate previews.
|
|
1246
1185
|
if (d?._type === "readImage") {
|
|
1247
|
-
const tw = termW();
|
|
1248
|
-
const out: string[] = [];
|
|
1249
|
-
const fname = basename(d.filePath);
|
|
1250
1186
|
const byteSize = Math.ceil(((d.data as string).length * 3) / 4);
|
|
1251
1187
|
const sizeStr = humanSize(byteSize);
|
|
1252
1188
|
const mimeStr = d.mimeType ?? "image";
|
|
1253
1189
|
|
|
1254
|
-
|
|
1255
|
-
out.push(rule(tw));
|
|
1256
|
-
|
|
1257
|
-
const protocol = detectImageProtocol();
|
|
1258
|
-
const passthroughWarning = getTmuxPassthroughWarning(protocol);
|
|
1259
|
-
if (passthroughWarning) {
|
|
1260
|
-
out.push(` ${FG_YELLOW}${passthroughWarning}${RST}`);
|
|
1261
|
-
} else if (protocol === "kitty") {
|
|
1262
|
-
if (d.mimeType && d.mimeType !== "image/png") {
|
|
1263
|
-
out.push(
|
|
1264
|
-
` ${FG_YELLOW}Kitty/Ghostty inline preview currently supports PNG payloads (got ${d.mimeType})${RST}`,
|
|
1265
|
-
);
|
|
1266
|
-
} else {
|
|
1267
|
-
const imgCols = Math.min(tw - 4, 80);
|
|
1268
|
-
out.push(renderKittyImage(d.data, { cols: imgCols }));
|
|
1269
|
-
}
|
|
1270
|
-
} else if (protocol === "iterm2") {
|
|
1271
|
-
const imgWidth = Math.min(tw - 4, 80);
|
|
1272
|
-
out.push(
|
|
1273
|
-
renderIterm2Image(d.data, {
|
|
1274
|
-
width: `${imgWidth}`,
|
|
1275
|
-
name: fname,
|
|
1276
|
-
}),
|
|
1277
|
-
);
|
|
1278
|
-
} else {
|
|
1279
|
-
out.push(` ${FG_DIM}(Inline image preview requires Ghostty, iTerm2, WezTerm, or Kitty)${RST}`);
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
out.push(rule(tw));
|
|
1283
|
-
text.setText(fillToolBackground(out.join("\n")));
|
|
1190
|
+
text.setText(fillToolBackground(` ${fileIcon(d.filePath)}${FG_DIM}${mimeStr} · ${sizeStr}${RST}`));
|
|
1284
1191
|
return text;
|
|
1285
1192
|
}
|
|
1286
1193
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { visibleWidth } from "@mariozechner/pi-tui";
|
|
1
2
|
import { describe, expect, it } from "vitest";
|
|
2
3
|
|
|
3
4
|
import piPrettyExtension from "../src/index.js";
|
|
@@ -18,6 +19,11 @@ const mockTheme = {
|
|
|
18
19
|
bold: (text: string) => text,
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
const ansiMockTheme = {
|
|
23
|
+
fg: (_key: string, text: string) => `\x1b[31m${text}\x1b[0m`,
|
|
24
|
+
bold: (text: string) => `\x1b[1m${text}\x1b[22m`,
|
|
25
|
+
};
|
|
26
|
+
|
|
21
27
|
function mockToolFactory(exec: any) {
|
|
22
28
|
return (_cwd: string) => ({
|
|
23
29
|
name: "mock",
|
|
@@ -27,6 +33,20 @@ function mockToolFactory(exec: any) {
|
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
function withStdoutColumns<T>(columns: number, fn: () => T): T {
|
|
37
|
+
const descriptor = Object.getOwnPropertyDescriptor(process.stdout, "columns");
|
|
38
|
+
Object.defineProperty(process.stdout, "columns", { configurable: true, value: columns });
|
|
39
|
+
try {
|
|
40
|
+
return fn();
|
|
41
|
+
} finally {
|
|
42
|
+
if (descriptor) {
|
|
43
|
+
Object.defineProperty(process.stdout, "columns", descriptor);
|
|
44
|
+
} else {
|
|
45
|
+
delete (process.stdout as NodeJS.WriteStream & { columns?: number }).columns;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
function loadBashTool() {
|
|
31
51
|
const noopExec = async () => ({ content: [{ type: "text", text: "" }] });
|
|
32
52
|
const tools = new Map<string, any>();
|
|
@@ -106,4 +126,42 @@ describe("bash renderCall expansion", () => {
|
|
|
106
126
|
expect(collapsed.getText()).toContain("5s timeout");
|
|
107
127
|
expect(expanded.getText()).toContain("5s timeout");
|
|
108
128
|
});
|
|
129
|
+
|
|
130
|
+
it("truncates expanded ANSI tool headers to the terminal width before padding backgrounds", () => {
|
|
131
|
+
withStdoutColumns(84, () => {
|
|
132
|
+
const bashTool = loadBashTool();
|
|
133
|
+
const command = `printf '${"界".repeat(120)}'`;
|
|
134
|
+
|
|
135
|
+
const rendered = bashTool.renderCall({ command }, ansiMockTheme, {
|
|
136
|
+
lastComponent: new MockText(),
|
|
137
|
+
isError: false,
|
|
138
|
+
state: {},
|
|
139
|
+
expanded: true,
|
|
140
|
+
invalidate: () => {},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
for (const line of rendered.getText().split("\n")) {
|
|
144
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(80);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("does not exceed narrow terminal widths", () => {
|
|
150
|
+
withStdoutColumns(24, () => {
|
|
151
|
+
const bashTool = loadBashTool();
|
|
152
|
+
const command = `printf '${"x".repeat(120)}'`;
|
|
153
|
+
|
|
154
|
+
const rendered = bashTool.renderCall({ command }, ansiMockTheme, {
|
|
155
|
+
lastComponent: new MockText(),
|
|
156
|
+
isError: false,
|
|
157
|
+
state: {},
|
|
158
|
+
expanded: true,
|
|
159
|
+
invalidate: () => {},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
for (const line of rendered.getText().split("\n")) {
|
|
163
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(20);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
109
167
|
});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
14
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
15
15
|
import { CursorStore, fffFormatGrepText } from "../src/fff-helpers.js";
|
|
16
16
|
import piPrettyExtension, { type PiPrettyDeps } from "../src/index.js";
|
|
17
17
|
import {
|
|
@@ -314,6 +314,7 @@ describe("piPrettyExtension integration", () => {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
beforeEach(() => {
|
|
317
|
+
vi.useRealTimers();
|
|
317
318
|
tools = new Map();
|
|
318
319
|
events = new Map();
|
|
319
320
|
mockPi = {
|
|
@@ -336,6 +337,10 @@ describe("piPrettyExtension integration", () => {
|
|
|
336
337
|
piPrettyExtension(mockPi, deps);
|
|
337
338
|
}
|
|
338
339
|
|
|
340
|
+
afterEach(() => {
|
|
341
|
+
vi.useRealTimers();
|
|
342
|
+
});
|
|
343
|
+
|
|
339
344
|
async function loadWithFFF(finderOverrides?: Record<string, any>) {
|
|
340
345
|
load(true, finderOverrides);
|
|
341
346
|
const start = events.get("session_start")!;
|
|
@@ -714,6 +719,29 @@ describe("piPrettyExtension integration", () => {
|
|
|
714
719
|
}));
|
|
715
720
|
});
|
|
716
721
|
|
|
722
|
+
it("delayed FFF status clear does not read a stale session ctx", async () => {
|
|
723
|
+
vi.useFakeTimers();
|
|
724
|
+
const setStatus = vi.fn();
|
|
725
|
+
let stale = false;
|
|
726
|
+
const ctx = {
|
|
727
|
+
cwd: "/tmp/test",
|
|
728
|
+
get ui() {
|
|
729
|
+
if (stale) throw new Error("stale ctx");
|
|
730
|
+
return { setStatus };
|
|
731
|
+
},
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
load(true);
|
|
735
|
+
const start = events.get("session_start")!;
|
|
736
|
+
await start({}, ctx);
|
|
737
|
+
stale = true;
|
|
738
|
+
|
|
739
|
+
vi.advanceTimersByTime(3000);
|
|
740
|
+
|
|
741
|
+
expect(setStatus).toHaveBeenNthCalledWith(1, "fff", "FFF indexed");
|
|
742
|
+
expect(setStatus).toHaveBeenNthCalledWith(2, "fff", undefined);
|
|
743
|
+
});
|
|
744
|
+
|
|
717
745
|
it("shutdown → subsequent find falls back to SDK", async () => {
|
|
718
746
|
await loadWithFFF();
|
|
719
747
|
await events.get("session_shutdown")!();
|
|
@@ -128,11 +128,8 @@ describe("image rendering terminal detection", () => {
|
|
|
128
128
|
expect(__imageInternals.getTmuxPassthroughWarning("kitty")).toBeNull();
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
it("renders
|
|
132
|
-
process.env.
|
|
133
|
-
process.env.TERM_PROGRAM = "tmux";
|
|
134
|
-
process.env.KITTY_WINDOW_ID = "1";
|
|
135
|
-
__imageInternals.setTmuxAllowPassthroughOverrideForTests(false);
|
|
131
|
+
it("renders image metadata without a second inline preview", async () => {
|
|
132
|
+
process.env.TERM_PROGRAM = "kitty";
|
|
136
133
|
|
|
137
134
|
const readTool = loadReadTool(async () => ({
|
|
138
135
|
content: [{ type: "image", data: Buffer.from("fake").toString("base64"), mimeType: "image/png" }],
|
|
@@ -147,25 +144,10 @@ describe("image rendering terminal detection", () => {
|
|
|
147
144
|
invalidate: () => {},
|
|
148
145
|
});
|
|
149
146
|
|
|
150
|
-
expect(rendered.getText()).toContain("
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const readTool = loadReadTool(async () => ({
|
|
157
|
-
content: [{ type: "image", data: Buffer.from("jpeg").toString("base64"), mimeType: "image/jpeg" }],
|
|
158
|
-
}));
|
|
159
|
-
|
|
160
|
-
const result = await readTool.execute("t1", { path: "media/photo.jpg" }, null, null, {});
|
|
161
|
-
const rendered = readTool.renderResult(result, {}, {}, {
|
|
162
|
-
lastComponent: new MockText(),
|
|
163
|
-
isError: false,
|
|
164
|
-
state: {},
|
|
165
|
-
expanded: false,
|
|
166
|
-
invalidate: () => {},
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
expect(rendered.getText()).toContain("supports PNG payloads");
|
|
147
|
+
expect(rendered.getText()).toContain("image/png");
|
|
148
|
+
expect(rendered.getText()).not.toContain("\x1b_G");
|
|
149
|
+
expect(result.content).toEqual([
|
|
150
|
+
{ type: "image", data: Buffer.from("fake").toString("base64"), mimeType: "image/png" },
|
|
151
|
+
]);
|
|
170
152
|
});
|
|
171
153
|
});
|