import { expect } from "@std/expect"; import { LIVE_PAGE_UPDATE, reconcileMain, updateIcon, updateMain, } from "./live-page-client.ts"; // The `
` of a page holding `html`. function mainOf(html: string): Element { return new DOMParser().parseFromString(`${html}`, "text/html") .querySelector("main")!; } Deno.test("a rendering changing one part of main replaces only that part", () => { const main = mainOf( "
1h ago
a

kept

", ); const page = main.ownerDocument; const rows = page.getElementById("rows"); const around = page.getElementById("around"); expect(reconcileMain( main, mainOf( "
2h ago
a
", ), )).toBe(true); expect(page.querySelector("main")).toBe(main); expect(page.getElementById("rows")).toBe(rows); expect(page.getElementById("around")).toBe(around); expect(page.getElementById("top")?.textContent).toBe("2h ago"); }); Deno.test("an unchanged rendering leaves main in place", () => { const main = mainOf("
same
"); expect(reconcileMain(main, mainOf("
same
"))).toBe(false); expect(main.ownerDocument.querySelector("main")).toBe(main); }); Deno.test("a rendering with other parts, or other text between them, replaces main whole", () => { for ( const next of [ "

one

two

", "
after

one

", ] ) { const main = mainOf("
before

one

"); const page = main.ownerDocument; expect(reconcileMain(main, mainOf(next))).toBe(true); expect(page.querySelector("main")).not.toBe(main); expect(page.querySelector("main")?.outerHTML).toBe(next); } }); Deno.test("the header's age changing leaves focus on the link beside it", () => { const main = document.createElement("main"); main.innerHTML = `
← dashboard1h ago
`; document.body.append(main); try { const back = main.querySelector("a")!; back.focus(); expect(reconcileMain( main, mainOf( `
`, ), )).toBe(true); expect(document.activeElement).toBe(back); expect(main.querySelector("span")?.textContent).toBe("2h ago"); } finally { main.remove(); } }); Deno.test("an update is announced with the fresh rendering, which the page may arrange first", () => { const main = document.createElement("main"); // The page shows its list in the reverse of the order it is served in. main.innerHTML = `
  1. b
  2. 1h
`; document.body.append(main); const kept = main.querySelector("#b"); const reverse = (event: Event) => { if (!(event instanceof CustomEvent)) return; const list = event.detail.querySelector("ol"); list.append(...[...list.children].reverse()); }; document.addEventListener(LIVE_PAGE_UPDATE, reverse); try { expect(updateMain( main, mainOf(`
  1. 2h
  2. b
`), )).toBe(true); expect([...main.querySelectorAll("li")].map((item) => item.id)) .toEqual(["b", "a"]); expect(main.querySelector("#b")).toBe(kept); expect(main.querySelector("#a")?.textContent).toBe("2h"); } finally { document.removeEventListener(LIVE_PAGE_UPDATE, reverse); main.remove(); } }); Deno.test("a rendering's favicon image replaces the page's", () => { const rendering = (href: string) => new DOMParser().parseFromString( `
`, "text/html", ); const page = rendering("data:,"); const icon = page.querySelector('link[rel="icon"]'); for (const href of ["/good.png", "/bad.png", "data:,"]) { updateIcon(page, rendering(href)); expect(page.querySelector('link[rel="icon"]')).toBe(icon); expect(icon?.getAttribute("href")).toBe(href); } // A rendering with no favicon leaves the page's as it is. updateIcon(page, new DOMParser().parseFromString("
", "text/html")); expect(icon?.getAttribute("href")).toBe("data:,"); });