import { batched, EventBus, htmlEscape, markup } from "../src/runtime/utils"; import { nextMicroTick } from "./helpers"; describe("event bus behaviour", () => { test("can subscribe and be notified", () => { const bus = new EventBus(); let notified = false; bus.addEventListener("event", () => { notified = true; }); expect(notified).toBe(false); bus.trigger("event"); expect(notified).toBe(true); }); test("can unsubscribe", () => { const bus = new EventBus(); let n = 0; let cb = () => n++; bus.addEventListener("event", cb); expect(n).toBe(0); bus.trigger("event"); expect(n).toBe(1); bus.removeEventListener("event", cb); expect(n).toBe(1); bus.trigger("event"); expect(n).toBe(1); }); test("arguments are properly propagated", () => { expect.assertions(1); const bus = new EventBus(); bus.addEventListener("event", (ev: any) => expect(ev.detail).toBe("hello world")); bus.trigger("event", "hello world"); }); }); describe("batched", () => { test("callback is called only once after operations", async () => { let n = 0; let fn = batched(() => n++); expect(n).toBe(0); fn(); fn(); expect(n).toBe(0); await nextMicroTick(); expect(n).toBe(1); await nextMicroTick(); expect(n).toBe(1); }); test("calling batched function from within the callback is not treated as part of the original batch", async () => { let n = 0; let fn = batched(() => { n++; if (n === 1) { fn(); } }); expect(n).toBe(0); fn(); expect(n).toBe(0); await nextMicroTick(); // First batch expect(n).toBe(1); await nextMicroTick(); // Second batch initiated from within the callback expect(n).toBe(2); await nextMicroTick(); expect(n).toBe(2); }); }); const Markup = markup("").constructor; describe("markup", () => { test("string is flagged as safe", () => { const html = markup("Hello"); expect(html).toBeInstanceOf(Markup); }); describe("htmlEscape", () => { test("htmlEscape escapes text", () => { const res = htmlEscape("

test

"); expect(res.toString()).toBe("<p>test</p>"); expect(res).toBeInstanceOf(Markup); }); test("htmlEscape keeps html markup", () => { const res = htmlEscape(markup("

test

")); expect(res.toString()).toBe("

test

"); expect(res).toBeInstanceOf(Markup); }); test("htmlEscape produces empty string on undefined", () => { const res = htmlEscape(undefined); expect(res.toString()).toBe(""); expect(res).toBeInstanceOf(Markup); }); test("htmlEscape produces string from number", () => { const res = htmlEscape(10); expect(res.toString()).toBe("10"); expect(res).toBeInstanceOf(Markup); }); test("htmlEscape produces string from boolean", () => { const res = htmlEscape(false); expect(res.toString()).toBe("false"); expect(res).toBeInstanceOf(Markup); }); test("htmlEscape correctly escapes various links", () => { expect(htmlEscape("this is a link").toString()).toBe( "<a>this is a link</a>" ); expect(htmlEscape(`odoo`).toString()).toBe( `<a href="https://www.odoo.com">odoo<a>` ); expect(htmlEscape(`odoo`).toString()).toBe( `<a href='https://www.odoo.com'>odoo<a>` ); expect(htmlEscape("Odoo`s website").toString()).toBe( `<a href='https://www.odoo.com'>Odoo`s website<a>` ); }); test("htmlEscape doesn't escape already escaped content", () => { const res = htmlEscape("

test

"); expect(res.toString()).toBe("<p>test</p>"); expect(res).toBeInstanceOf(Markup); const res2 = htmlEscape(res); expect(res2.toString()).toBe("<p>test</p>"); expect(res2).toBeInstanceOf(Markup); expect(res2).toBe(res); }); test("htmlEscape returns markup even for only-safe text", () => { const res = htmlEscape("safe"); expect(res.toString()).toBe("safe"); expect(res).toBeInstanceOf(Markup); }); }); describe("tag function", () => { test("interpolated values are escaped", () => { const maliciousInput = ""; const html = markup`${maliciousInput}`; expect(html.toString()).toBe("<script>alert('💥💥')</script>"); expect(html).toBeInstanceOf(Markup); }); test("interpolated markups aren't escaped", () => { const shouldBeEscaped = ""; const shouldnt = markup("this is safe"); const html = markup`
${shouldBeEscaped} ${shouldnt}
`; expect(html.toString()).toBe( "
<script>alert('should be escaped')</script> this is safe
" ); expect(html).toBeInstanceOf(Markup); }); test("quotes in interpolated values are escaped", () => { const imgUrl = `lol" onerror="alert('xss')`; const html = markup``; expect(html.toString()).toBe(``); }); test("already escaped content is not escaped again", () => { const res = htmlEscape("

test

"); expect(res.toString()).toBe("<p>test</p>"); const html = markup`${res}`; expect(html.toString()).toBe("<p>test</p>"); }); }); });