| import { describe, expect, it } from "vitest"; |
| import { addOsc8Hyperlinks, extractUrls, wrapOsc8 } from "./osc8-hyperlinks.js"; |
|
|
| describe("wrapOsc8", () => { |
| it("wraps text with OSC 8 open and close sequences", () => { |
| const result = wrapOsc8("https://example.com", "click here"); |
| expect(result).toBe("\x1b]8;;https://example.com\x07click here\x1b]8;;\x07"); |
| }); |
|
|
| it("handles empty text", () => { |
| const result = wrapOsc8("https://example.com", ""); |
| expect(result).toBe("\x1b]8;;https://example.com\x07\x1b]8;;\x07"); |
| }); |
| }); |
|
|
| describe("extractUrls", () => { |
| it("extracts bare URLs", () => { |
| const urls = extractUrls("Check out https://example.com for more info"); |
| expect(urls).toEqual(["https://example.com"]); |
| }); |
|
|
| it("extracts multiple bare URLs", () => { |
| const urls = extractUrls("Visit https://foo.com and http://bar.com"); |
| expect(urls).toContain("https://foo.com"); |
| expect(urls).toContain("http://bar.com"); |
| expect(urls).toHaveLength(2); |
| }); |
|
|
| it("extracts markdown link hrefs", () => { |
| const urls = extractUrls("[Click here](https://example.com/path)"); |
| expect(urls).toEqual(["https://example.com/path"]); |
| }); |
|
|
| it("extracts markdown links with angle brackets and title text", () => { |
| const urls = extractUrls('[Click here](<https://example.com/path> "Example Title")'); |
| expect(urls).toEqual(["https://example.com/path"]); |
| }); |
|
|
| it("extracts both bare URLs and markdown links", () => { |
| const md = "See [docs](https://docs.example.com) and https://api.example.com"; |
| const urls = extractUrls(md); |
| expect(urls).toContain("https://docs.example.com"); |
| expect(urls).toContain("https://api.example.com"); |
| expect(urls).toHaveLength(2); |
| }); |
|
|
| it("deduplicates URLs", () => { |
| const md = "Visit https://example.com and [link](https://example.com)"; |
| const urls = extractUrls(md); |
| expect(urls).toEqual(["https://example.com"]); |
| }); |
|
|
| it("returns empty array for text without URLs", () => { |
| expect(extractUrls("No links here")).toEqual([]); |
| }); |
|
|
| it("handles URLs with query params and fragments", () => { |
| const urls = extractUrls("https://example.com/path?q=1&r=2#section"); |
| expect(urls).toEqual(["https://example.com/path?q=1&r=2#section"]); |
| }); |
| }); |
|
|
| describe("addOsc8Hyperlinks", () => { |
| it("returns lines unchanged when no URLs", () => { |
| const lines = ["Hello world", "No links here"]; |
| expect(addOsc8Hyperlinks(lines, [])).toEqual(lines); |
| }); |
|
|
| it("wraps a single-line URL with OSC 8", () => { |
| const url = "https://example.com"; |
| const lines = [`Visit ${url} for info`]; |
| const result = addOsc8Hyperlinks(lines, [url]); |
|
|
| expect(result[0]).toContain(`\x1b]8;;${url}\x07`); |
| expect(result[0]).toContain(`\x1b]8;;\x07`); |
| |
| expect(result[0]).toBe(`Visit \x1b]8;;${url}\x07${url}\x1b]8;;\x07 for info`); |
| }); |
|
|
| it("wraps a URL broken across two lines", () => { |
| const fullUrl = "https://example.com/very/long/path/to/resource"; |
| const lines = ["https://example.com/very/long/pa", "th/to/resource"]; |
| const result = addOsc8Hyperlinks(lines, [fullUrl]); |
|
|
| |
| expect(result[0]).toContain(`\x1b]8;;${fullUrl}\x07`); |
| |
| expect(result[1]).toContain(`\x1b]8;;${fullUrl}\x07`); |
| }); |
|
|
| it("handles URL with ANSI styling codes", () => { |
| const url = "https://example.com"; |
| |
| const styledLine = `\x1b[32m${url}\x1b[0m`; |
| const result = addOsc8Hyperlinks([styledLine], [url]); |
|
|
| |
| expect(result[0]).toContain("\x1b[32m"); |
| expect(result[0]).toContain("\x1b[0m"); |
| expect(result[0]).toContain(`\x1b]8;;${url}\x07`); |
| expect(result[0]).toContain(`\x1b]8;;\x07`); |
| }); |
|
|
| it("handles named link rendered as text (url)", () => { |
| const url = "https://github.com/org/repo"; |
| |
| const line = `Click here (${url})`; |
| const result = addOsc8Hyperlinks([line], [url]); |
|
|
| |
| expect(result[0]).toContain(`\x1b]8;;${url}\x07`); |
| }); |
|
|
| it("handles multiple URLs on the same line", () => { |
| const url1 = "https://foo.com"; |
| const url2 = "https://bar.com"; |
| const line = `${url1} and ${url2}`; |
| const result = addOsc8Hyperlinks([line], [url1, url2]); |
|
|
| expect(result[0]).toContain(`\x1b]8;;${url1}\x07`); |
| expect(result[0]).toContain(`\x1b]8;;${url2}\x07`); |
| }); |
|
|
| it("does not modify lines without URL text", () => { |
| const url = "https://example.com"; |
| const lines = ["Just some text", "No URLs here"]; |
| const result = addOsc8Hyperlinks(lines, [url]); |
|
|
| expect(result).toEqual(lines); |
| }); |
|
|
| it("prefers the longest known URL when a fragment matches multiple prefixes", () => { |
| const short = "https://example.com/api/v2/users"; |
| const long = "https://example.com/api/v2/users/list"; |
| const fragment = "https://example.com/api/v2/u"; |
| const result = addOsc8Hyperlinks([fragment], [short, long]); |
| expect(result[0]).toContain(`\x1b]8;;${long}\x07${fragment}\x1b]8;;\x07`); |
| }); |
|
|
| it("handles URL split across three lines", () => { |
| const fullUrl = "https://example.com/a/very/long/path/that/keeps/going/and/going"; |
| const lines = ["https://example.com/a/very/lon", "g/path/that/keeps/going/and/g", "oing"]; |
| const result = addOsc8Hyperlinks(lines, [fullUrl]); |
|
|
| |
| for (const line of result) { |
| expect(line).toContain(`\x1b]8;;${fullUrl}\x07`); |
| expect(line).toContain(`\x1b]8;;\x07`); |
| } |
| }); |
| }); |
|
|