Skip to content

Commit c59b60c

Browse files
committed
Route stale-handler regression through the row retext path
1 parent 569eff5 commit c59b60c

1 file changed

Lines changed: 66 additions & 1 deletion

File tree

‎src/tui/url-click.test.ts‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { TextRenderable } from "@opentui/core";
1111

1212
import { defined } from "../../tests/helpers/defined.js";
1313
import { withTestRenderer } from "./harness";
14-
import { appendStreamRow } from "./shell/chrome";
14+
import { appendStreamRow, replaceStreamRowAt } from "./shell/chrome";
1515
import { createAppShell } from "./shell/index";
1616
import {
1717
isUnderlined,
@@ -188,4 +188,69 @@ describe("Ctrl+clicking a transcript URL", () => {
188188
{ width: 80, height: 24 },
189189
);
190190
});
191+
192+
test("retexting a plain row's URL away through the row path disarms it", async () => {
193+
await withTestRenderer(
194+
async (h) => {
195+
const shell = createAppShell(h.renderer, {
196+
terminal: { columns: 80, rows: 24 },
197+
wireKeys: false,
198+
run: "idle",
199+
});
200+
const opened: string[] = [];
201+
setUrlOpener((url) => {
202+
opened.push(url);
203+
});
204+
try {
205+
// A user row paints literal text through paintPlainRowNode, so the
206+
// arm and the later disarm both run on the production row path.
207+
appendStreamRow(shell, {
208+
role: "user",
209+
text: "see https://example.com/x ok",
210+
});
211+
await h.renderOnce();
212+
213+
const link = findCell(h.captureCharFrame(), "example.com");
214+
expect(link).not.toBeNull();
215+
const at = defined(link);
216+
217+
await h.mockMouse.click(at.x, at.y, 0, {
218+
modifiers: { ctrl: true },
219+
});
220+
await h.renderOnce();
221+
expect(opened).toEqual(["https://example.com/x"]);
222+
223+
// Retext in place through replaceStreamRowAt -> retextStreamRow ->
224+
// paintPlainRowNode's URL-free branch. Reverting that branch's
225+
// disarm must fail this test (stale handlers survive on the node).
226+
replaceStreamRowAt(shell, 0, {
227+
role: "user",
228+
text: "see nothing here",
229+
});
230+
await h.renderOnce();
231+
const frame = h.captureCharFrame();
232+
expect(frame).toContain("nothing here");
233+
expect(frame).not.toContain("example.com");
234+
235+
opened.length = 0;
236+
await h.mockMouse.click(at.x, at.y, 0, {
237+
modifiers: { ctrl: true },
238+
});
239+
await h.renderOnce();
240+
expect(opened).toEqual([]);
241+
242+
const before = h.captureCharFrame();
243+
await h.mockMouse.moveTo(at.x, at.y, {
244+
modifiers: { ctrl: true },
245+
});
246+
await h.renderOnce();
247+
expect(h.captureCharFrame()).toBe(before);
248+
} finally {
249+
resetUrlOpener();
250+
shell.dispose();
251+
}
252+
},
253+
{ width: 80, height: 24 },
254+
);
255+
});
191256
});

0 commit comments

Comments
 (0)