// The escape every view depends on, and the length bound on a displayed // token symbol. Both were added for #307, where a token whose symbol() // returned an '; describe("escapeHtml", () => { test("escapes all five characters, quotes included", () => { expect(escapeHtml("&<>\"'")).toBe("&<>"'"); }); // The regression this function was rewritten for. The previous // implementation round-tripped through a detached div's textContent, // and an HTML text node serializes a quote as itself — so a value with // a quote in it broke straight out of data-copy="..." and href="...". test("escapes quotes, which the textContent round trip did not", () => { expect(escapeHtml('a"b')).toBe("a"b"); expect(escapeHtml("a'b")).toBe("a'b"); }); test("does not double-escape an ampersand it just introduced", () => { expect(escapeHtml("<")).toBe("&lt;"); expect(escapeHtml("&")).toBe("&amp;"); }); test("leaves a string with nothing to escape untouched", () => { expect(escapeHtml("USDC")).toBe("USDC"); expect(escapeHtml("")).toBe(""); }); test("renders the hostile symbol inert", () => { const out = escapeHtml(HOSTILE_SYMBOL); expect(out).not.toContain("<"); expect(out).not.toContain(">"); expect(out).not.toContain('"'); expect(out).toContain("<iframe"); }); // A quoted attribute is broken out of by a quote, a bare one by a // space; both are closed here. Asserted as a whole attribute rather // than character by character, because it is the attribute that has to // survive, not the escape table. test("a value carrying a quote stays inside its attribute", () => { const evil = '" onload="alert(1)'; const attr = `data-copy="${escapeHtml(evil)}"`; expect(attr).toBe('data-copy="" onload="alert(1)"'); expect(attr.split('"').length - 1).toBe(2); }); test("null and undefined render as nothing rather than as words", () => { expect(escapeHtml(null)).toBe(""); expect(escapeHtml(undefined)).toBe(""); }); test("coerces a non-string without losing the escape", () => { expect(escapeHtml(42)).toBe("42"); expect(escapeHtml({ toString: () => "" })).toBe("<b>"); }); }); describe("displaySymbol", () => { test("passes every symbol in the bundled list through unchanged", () => { const { TOKENS } = require("../src/shared/tokenList"); for (const t of TOKENS) { expect([t.address, displaySymbol(t.symbol)]).toEqual([ t.address, t.symbol, ]); } }); test("caps an over-long symbol and marks it as truncated", () => { const long = "A".repeat(4096); const out = displaySymbol(long); expect(out.length).toBe(MAX_SYMBOL_LENGTH); expect(out.endsWith("…")).toBe(true); }); test("keeps a symbol of exactly the cap intact", () => { const exact = "A".repeat(MAX_SYMBOL_LENGTH); expect(displaySymbol(exact)).toBe(exact); }); test("substitutes a placeholder for an absent symbol", () => { expect(displaySymbol("")).toBe(UNKNOWN_SYMBOL); expect(displaySymbol(null)).toBe(UNKNOWN_SYMBOL); expect(displaySymbol(undefined)).toBe(UNKNOWN_SYMBOL); }); // The cap is a layout bound and nothing more: it must not be mistaken // for the thing that makes a symbol safe to render. A short hostile // symbol passes through it untouched, and is inert only because the // caller escapes it afterwards. test("does not sanitize — a short markup symbol survives it verbatim", () => { expect(displaySymbol("")).toBe(""); expect(escapeHtml(displaySymbol(""))).toBe( "<img src=x>", ); }); });