@heyhuynhgiabuu/pi-pretty 0.3.1 → 0.3.2
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/README.md +9 -1
- package/package.json +1 -1
- package/release-notes/v0.3.2.md +34 -0
- package/src/index.ts +121 -19
- package/test/image-rendering.test.ts +165 -0
package/README.md
CHANGED
|
@@ -41,7 +41,15 @@ pi -e ./src/index.ts
|
|
|
41
41
|
## Terminal support for inline images
|
|
42
42
|
|
|
43
43
|
Inline image previews are supported in **Ghostty**, **Kitty**, **iTerm2**, and **WezTerm**.
|
|
44
|
-
When running in **tmux**, pi-pretty uses passthrough escape sequences
|
|
44
|
+
When running in **tmux**, pi-pretty uses passthrough escape sequences.
|
|
45
|
+
|
|
46
|
+
> tmux must allow passthrough. Enable it with:
|
|
47
|
+
>
|
|
48
|
+
> ```tmux
|
|
49
|
+
> set -g allow-passthrough on
|
|
50
|
+
> ```
|
|
51
|
+
>
|
|
52
|
+
> (or run once in a session: `tmux set -g allow-passthrough on`)
|
|
45
53
|
|
|
46
54
|
## Configuration
|
|
47
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyhuynhgiabuu/pi-pretty",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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,34 @@
|
|
|
1
|
+
# pi-pretty v0.3.2
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
This patch release fixes inline image rendering reliability when running `pi-pretty` inside `tmux`.
|
|
5
|
+
|
|
6
|
+
## What changed
|
|
7
|
+
- Fixed terminal detection in tmux for:
|
|
8
|
+
- Kitty (`KITTY_WINDOW_ID`, `KITTY_PID`)
|
|
9
|
+
- WezTerm (`WEZTERM_EXECUTABLE`, `WEZTERM_CONFIG_DIR`, `WEZTERM_CONFIG_FILE`)
|
|
10
|
+
- Added tmux fallback detection via:
|
|
11
|
+
- `tmux display-message -p "#{client_termname}"`
|
|
12
|
+
- Replaced static tmux detection with runtime detection to avoid stale module-load state.
|
|
13
|
+
- Added explicit warning when tmux passthrough is disabled:
|
|
14
|
+
- `tmux allow-passthrough is off. Run: tmux set -g allow-passthrough on`
|
|
15
|
+
- Added non-PNG warning for Kitty/Ghostty image rendering path.
|
|
16
|
+
- Added test coverage for image protocol detection and tmux passthrough warning behavior.
|
|
17
|
+
- Updated README with tmux passthrough setup instructions.
|
|
18
|
+
|
|
19
|
+
## Files
|
|
20
|
+
- `src/index.ts`
|
|
21
|
+
- `test/image-rendering.test.ts`
|
|
22
|
+
- `README.md`
|
|
23
|
+
|
|
24
|
+
## Verification
|
|
25
|
+
- `npm run typecheck` ✅
|
|
26
|
+
- `npm test` ✅ (46 tests)
|
|
27
|
+
- `npm run lint` ⚠️ fails due to existing Biome diagnostics in legacy code paths (pre-existing, not introduced in this patch).
|
|
28
|
+
|
|
29
|
+
## Upgrade notes
|
|
30
|
+
When using `tmux`, enable passthrough:
|
|
31
|
+
|
|
32
|
+
```tmux
|
|
33
|
+
set -g allow-passthrough on
|
|
34
|
+
```
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* • Large-file fallback (skip highlighting, still show line numbers)
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import * as childProcess from "node:child_process";
|
|
26
27
|
import { existsSync, mkdirSync, statSync } from "node:fs";
|
|
27
28
|
import { basename, dirname, extname, join, relative } from "node:path";
|
|
28
29
|
|
|
@@ -217,7 +218,45 @@ function lang(fp: string): BundledLanguage | undefined {
|
|
|
217
218
|
|
|
218
219
|
type ImageProtocol = "iterm2" | "kitty" | "none";
|
|
219
220
|
|
|
220
|
-
|
|
221
|
+
let _tmuxClientTermCache: string | null | undefined;
|
|
222
|
+
let _tmuxAllowPassthroughCache: boolean | null | undefined;
|
|
223
|
+
let _tmuxClientTermOverrideForTests: string | null | undefined;
|
|
224
|
+
let _tmuxAllowPassthroughOverrideForTests: boolean | null | undefined;
|
|
225
|
+
|
|
226
|
+
function isTmuxSession(): boolean {
|
|
227
|
+
return !!process.env.TMUX || /^(tmux|screen)/.test(process.env.TERM ?? "");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function normalizeTerminalName(term: string): string {
|
|
231
|
+
const t = term.toLowerCase();
|
|
232
|
+
if (t.includes("kitty")) return "kitty";
|
|
233
|
+
if (t.includes("ghostty")) return "ghostty";
|
|
234
|
+
if (t.includes("wezterm")) return "WezTerm";
|
|
235
|
+
if (t.includes("iterm")) return "iTerm.app";
|
|
236
|
+
if (t.includes("mintty")) return "mintty";
|
|
237
|
+
return term;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function readTmuxClientTerm(): string | null {
|
|
241
|
+
if (_tmuxClientTermOverrideForTests !== undefined) {
|
|
242
|
+
return _tmuxClientTermOverrideForTests ? normalizeTerminalName(_tmuxClientTermOverrideForTests) : null;
|
|
243
|
+
}
|
|
244
|
+
if (!isTmuxSession()) return null;
|
|
245
|
+
if (_tmuxClientTermCache !== undefined) return _tmuxClientTermCache;
|
|
246
|
+
try {
|
|
247
|
+
const term = childProcess
|
|
248
|
+
.execFileSync("tmux", ["display-message", "-p", "#{client_termname}"], {
|
|
249
|
+
encoding: "utf8",
|
|
250
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
251
|
+
timeout: 200,
|
|
252
|
+
})
|
|
253
|
+
.trim();
|
|
254
|
+
_tmuxClientTermCache = term ? normalizeTerminalName(term) : null;
|
|
255
|
+
} catch {
|
|
256
|
+
_tmuxClientTermCache = null;
|
|
257
|
+
}
|
|
258
|
+
return _tmuxClientTermCache;
|
|
259
|
+
}
|
|
221
260
|
|
|
222
261
|
/**
|
|
223
262
|
* Detect the outer terminal when running inside tmux.
|
|
@@ -225,27 +264,34 @@ const IS_TMUX = !!process.env.TMUX;
|
|
|
225
264
|
* the environment of the tmux server or can be inferred.
|
|
226
265
|
*/
|
|
227
266
|
function getOuterTerminal(): string {
|
|
228
|
-
//
|
|
229
|
-
const term = process.env.TERM_PROGRAM ?? "";
|
|
230
|
-
if (term !== "tmux" && term !== "screen") return term;
|
|
231
|
-
|
|
232
|
-
// Inside tmux: check common env vars that leak through
|
|
233
|
-
// Ghostty sets this; iTerm2 sets LC_TERMINAL
|
|
267
|
+
// Environment hints that often survive inside tmux
|
|
234
268
|
if (process.env.LC_TERMINAL === "iTerm2") return "iTerm.app";
|
|
235
|
-
|
|
236
|
-
// TERM_PROGRAM_VERSION sometimes survives into tmux
|
|
237
|
-
// Try to detect via COLORTERM or other hints
|
|
238
269
|
if (process.env.GHOSTTY_RESOURCES_DIR) return "ghostty";
|
|
270
|
+
if (process.env.KITTY_WINDOW_ID || process.env.KITTY_PID) return "kitty";
|
|
271
|
+
if (process.env.WEZTERM_EXECUTABLE || process.env.WEZTERM_CONFIG_DIR || process.env.WEZTERM_CONFIG_FILE) {
|
|
272
|
+
return "WezTerm";
|
|
273
|
+
}
|
|
239
274
|
|
|
240
|
-
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
return "unknown-modern";
|
|
275
|
+
const termProgram = process.env.TERM_PROGRAM ?? "";
|
|
276
|
+
if (termProgram && termProgram !== "tmux" && termProgram !== "screen") {
|
|
277
|
+
return normalizeTerminalName(termProgram);
|
|
244
278
|
}
|
|
245
|
-
|
|
279
|
+
|
|
280
|
+
const tmuxClientTerm = readTmuxClientTerm();
|
|
281
|
+
if (tmuxClientTerm) return tmuxClientTerm;
|
|
282
|
+
|
|
283
|
+
const term = process.env.TERM ?? "";
|
|
284
|
+
if (term) return normalizeTerminalName(term);
|
|
285
|
+
if (process.env.COLORTERM === "truecolor" || process.env.COLORTERM === "24bit") return "unknown-modern";
|
|
286
|
+
return termProgram;
|
|
246
287
|
}
|
|
247
288
|
|
|
248
289
|
function detectImageProtocol(): ImageProtocol {
|
|
290
|
+
const forced = (process.env.PRETTY_IMAGE_PROTOCOL ?? "").toLowerCase();
|
|
291
|
+
if (forced === "kitty" || forced === "iterm2" || forced === "none") {
|
|
292
|
+
return forced;
|
|
293
|
+
}
|
|
294
|
+
|
|
249
295
|
const term = getOuterTerminal();
|
|
250
296
|
// Ghostty and Kitty use the Kitty graphics protocol
|
|
251
297
|
if (term === "ghostty" || term === "kitty") return "kitty";
|
|
@@ -255,18 +301,67 @@ function detectImageProtocol(): ImageProtocol {
|
|
|
255
301
|
return "none";
|
|
256
302
|
}
|
|
257
303
|
|
|
304
|
+
function tmuxAllowsPassthrough(): boolean | null {
|
|
305
|
+
if (_tmuxAllowPassthroughOverrideForTests !== undefined) return _tmuxAllowPassthroughOverrideForTests;
|
|
306
|
+
if (!isTmuxSession()) return null;
|
|
307
|
+
if (_tmuxAllowPassthroughCache !== undefined) return _tmuxAllowPassthroughCache;
|
|
308
|
+
try {
|
|
309
|
+
const value = childProcess
|
|
310
|
+
.execFileSync("tmux", ["show-options", "-gv", "allow-passthrough"], {
|
|
311
|
+
encoding: "utf8",
|
|
312
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
313
|
+
timeout: 200,
|
|
314
|
+
})
|
|
315
|
+
.trim()
|
|
316
|
+
.toLowerCase();
|
|
317
|
+
_tmuxAllowPassthroughCache = value === "on" || value === "all";
|
|
318
|
+
} catch {
|
|
319
|
+
_tmuxAllowPassthroughCache = null;
|
|
320
|
+
}
|
|
321
|
+
return _tmuxAllowPassthroughCache;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function getTmuxPassthroughWarning(protocol: ImageProtocol): string | null {
|
|
325
|
+
if (!isTmuxSession() || protocol === "none") return null;
|
|
326
|
+
if (tmuxAllowsPassthrough() === false) {
|
|
327
|
+
return "tmux allow-passthrough is off. Run: tmux set -g allow-passthrough on";
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
258
332
|
/**
|
|
259
333
|
* Wrap escape sequence for tmux passthrough.
|
|
260
334
|
* tmux requires: ESC Ptmux; <escaped-sequence> ESC \
|
|
261
335
|
* Inner ESC chars must be doubled.
|
|
262
336
|
*/
|
|
263
337
|
function tmuxWrap(seq: string): string {
|
|
264
|
-
if (!
|
|
338
|
+
if (!isTmuxSession()) return seq;
|
|
265
339
|
// Double all ESC chars inside the sequence
|
|
266
340
|
const escaped = seq.split("\x1b").join("\x1b\x1b");
|
|
267
341
|
return `\x1bPtmux;${escaped}\x1b\\`;
|
|
268
342
|
}
|
|
269
343
|
|
|
344
|
+
export const __imageInternals = {
|
|
345
|
+
isTmuxSession,
|
|
346
|
+
getOuterTerminal,
|
|
347
|
+
detectImageProtocol,
|
|
348
|
+
tmuxWrap,
|
|
349
|
+
tmuxAllowsPassthrough,
|
|
350
|
+
getTmuxPassthroughWarning,
|
|
351
|
+
setTmuxClientTermOverrideForTests: (value: string | null | undefined) => {
|
|
352
|
+
_tmuxClientTermOverrideForTests = value;
|
|
353
|
+
},
|
|
354
|
+
setTmuxAllowPassthroughOverrideForTests: (value: boolean | null | undefined) => {
|
|
355
|
+
_tmuxAllowPassthroughOverrideForTests = value;
|
|
356
|
+
},
|
|
357
|
+
resetCachesForTests: () => {
|
|
358
|
+
_tmuxClientTermCache = undefined;
|
|
359
|
+
_tmuxAllowPassthroughCache = undefined;
|
|
360
|
+
_tmuxClientTermOverrideForTests = undefined;
|
|
361
|
+
_tmuxAllowPassthroughOverrideForTests = undefined;
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
270
365
|
/**
|
|
271
366
|
* Render base64 image inline using iTerm2 inline image protocol.
|
|
272
367
|
* Protocol: ESC ] 1337 ; File=[args] : base64data BEL
|
|
@@ -916,9 +1011,16 @@ export default function piPrettyExtension(pi: any, deps?: PiPrettyDeps): void {
|
|
|
916
1011
|
out.push(rule(tw));
|
|
917
1012
|
|
|
918
1013
|
const protocol = detectImageProtocol();
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
out.push(
|
|
1014
|
+
const passthroughWarning = getTmuxPassthroughWarning(protocol);
|
|
1015
|
+
if (passthroughWarning) {
|
|
1016
|
+
out.push(` ${FG_YELLOW}${passthroughWarning}${RST}`);
|
|
1017
|
+
} else if (protocol === "kitty") {
|
|
1018
|
+
if (d.mimeType && d.mimeType !== "image/png") {
|
|
1019
|
+
out.push(` ${FG_YELLOW}Kitty/Ghostty inline preview currently supports PNG payloads (got ${d.mimeType})${RST}`);
|
|
1020
|
+
} else {
|
|
1021
|
+
const imgCols = Math.min(tw - 4, 80);
|
|
1022
|
+
out.push(renderKittyImage(d.data, { cols: imgCols }));
|
|
1023
|
+
}
|
|
922
1024
|
} else if (protocol === "iterm2") {
|
|
923
1025
|
const imgWidth = Math.min(tw - 4, 80);
|
|
924
1026
|
out.push(
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import piPrettyExtension, { __imageInternals } from "../src/index.js";
|
|
4
|
+
|
|
5
|
+
const ENV_KEYS = [
|
|
6
|
+
"TMUX",
|
|
7
|
+
"TERM",
|
|
8
|
+
"TERM_PROGRAM",
|
|
9
|
+
"LC_TERMINAL",
|
|
10
|
+
"GHOSTTY_RESOURCES_DIR",
|
|
11
|
+
"KITTY_WINDOW_ID",
|
|
12
|
+
"KITTY_PID",
|
|
13
|
+
"WEZTERM_EXECUTABLE",
|
|
14
|
+
"WEZTERM_CONFIG_DIR",
|
|
15
|
+
"WEZTERM_CONFIG_FILE",
|
|
16
|
+
"COLORTERM",
|
|
17
|
+
"PRETTY_IMAGE_PROTOCOL",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
class MockText {
|
|
21
|
+
private text = "";
|
|
22
|
+
constructor(_text = "", _x = 0, _y = 0) {}
|
|
23
|
+
setText(value: string) {
|
|
24
|
+
this.text = value;
|
|
25
|
+
}
|
|
26
|
+
getText() {
|
|
27
|
+
return this.text;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function mockToolFactory(exec: any) {
|
|
32
|
+
return (_cwd: string) => ({
|
|
33
|
+
name: "mock",
|
|
34
|
+
description: "mock",
|
|
35
|
+
parameters: { type: "object", properties: {} },
|
|
36
|
+
execute: exec,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function loadReadTool(readExec: any) {
|
|
41
|
+
const noopExec = async () => ({ content: [{ type: "text", text: "" }] });
|
|
42
|
+
const tools = new Map<string, any>();
|
|
43
|
+
const pi = {
|
|
44
|
+
registerTool: (tool: any) => tools.set(tool.name, tool),
|
|
45
|
+
registerCommand: () => {},
|
|
46
|
+
on: () => {},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
piPrettyExtension(pi, {
|
|
50
|
+
sdk: {
|
|
51
|
+
createReadToolDefinition: mockToolFactory(readExec),
|
|
52
|
+
createBashToolDefinition: mockToolFactory(noopExec),
|
|
53
|
+
createLsToolDefinition: mockToolFactory(noopExec),
|
|
54
|
+
createFindToolDefinition: mockToolFactory(noopExec),
|
|
55
|
+
createGrepToolDefinition: mockToolFactory(noopExec),
|
|
56
|
+
getAgentDir: () => "/tmp/pi-pretty-test",
|
|
57
|
+
},
|
|
58
|
+
TextComponent: MockText,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return tools.get("read");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("image rendering terminal detection", () => {
|
|
65
|
+
const envSnapshot = new Map<string, string | undefined>();
|
|
66
|
+
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
for (const key of ENV_KEYS) {
|
|
69
|
+
envSnapshot.set(key, process.env[key]);
|
|
70
|
+
delete process.env[key];
|
|
71
|
+
}
|
|
72
|
+
__imageInternals.resetCachesForTests();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
for (const key of ENV_KEYS) {
|
|
77
|
+
const value = envSnapshot.get(key);
|
|
78
|
+
if (value === undefined) delete process.env[key];
|
|
79
|
+
else process.env[key] = value;
|
|
80
|
+
}
|
|
81
|
+
__imageInternals.resetCachesForTests();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("detects kitty protocol inside tmux via KITTY_WINDOW_ID", () => {
|
|
85
|
+
process.env.TMUX = "/tmp/tmux-1000/default,123,0";
|
|
86
|
+
process.env.TERM_PROGRAM = "tmux";
|
|
87
|
+
process.env.KITTY_WINDOW_ID = "1";
|
|
88
|
+
|
|
89
|
+
expect(__imageInternals.getOuterTerminal()).toBe("kitty");
|
|
90
|
+
expect(__imageInternals.detectImageProtocol()).toBe("kitty");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("detects wezterm protocol inside tmux via WEZTERM_EXECUTABLE", () => {
|
|
94
|
+
process.env.TMUX = "/tmp/tmux-1000/default,123,0";
|
|
95
|
+
process.env.TERM_PROGRAM = "tmux";
|
|
96
|
+
process.env.WEZTERM_EXECUTABLE = "/Applications/WezTerm.app/Contents/MacOS/wezterm";
|
|
97
|
+
|
|
98
|
+
expect(__imageInternals.getOuterTerminal()).toBe("WezTerm");
|
|
99
|
+
expect(__imageInternals.detectImageProtocol()).toBe("iterm2");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("falls back to tmux client term for outer terminal detection", () => {
|
|
103
|
+
process.env.TMUX = "/tmp/tmux-1000/default,123,0";
|
|
104
|
+
process.env.TERM_PROGRAM = "tmux";
|
|
105
|
+
__imageInternals.setTmuxClientTermOverrideForTests("xterm-kitty");
|
|
106
|
+
|
|
107
|
+
expect(__imageInternals.getOuterTerminal()).toBe("kitty");
|
|
108
|
+
expect(__imageInternals.detectImageProtocol()).toBe("kitty");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("reports warning when tmux allow-passthrough is off", () => {
|
|
112
|
+
process.env.TMUX = "/tmp/tmux-1000/default,123,0";
|
|
113
|
+
__imageInternals.setTmuxAllowPassthroughOverrideForTests(false);
|
|
114
|
+
|
|
115
|
+
expect(__imageInternals.getTmuxPassthroughWarning("kitty")).toContain("allow-passthrough is off");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("does not warn when tmux allow-passthrough is enabled", () => {
|
|
119
|
+
process.env.TMUX = "/tmp/tmux-1000/default,123,0";
|
|
120
|
+
__imageInternals.setTmuxAllowPassthroughOverrideForTests(true);
|
|
121
|
+
|
|
122
|
+
expect(__imageInternals.getTmuxPassthroughWarning("kitty")).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("renders explicit warning for read image when tmux passthrough is off", async () => {
|
|
126
|
+
process.env.TMUX = "/tmp/tmux-1000/default,123,0";
|
|
127
|
+
process.env.TERM_PROGRAM = "tmux";
|
|
128
|
+
process.env.KITTY_WINDOW_ID = "1";
|
|
129
|
+
__imageInternals.setTmuxAllowPassthroughOverrideForTests(false);
|
|
130
|
+
|
|
131
|
+
const readTool = loadReadTool(async () => ({
|
|
132
|
+
content: [{ type: "image", data: Buffer.from("fake").toString("base64"), mimeType: "image/png" }],
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
const result = await readTool.execute("t1", { path: "media/inline-image.png" }, null, null, {});
|
|
136
|
+
const rendered = readTool.renderResult(result, {}, {}, {
|
|
137
|
+
lastComponent: new MockText(),
|
|
138
|
+
isError: false,
|
|
139
|
+
state: {},
|
|
140
|
+
expanded: false,
|
|
141
|
+
invalidate: () => {},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(rendered.getText()).toContain("allow-passthrough is off");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("warns on non-PNG payloads for kitty protocol", async () => {
|
|
148
|
+
process.env.TERM_PROGRAM = "kitty";
|
|
149
|
+
|
|
150
|
+
const readTool = loadReadTool(async () => ({
|
|
151
|
+
content: [{ type: "image", data: Buffer.from("jpeg").toString("base64"), mimeType: "image/jpeg" }],
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
const result = await readTool.execute("t1", { path: "media/photo.jpg" }, null, null, {});
|
|
155
|
+
const rendered = readTool.renderResult(result, {}, {}, {
|
|
156
|
+
lastComponent: new MockText(),
|
|
157
|
+
isError: false,
|
|
158
|
+
state: {},
|
|
159
|
+
expanded: false,
|
|
160
|
+
invalidate: () => {},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(rendered.getText()).toContain("supports PNG payloads");
|
|
164
|
+
});
|
|
165
|
+
});
|