spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import { describe, expect, it } from "vitest";2import { canonicalUrl, contentFingerprint, htmlToText, sha256, urlFingerprint } from "./hash.js";34describe("canonicalUrl", () => {5 it("lowercases the host, drops fragments, default ports, tracking params, sorts the query and trims the slash", () => {6 expect(canonicalUrl("HTTPS://WWW.Example.COM:443/Path/?utm_source=x&b=2&a=1&fbclid=z#frag")).toBe("https://www.example.com/Path/?a=1&b=2");7 expect(canonicalUrl("http://x.com:80/a/")).toBe("http://x.com/a");8 expect(canonicalUrl("https://x.com/")).toBe("https://x.com/");9 expect(canonicalUrl("https://x.com/a?ref=nav&source=rss&gclid=1")).toBe("https://x.com/a");10 expect(canonicalUrl("https://x.com/a?page=2&ref_id=7")).toBe("https://x.com/a?page=2&ref_id=7"); // ref_id is not `ref`11 expect(canonicalUrl("https://x.com:8443/a")).toBe("https://x.com:8443/a");12 });13 it("urlFingerprint is stable across equivalent forms", () => {14 expect(urlFingerprint("https://X.com/a?b=1&a=2&utm_medium=m")).toBe(urlFingerprint("https://x.com/a?a=2&b=1#x"));15 expect(urlFingerprint("https://x.com/a/")).toBe(urlFingerprint("https://x.com/a"));16 // documented: the trailing slash is only trimmed when there is no query17 expect(urlFingerprint("https://x.com/a/?q=1")).not.toBe(urlFingerprint("https://x.com/a?q=1"));18 expect(urlFingerprint("https://x.com/a")).not.toBe(urlFingerprint("https://x.com/b"));19 expect(urlFingerprint("https://x.com/a")).toHaveLength(32);20 });21});2223describe("contentFingerprint", () => {24 it("ignores comments, scripts, styles, nonces, csrf inputs and whitespace on HTML", () => {25 const a = contentFingerprint('<html><head><script nonce="n1">x()</script><style>.a{}</style></head><body data-build="1"><!-- c1 --><p>Hi there</p><input type="hidden" name="csrf" value="a"></body></html>', "text/html");26 const b = contentFingerprint('<html><head><script nonce="n2">y()</script></head><body data-build="2"><!-- c2 --><p>Hi\nthere</p><input type="hidden" name="csrf" value="b"></body></html>', "text/html");27 expect(a).toBe(b);28 expect(contentFingerprint("<p>Hi there</p>", "text/html")).not.toBe(contentFingerprint("<p>Hi here</p>", "text/html"));29 });30 it("only collapses whitespace on non-HTML bodies", () => {31 expect(contentFingerprint('{"a": 1}', "application/json")).toBe(contentFingerprint('{"a": 1}', "application/json"));32 expect(contentFingerprint("<script>x</script>a", "text/plain")).not.toBe(contentFingerprint("a", "text/plain"));33 expect(contentFingerprint("<script>x</script>a", null)).toBe(contentFingerprint("a", null)); // unknown type treated as HTML34 });35 it("sha256 is hex and deterministic", () => {36 expect(sha256("abc")).toBe("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");37 expect(sha256(Buffer.from("abc"))).toBe(sha256("abc"));38 });39});4041describe("htmlToText", () => {42 it("drops scripts/styles/comments, breaks blocks into lines and decodes entities", () => {43 // block closers become line breaks; an opening tag leaves one space (the projection is for diffing, not display)44 expect(htmlToText("<div>A & B</div><p>C ©</p><br>D")).toBe("A & B\n C ©\nD");45 expect(htmlToText("x<script>no()</script>y<style>.s{}</style><!-- c -->z")).toBe("x y z");46 expect(htmlToText("<ul><li>1</li><li>2</li></ul>")).toBe("1\n 2");47 expect(htmlToText("<tag> "q" 's'")).toBe("<tag> \"q\" 's'");48 });49});50