/** * @file openInPreview.test.ts * @description The one link rule: which URLs the Preview takes (http(s), * never the dashboard's origin, `0.0.0.0` read as `localhost`), that a local * link goes to the session panel only when one answers, and that a * Ctrl/Cmd click keeps the browser's own tab. * @author Nguyễn Ngọc Trí Vĩ */ import { afterEach, describe, expect, it, vi } from "vitest"; import { OPEN_IN_PREVIEW_EVENT, isLoopbackUrl, normalizePreviewUrl, onLinkClick, openInPreview, type OpenInPreview, } from "../openInPreview"; const SELF = "http://localhost:4820"; describe("normalizePreviewUrl", () => { it("adds http:// to a bare host:port and reads 0.0.0.0 as localhost", () => { expect(normalizePreviewUrl("localhost:3000", SELF)).toBe("http://localhost:3000/"); expect(normalizePreviewUrl("http://0.0.0.0:5173/a", SELF)).toBe("http://localhost:5173/a"); }); it("refuses the dashboard's own origin, other schemes and credentials", () => { expect(normalizePreviewUrl("http://localhost:4820/settings", SELF)).toBeNull(); expect(normalizePreviewUrl("javascript:alert(1)", SELF)).toBeNull(); expect(normalizePreviewUrl("file:///etc/passwd", SELF)).toBeNull(); expect(normalizePreviewUrl("http://u:p@localhost:3000", SELF)).toBeNull(); expect(normalizePreviewUrl(" ", SELF)).toBeNull(); }); it("accepts an external https page", () => { expect(normalizePreviewUrl("https://example.com/x", SELF)).toBe("https://example.com/x"); }); }); describe("isLoopbackUrl", () => { it("is true only for this machine's hosts", () => { expect(isLoopbackUrl("http://localhost:3000")).toBe(true); expect(isLoopbackUrl("http://127.0.0.1:8080/x")).toBe(true); expect(isLoopbackUrl("http://[::1]:3000")).toBe(true); expect(isLoopbackUrl("https://github.com")).toBe(false); expect(isLoopbackUrl("not a url")).toBe(false); }); }); describe("openInPreview", () => { let off: (() => void) | null = null; afterEach(() => off?.()); const listen = (take: boolean) => { const seen: string[] = []; const fn = (e: Event) => { const d = (e as CustomEvent).detail; seen.push(d.url); if (take) d.handled = true; }; window.addEventListener(OPEN_IN_PREVIEW_EVENT, fn); off = () => window.removeEventListener(OPEN_IN_PREVIEW_EVENT, fn); return seen; }; it("hands a local page to a listening panel", () => { const seen = listen(true); expect(openInPreview("http://localhost:3000/login")).toBe(true); expect(seen).toEqual(["http://localhost:3000/login"]); }); it("falls back when no panel takes it, and never sends an external page", () => { const seen = listen(false); expect(openInPreview("http://localhost:3000")).toBe(false); expect(openInPreview("https://github.com/login")).toBe(false); expect(seen).toEqual(["http://localhost:3000/"]); }); it("leaves a Ctrl/Cmd click to the browser", () => { listen(true); const preventDefault = vi.fn(); const base = { ctrlKey: false, metaKey: false, shiftKey: false, button: 0, preventDefault }; onLinkClick({ ...base, ctrlKey: true }, "http://localhost:3000"); expect(preventDefault).not.toHaveBeenCalled(); onLinkClick(base, "http://localhost:3000"); expect(preventDefault).toHaveBeenCalledTimes(1); }); });