@heyhuynhgiabuu/pi-pretty 0.4.1 → 0.4.3
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 +1 -1
- package/package.json +1 -1
- package/release-notes/v0.4.2.md +36 -0
- package/src/fff-helpers.ts +16 -4
- package/src/index.ts +225 -130
- package/src/multi-grep-fallback.ts +248 -0
- package/test/bash-rendering.test.ts +109 -0
- package/test/fff-integration.test.ts +284 -9
- package/test/image-rendering.test.ts +7 -25
|
@@ -8,9 +8,17 @@
|
|
|
8
8
|
* - Graceful degradation (FFF fails → SDK fallback)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
12
15
|
import { CursorStore, fffFormatGrepText } from "../src/fff-helpers.js";
|
|
13
16
|
import piPrettyExtension, { type PiPrettyDeps } from "../src/index.js";
|
|
17
|
+
import {
|
|
18
|
+
getMultiGrepRipgrepArgs,
|
|
19
|
+
parseMultiGrepConstraints,
|
|
20
|
+
runMultiGrepRipgrepFallback,
|
|
21
|
+
} from "../src/multi-grep-fallback.js";
|
|
14
22
|
|
|
15
23
|
// =========================================================================
|
|
16
24
|
// 1. Unit tests — pure functions
|
|
@@ -109,6 +117,117 @@ describe("fffFormatGrepText", () => {
|
|
|
109
117
|
expect(lines[0]).toBe("a.ts:5:match");
|
|
110
118
|
expect(lines[1]).toBe("a.ts-6-after1");
|
|
111
119
|
});
|
|
120
|
+
|
|
121
|
+
it("sanitizes CRLF and CR without injecting grep record newlines", () => {
|
|
122
|
+
const items = [{
|
|
123
|
+
relativePath: "a.ts",
|
|
124
|
+
lineNumber: 5,
|
|
125
|
+
lineContent: "match\r\ncontinued\rtrail",
|
|
126
|
+
contextBefore: ["before\r\nline"],
|
|
127
|
+
contextAfter: ["after\rline"],
|
|
128
|
+
}];
|
|
129
|
+
const text = fffFormatGrepText(items, 100);
|
|
130
|
+
const lines = text.split("\n");
|
|
131
|
+
|
|
132
|
+
expect(lines).toEqual([
|
|
133
|
+
"a.ts-4-before\\nline",
|
|
134
|
+
"a.ts:5:match\\ncontinued\\rtrail",
|
|
135
|
+
"a.ts-6-after\\rline",
|
|
136
|
+
]);
|
|
137
|
+
expect(lines).toHaveLength(3);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("strips trailing CR from CRLF-backed FFF records", () => {
|
|
141
|
+
const items = [{ relativePath: "a.ts", lineNumber: 5, lineContent: "match\r" }];
|
|
142
|
+
expect(fffFormatGrepText(items, 100)).toBe("a.ts:5:match");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("multi_grep constraint parsing", () => {
|
|
147
|
+
it("maps complex include/exclude constraints to ripgrep globs", () => {
|
|
148
|
+
expect(parseMultiGrepConstraints("*.{ts,tsx} !test/")).toEqual({
|
|
149
|
+
ok: true,
|
|
150
|
+
tokens: ["*.{ts,tsx}", "!test/"],
|
|
151
|
+
globs: ["*.{ts,tsx}", "!**/test/**"],
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("maps directory constraints as path components", () => {
|
|
156
|
+
expect(parseMultiGrepConstraints("src/ !src/generated/")).toEqual({
|
|
157
|
+
ok: true,
|
|
158
|
+
tokens: ["src/", "!src/generated/"],
|
|
159
|
+
globs: ["**/src/**", "!**/src/generated/**"],
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("builds literal ripgrep OR arguments with every constraint glob", () => {
|
|
164
|
+
const result = getMultiGrepRipgrepArgs({
|
|
165
|
+
cwd: "/repo",
|
|
166
|
+
patterns: ["foo", "bar"],
|
|
167
|
+
path: "src",
|
|
168
|
+
constraints: "*.ts !test/",
|
|
169
|
+
ignoreCase: true,
|
|
170
|
+
limit: 100,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result.ok).toBe(true);
|
|
174
|
+
if (!result.ok) return;
|
|
175
|
+
expect(result.args).toEqual([
|
|
176
|
+
"--line-number",
|
|
177
|
+
"--with-filename",
|
|
178
|
+
"--color=never",
|
|
179
|
+
"--hidden",
|
|
180
|
+
"--fixed-strings",
|
|
181
|
+
"--ignore-case",
|
|
182
|
+
"--glob",
|
|
183
|
+
"*.ts",
|
|
184
|
+
"--glob",
|
|
185
|
+
"!**/test/**",
|
|
186
|
+
"-e",
|
|
187
|
+
"foo",
|
|
188
|
+
"-e",
|
|
189
|
+
"bar",
|
|
190
|
+
"--",
|
|
191
|
+
"src",
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("ripgrep fallback enforces include/exclude constraints without widening", async () => {
|
|
196
|
+
const root = mkdtempSync(join(tmpdir(), "pi-pretty-mgrep-"));
|
|
197
|
+
try {
|
|
198
|
+
mkdirSync(join(root, "src", "test"), { recursive: true });
|
|
199
|
+
mkdirSync(join(root, "test"), { recursive: true });
|
|
200
|
+
writeFileSync(join(root, "src", "keep.ts"), "needle\n");
|
|
201
|
+
writeFileSync(join(root, "src", "keep.js"), "needle\n");
|
|
202
|
+
writeFileSync(join(root, "src", "test", "drop.ts"), "needle\n");
|
|
203
|
+
writeFileSync(join(root, "test", "drop.ts"), "needle\n");
|
|
204
|
+
|
|
205
|
+
const result = await runMultiGrepRipgrepFallback({
|
|
206
|
+
cwd: root,
|
|
207
|
+
patterns: ["needle"],
|
|
208
|
+
constraints: "*.ts !test/",
|
|
209
|
+
ignoreCase: true,
|
|
210
|
+
limit: 100,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(result.text).toContain("src/keep.ts");
|
|
214
|
+
expect(result.text).not.toContain("src/keep.js");
|
|
215
|
+
expect(result.text).not.toContain("src/test/drop.ts");
|
|
216
|
+
expect(result.text).not.toContain("test/drop.ts");
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (String(error).includes("ripgrep (rg) is not available")) return;
|
|
219
|
+
throw error;
|
|
220
|
+
} finally {
|
|
221
|
+
rmSync(root, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("rejects unsupported empty negation instead of ignoring it", () => {
|
|
226
|
+
expect(parseMultiGrepConstraints("*.ts !")).toEqual({
|
|
227
|
+
ok: false,
|
|
228
|
+
error: "empty constraint token: !",
|
|
229
|
+
});
|
|
230
|
+
});
|
|
112
231
|
});
|
|
113
232
|
|
|
114
233
|
// =========================================================================
|
|
@@ -172,6 +291,7 @@ describe("piPrettyExtension integration", () => {
|
|
|
172
291
|
const readExec = vi.fn();
|
|
173
292
|
const bashExec = vi.fn();
|
|
174
293
|
const lsExec = vi.fn();
|
|
294
|
+
const multiGrepRgExec = vi.fn();
|
|
175
295
|
|
|
176
296
|
function makeDeps(withFFF: boolean, finderOverrides?: Record<string, any>): PiPrettyDeps {
|
|
177
297
|
const finder = mkFinder(finderOverrides);
|
|
@@ -189,10 +309,12 @@ describe("piPrettyExtension integration", () => {
|
|
|
189
309
|
},
|
|
190
310
|
TextComponent: class { private t = ""; setText(v: string) { this.t = v; } getText() { return this.t; } },
|
|
191
311
|
fffModule: withFFF ? fffModule : undefined,
|
|
312
|
+
multiGrepRipgrepFallback: multiGrepRgExec,
|
|
192
313
|
};
|
|
193
314
|
}
|
|
194
315
|
|
|
195
316
|
beforeEach(() => {
|
|
317
|
+
vi.useRealTimers();
|
|
196
318
|
tools = new Map();
|
|
197
319
|
events = new Map();
|
|
198
320
|
mockPi = {
|
|
@@ -201,12 +323,13 @@ describe("piPrettyExtension integration", () => {
|
|
|
201
323
|
on: vi.fn((e: string, h: Function) => events.set(e, h)),
|
|
202
324
|
};
|
|
203
325
|
|
|
204
|
-
for (const fn of [findExec, grepExec, readExec, bashExec, lsExec]) fn.mockReset();
|
|
326
|
+
for (const fn of [findExec, grepExec, readExec, bashExec, lsExec, multiGrepRgExec]) fn.mockReset();
|
|
205
327
|
findExec.mockResolvedValue({ content: [{ type: "text", text: "src/index.ts\nsrc/main.ts" }] });
|
|
206
328
|
grepExec.mockResolvedValue({ content: [{ type: "text", text: "src/index.ts:10:const x = 1;" }] });
|
|
207
329
|
readExec.mockResolvedValue({ content: [{ type: "text", text: "content" }] });
|
|
208
330
|
bashExec.mockResolvedValue({ content: [{ type: "text", text: "output" }] });
|
|
209
331
|
lsExec.mockResolvedValue({ content: [{ type: "text", text: "f1\nf2" }] });
|
|
332
|
+
multiGrepRgExec.mockResolvedValue({ text: "src/index.ts:10:const x = 1;", matchCount: 1, limitReached: false });
|
|
210
333
|
});
|
|
211
334
|
|
|
212
335
|
function load(withFFF = false, finderOverrides?: Record<string, any>) {
|
|
@@ -214,6 +337,10 @@ describe("piPrettyExtension integration", () => {
|
|
|
214
337
|
piPrettyExtension(mockPi, deps);
|
|
215
338
|
}
|
|
216
339
|
|
|
340
|
+
afterEach(() => {
|
|
341
|
+
vi.useRealTimers();
|
|
342
|
+
});
|
|
343
|
+
|
|
217
344
|
async function loadWithFFF(finderOverrides?: Record<string, any>) {
|
|
218
345
|
load(true, finderOverrides);
|
|
219
346
|
const start = events.get("session_start")!;
|
|
@@ -236,9 +363,9 @@ describe("piPrettyExtension integration", () => {
|
|
|
236
363
|
expect(tools.has("multi_grep")).toBe(true);
|
|
237
364
|
});
|
|
238
365
|
|
|
239
|
-
it("
|
|
366
|
+
it("registers multi_grep when grep SDK available", () => {
|
|
240
367
|
load(false);
|
|
241
|
-
expect(tools.has("multi_grep")).toBe(
|
|
368
|
+
expect(tools.has("multi_grep")).toBe(true);
|
|
242
369
|
});
|
|
243
370
|
|
|
244
371
|
it("registers session_start + session_shutdown", () => {
|
|
@@ -285,6 +412,32 @@ describe("piPrettyExtension integration", () => {
|
|
|
285
412
|
const r = await tools.get("grep")!.execute("t1", { pattern: "TODO" }, null, null, {});
|
|
286
413
|
expect(r.details.matchCount).toBe(3);
|
|
287
414
|
});
|
|
415
|
+
|
|
416
|
+
it("normalizes CRLF in SDK text results", async () => {
|
|
417
|
+
grepExec.mockResolvedValue({
|
|
418
|
+
content: [{ type: "text", text: "a.ts:1:TODO\r\na.ts:5:TODO\rb.ts:10:TODO" }],
|
|
419
|
+
});
|
|
420
|
+
load(false);
|
|
421
|
+
const r = await tools.get("grep")!.execute("t1", { pattern: "TODO" }, null, null, {});
|
|
422
|
+
expect(r.content[0].text).toBe("a.ts:1:TODO\na.ts:5:TODO\nb.ts:10:TODO");
|
|
423
|
+
expect(r.details.text).toBe("a.ts:1:TODO\na.ts:5:TODO\nb.ts:10:TODO");
|
|
424
|
+
expect(r.details.matchCount).toBe(3);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// ---- read -----------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
describe("read", () => {
|
|
431
|
+
it("normalizes CRLF in read details content", async () => {
|
|
432
|
+
readExec.mockResolvedValue({
|
|
433
|
+
content: [{ type: "text", text: "line1\r\nline2\rline3" }],
|
|
434
|
+
});
|
|
435
|
+
load(false);
|
|
436
|
+
const r = await tools.get("read")!.execute("t1", { path: "file.txt" }, null, null, {});
|
|
437
|
+
expect(r.details._type).toBe("readFile");
|
|
438
|
+
expect(r.details.content).toBe("line1\nline2\nline3");
|
|
439
|
+
expect(r.details.lineCount).toBe(3);
|
|
440
|
+
});
|
|
288
441
|
});
|
|
289
442
|
|
|
290
443
|
// ---- find: FFF path ------------------------------------------------
|
|
@@ -356,6 +509,22 @@ describe("piPrettyExtension integration", () => {
|
|
|
356
509
|
expect(r.content[0].text).toContain("src/index.ts:42:const x = 1;");
|
|
357
510
|
});
|
|
358
511
|
|
|
512
|
+
it("sanitizes CRLF in FFF grep output without extra records", async () => {
|
|
513
|
+
await loadWithFFF({
|
|
514
|
+
grep: vi.fn().mockReturnValue({
|
|
515
|
+
ok: true,
|
|
516
|
+
value: {
|
|
517
|
+
items: [{ relativePath: "src/index.ts", lineNumber: 42, lineContent: "const x = 1;\r\nconst y = 2;" }],
|
|
518
|
+
totalMatched: 1,
|
|
519
|
+
nextCursor: null,
|
|
520
|
+
},
|
|
521
|
+
}),
|
|
522
|
+
});
|
|
523
|
+
const r = await tools.get("grep")!.execute("t1", { pattern: "const" }, null, null, {});
|
|
524
|
+
expect(r.content[0].text).toBe("src/index.ts:42:const x = 1;\\nconst y = 2;");
|
|
525
|
+
expect(r.details.text.split("\n")).toHaveLength(1);
|
|
526
|
+
});
|
|
527
|
+
|
|
359
528
|
it("literal=true → mode=plain", async () => {
|
|
360
529
|
const grep = vi.fn().mockReturnValue({ ok: true, value: { items: [], totalMatched: 0, nextCursor: null } });
|
|
361
530
|
await loadWithFFF({ grep });
|
|
@@ -415,10 +584,11 @@ describe("piPrettyExtension integration", () => {
|
|
|
415
584
|
expect(r.content[0].text).toContain("patterns array must have at least 1 element");
|
|
416
585
|
});
|
|
417
586
|
|
|
418
|
-
it("
|
|
587
|
+
it("falls back to SDK when FFF not initialized (no session_start)", async () => {
|
|
419
588
|
load(true);
|
|
420
589
|
const r = await tools.get("multi_grep")!.execute("t1", { patterns: ["foo"] }, null, null, null);
|
|
421
|
-
expect(
|
|
590
|
+
expect(grepExec).toHaveBeenCalledOnce();
|
|
591
|
+
expect(r.details._type).toBe("grepResult");
|
|
422
592
|
});
|
|
423
593
|
|
|
424
594
|
it("returns multiGrep results", async () => {
|
|
@@ -442,13 +612,95 @@ describe("piPrettyExtension integration", () => {
|
|
|
442
612
|
expect(r.content[0].text).toContain("compile failed");
|
|
443
613
|
});
|
|
444
614
|
|
|
445
|
-
it("passes
|
|
615
|
+
it("passes context to unconstrained FFF multiGrep", async () => {
|
|
446
616
|
const multiGrep = vi.fn().mockReturnValue({ ok: true, value: { items: [], totalMatched: 0, nextCursor: null } });
|
|
447
617
|
await loadWithFFF({ multiGrep });
|
|
448
|
-
await tools.get("multi_grep")!.execute("t1", { patterns: ["a", "b"],
|
|
618
|
+
await tools.get("multi_grep")!.execute("t1", { patterns: ["a", "b"], context: 2 }, null, null, null);
|
|
449
619
|
expect(multiGrep).toHaveBeenCalledWith(expect.objectContaining({
|
|
450
|
-
patterns: ["a", "b"],
|
|
620
|
+
patterns: ["a", "b"], beforeContext: 2, afterContext: 2,
|
|
621
|
+
}));
|
|
622
|
+
expect(multiGrep.mock.calls[0][0]).not.toHaveProperty("constraints");
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("glob constraints bypass FFF multiGrep and use ripgrep fallback", async () => {
|
|
626
|
+
const multiGrep = vi.fn().mockReturnValue({ ok: true, value: { items: [], totalMatched: 0, nextCursor: null } });
|
|
627
|
+
await loadWithFFF({ multiGrep });
|
|
628
|
+
await tools.get("multi_grep")!.execute("t1", { patterns: ["a", "b"], constraints: "*.ts", context: 2 }, null, null, {});
|
|
629
|
+
expect(multiGrep).not.toHaveBeenCalled();
|
|
630
|
+
expect(grepExec).not.toHaveBeenCalled();
|
|
631
|
+
expect(multiGrepRgExec).toHaveBeenCalledWith(expect.objectContaining({
|
|
632
|
+
patterns: ["a", "b"], constraints: "*.ts", context: 2, ignoreCase: true,
|
|
633
|
+
}));
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("path and constraints together bypass FFF multiGrep", async () => {
|
|
637
|
+
const multiGrep = vi.fn().mockReturnValue({ ok: true, value: { items: [], totalMatched: 0, nextCursor: null } });
|
|
638
|
+
await loadWithFFF({ multiGrep });
|
|
639
|
+
await tools.get("multi_grep")!.execute(
|
|
640
|
+
"t1",
|
|
641
|
+
{ patterns: ["a", "b"], path: "src", constraints: "*.ts" },
|
|
642
|
+
null,
|
|
643
|
+
null,
|
|
644
|
+
{},
|
|
645
|
+
);
|
|
646
|
+
expect(multiGrep).not.toHaveBeenCalled();
|
|
647
|
+
expect(grepExec).not.toHaveBeenCalled();
|
|
648
|
+
expect(multiGrepRgExec).toHaveBeenCalledWith(expect.objectContaining({
|
|
649
|
+
patterns: ["a", "b"], path: "src", constraints: "*.ts",
|
|
650
|
+
}));
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("falls back to SDK when path is provided", async () => {
|
|
654
|
+
const multiGrep = vi.fn().mockReturnValue({ ok: true, value: { items: [], totalMatched: 0, nextCursor: null } });
|
|
655
|
+
await loadWithFFF({ multiGrep });
|
|
656
|
+
await tools.get("multi_grep")!.execute("t1", { patterns: ["foo", "bar"], path: "src" }, null, null, {});
|
|
657
|
+
expect(multiGrep).not.toHaveBeenCalled();
|
|
658
|
+
expect(grepExec).toHaveBeenCalledWith(
|
|
659
|
+
"t1",
|
|
660
|
+
expect.objectContaining({ pattern: "foo|bar", path: "src", ignoreCase: true }),
|
|
661
|
+
null,
|
|
662
|
+
null,
|
|
663
|
+
{},
|
|
664
|
+
);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it("uses path-backed ripgrep fallback for simple path constraints", async () => {
|
|
668
|
+
const multiGrep = vi.fn().mockReturnValue({ ok: true, value: { items: [], totalMatched: 0, nextCursor: null } });
|
|
669
|
+
await loadWithFFF({ multiGrep });
|
|
670
|
+
await tools.get("multi_grep")!.execute("t1", { patterns: ["foo", "bar"], constraints: "src" }, null, null, {});
|
|
671
|
+
expect(multiGrep).not.toHaveBeenCalled();
|
|
672
|
+
expect(grepExec).not.toHaveBeenCalled();
|
|
673
|
+
expect(multiGrepRgExec).toHaveBeenCalledWith(expect.objectContaining({
|
|
674
|
+
patterns: ["foo", "bar"], path: "src", constraints: undefined,
|
|
675
|
+
}));
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it("preserves complex constraints in ripgrep fallback", async () => {
|
|
679
|
+
await loadWithFFF();
|
|
680
|
+
const result = await tools.get("multi_grep")!.execute(
|
|
681
|
+
"t1",
|
|
682
|
+
{ patterns: ["foo", "bar"], path: "src", constraints: "*.ts !test/" },
|
|
683
|
+
null,
|
|
684
|
+
null,
|
|
685
|
+
{},
|
|
686
|
+
);
|
|
687
|
+
expect(grepExec).not.toHaveBeenCalled();
|
|
688
|
+
expect(multiGrepRgExec).toHaveBeenCalledWith(expect.objectContaining({
|
|
689
|
+
patterns: ["foo", "bar"], path: "src", constraints: "*.ts !test/",
|
|
451
690
|
}));
|
|
691
|
+
expect(result.content[0].text).not.toContain("ignored unsupported constraints");
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it("uses case-sensitive SDK fallback when any pattern contains uppercase", async () => {
|
|
695
|
+
await loadWithFFF();
|
|
696
|
+
await tools.get("multi_grep")!.execute("t1", { patterns: ["foo", "Bar"], path: "src" }, null, null, {});
|
|
697
|
+
expect(grepExec).toHaveBeenCalledWith(
|
|
698
|
+
"t1",
|
|
699
|
+
expect.objectContaining({ pattern: "foo|Bar", ignoreCase: false }),
|
|
700
|
+
null,
|
|
701
|
+
null,
|
|
702
|
+
{},
|
|
703
|
+
);
|
|
452
704
|
});
|
|
453
705
|
});
|
|
454
706
|
|
|
@@ -467,6 +719,29 @@ describe("piPrettyExtension integration", () => {
|
|
|
467
719
|
}));
|
|
468
720
|
});
|
|
469
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
|
+
|
|
470
745
|
it("shutdown → subsequent find falls back to SDK", async () => {
|
|
471
746
|
await loadWithFFF();
|
|
472
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
|
});
|