authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-05 01:29:35-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-05 01:29:35-07:00
log6615d3d70ba399c4b274d17f19487d94afac17ed
treeac7376784672263c040a70119beab6b02b5e0cf5
parent5a4287cd63aefef93c0fd15fe79f63a2f2657211
signature Signed by PGP key B5690EEEBB952194

refactor(web): test live Ghostty link resolution directly (#10041)


2 files changed, 15 insertions(+), 27 deletions(-)

apps/web/src/terminal/ghostty/surface.test.ts+15-15
......@@ -23,8 +23,6 @@ import {
2323 terminalGridCellAt,
2424 terminalScrollbarGeometry,
2525 terminalScrollbarOffsetAtPointer,
26 terminalLinkAtColumn,
27 terminalLinkAtPosition,
2826 terminalLinkAtPositionWithRange,
2927 terminalContentOriginY,
3028 terminalFontFamily,
......@@ -433,7 +431,7 @@ describe("shouldBlinkTerminalCursor", () => {
433431 });
434432});
435433
436describe("terminalLinkAtColumn", () => {
434describe("terminalLinkAtPositionWithRange", () => {
437435 it("maps terminal cells to UTF-16 offsets after a wide emoji", () => {
438436 const cells = [
439437 cell("🙂"),
......@@ -450,9 +448,11 @@ describe("terminalLinkAtColumn", () => {
450448 wrapsToNext: false,
451449 };
452450
453 expect(terminalLinkAtColumn(row, 2)).toBe("https://t3.codes");
454 expect(terminalLinkAtColumn(row, cells.length - 1)).toBe("https://t3.codes");
455 expect(terminalLinkAtColumn(row, 0)).toBeNull();
451 expect(terminalLinkAtPositionWithRange([row], 0, 2)?.text).toBe("https://t3.codes");
452 expect(terminalLinkAtPositionWithRange([row], 0, cells.length - 1)?.text).toBe(
453 "https://t3.codes",
454 );
455 expect(terminalLinkAtPositionWithRange([row], 0, 0)).toBeNull();
456456 expect(terminalLinkAtPositionWithRange([row], 0, 8)?.range).toEqual({
457457 start: { x: 2, y: 0 },
458458 end: { x: cells.length - 1, y: 0 },
......@@ -473,10 +473,10 @@ describe("terminalLinkAtColumn", () => {
473473 row("C:\\repo\\file.ts", false),
474474 ];
475475
476 expect(terminalLinkAtPosition(rows, 0, 8)).toBe("https://example.com/reference");
477 expect(terminalLinkAtPosition(rows, 1, 4)).toBe("https://example.com/reference");
478 expect(terminalLinkAtPosition(rows, 2, 2)).toBe("~/project/file");
479 expect(terminalLinkAtPosition(rows, 3, 4)).toBe("C:\\repo\\file.ts");
476 expect(terminalLinkAtPositionWithRange(rows, 0, 8)?.text).toBe("https://example.com/reference");
477 expect(terminalLinkAtPositionWithRange(rows, 1, 4)?.text).toBe("https://example.com/reference");
478 expect(terminalLinkAtPositionWithRange(rows, 2, 2)?.text).toBe("~/project/file");
479 expect(terminalLinkAtPositionWithRange(rows, 3, 4)?.text).toBe("C:\\repo\\file.ts");
480480 expect(terminalLinkAtPositionWithRange(rows, 1, 4)).toEqual({
481481 text: "https://example.com/reference",
482482 range: {
......@@ -495,13 +495,13 @@ describe("terminalLinkAtColumn", () => {
495495 });
496496 // The head of the wrapped line scrolled above the viewport.
497497 const headCut = [row("ple.com/missing", true), row("head", true)];
498 expect(terminalLinkAtPosition(headCut, 0, 4)).toBeNull();
498 expect(terminalLinkAtPositionWithRange(headCut, 0, 4)).toBeNull();
499499 // The bottom row soft-wraps on below the viewport.
500500 const tailCut = [row("https://t3.codes", false, true)];
501 expect(terminalLinkAtPosition(tailCut, 0, 8)).toBeNull();
501 expect(terminalLinkAtPositionWithRange(tailCut, 0, 8)).toBeNull();
502502 // A partial bottom row is provably complete and still resolves.
503503 const complete = [row("https://t3.codes", false), row("", false)];
504 expect(terminalLinkAtPosition(complete, 0, 8)).toBe("https://t3.codes");
504 expect(terminalLinkAtPositionWithRange(complete, 0, 8)?.text).toBe("https://t3.codes");
505505 // A wide grapheme earlier in the row must not break truncation detection:
506506 // the soft-wrap flag decides, not string-length-versus-cell-count.
507507 const wideFull: GhosttyRow = {
......@@ -514,7 +514,7 @@ describe("terminalLinkAtColumn", () => {
514514 isWrapContinuation: false,
515515 wrapsToNext: true,
516516 };
517 expect(terminalLinkAtPosition([wideFull], 0, 8)).toBeNull();
517 expect(terminalLinkAtPositionWithRange([wideFull], 0, 8)).toBeNull();
518518 // Unwritten trailing cells prove the bottom row is complete.
519519 const unwrittenTail: GhosttyRow = {
520520 cells: [
......@@ -526,7 +526,7 @@ describe("terminalLinkAtColumn", () => {
526526 isWrapContinuation: false,
527527 wrapsToNext: false,
528528 };
529 expect(terminalLinkAtPosition([unwrittenTail], 0, 8)).toBe("https://t3.codes");
529 expect(terminalLinkAtPositionWithRange([unwrittenTail], 0, 8)?.text).toBe("https://t3.codes");
530530 });
531531});
532532
apps/web/src/terminal/ghostty/surface.ts-12
......@@ -244,14 +244,6 @@ function terminalColumnOffset(row: GhosttySnapshot["rowData"][number], column: n
244244 return offset;
245245}
246246
247export function terminalLinkAtPosition(
248 rows: GhosttySnapshot["rowData"],
249 rowIndex: number,
250 column: number,
251): string | null {
252 return terminalLinkAtPositionWithRange(rows, rowIndex, column)?.text ?? null;
253}
254
255247export interface TerminalLinkWithRange {
256248 readonly text: string;
257249 readonly range: GhosttyCellRange;
......@@ -325,10 +317,6 @@ export function terminalLinkAtPositionWithRange(
325317 return null;
326318}
327319
328export function terminalLinkAtColumn(row: GhosttySnapshot["rowData"][number], column: number) {
329 return terminalLinkAtPosition([row], 0, column);
330}
331
332320export function isTerminalCopyShortcut(
333321 event: Pick<KeyboardEvent, "ctrlKey" | "key" | "metaKey" | "shiftKey">,
334322 platform = navigator.platform,