From f7e4eb83694aa007a492ef66c28ffbe6a2dae791 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Tue, 7 Mar 2023 12:22:34 -0800 Subject: Reorganize tests (#2332) --- test/js/workerd/html-rewriter.test.js | 303 ++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 test/js/workerd/html-rewriter.test.js (limited to 'test/js/workerd/html-rewriter.test.js') diff --git a/test/js/workerd/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js new file mode 100644 index 000000000..b5db3fde2 --- /dev/null +++ b/test/js/workerd/html-rewriter.test.js @@ -0,0 +1,303 @@ +import { describe, it, expect } from "bun:test"; +import { gcTick } from "../gc"; + +var setTimeoutAsync = (fn, delay) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + try { + resolve(fn()); + } catch (e) { + reject(e); + } + }, delay); + }); +}; + +describe("HTMLRewriter", () => { + it("HTMLRewriter: async replacement", async () => { + await gcTick(); + const res = new HTMLRewriter() + .on("div", { + async element(element) { + await setTimeoutAsync(() => { + element.setInnerContent("replace", { html: true }); + }, 5); + }, + }) + .transform(new Response("
example.com
")); + await gcTick(); + expect(await res.text()).toBe("
replace
"); + await gcTick(); + }); + + it("supports element handlers", async () => { + var rewriter = new HTMLRewriter(); + rewriter.on("div", { + element(element) { + element.setInnerContent("it worked!", { html: true }); + }, + }); + var input = new Response("
hello
"); + var output = rewriter.transform(input); + expect(await output.text()).toBe("
it worked!
"); + }); + + it("(from file) supports element handlers", async () => { + var rewriter = new HTMLRewriter(); + rewriter.on("div", { + element(element) { + element.setInnerContent("it worked!", { html: true }); + }, + }); + await Bun.write("/tmp/html-rewriter.txt.js", "
hello
"); + var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); + var output = rewriter.transform(input); + expect(await output.text()).toBe("
it worked!
"); + }); + + it("supports attribute iterator", async () => { + var rewriter = new HTMLRewriter(); + var expected = [ + ["first", ""], + ["second", "alrihgt"], + ["third", "123"], + ["fourth", "5"], + ["fifth", "helloooo"], + ]; + rewriter.on("div", { + element(element2) { + for (let attr of element2.attributes) { + const stack = expected.shift(); + expect(stack[0]).toBe(attr[0]); + expect(stack[1]).toBe(attr[1]); + } + }, + }); + var input = new Response('
hello
'); + var output = rewriter.transform(input); + expect(await output.text()).toBe('
hello
'); + expect(expected.length).toBe(0); + }); + + it("handles element specific mutations", async () => { + // prepend/append + let res = new HTMLRewriter() + .on("p", { + element(element) { + element.prepend("prepend"); + element.prepend("prepend html", { html: true }); + element.append("append"); + element.append("append html", { html: true }); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe( + [ + "

", + "prepend html", + "<span>prepend</span>", + "test", + "<span>append</span>", + "append html", + "

", + ].join(""), + ); + + // setInnerContent + res = new HTMLRewriter() + .on("p", { + element(element) { + element.setInnerContent("replace"); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe("

<span>replace</span>

"); + res = new HTMLRewriter() + .on("p", { + element(element) { + element.setInnerContent("replace", { html: true }); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe("

replace

"); + + // removeAndKeepContent + res = new HTMLRewriter() + .on("p", { + element(element) { + element.removeAndKeepContent(); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe("test"); + }); + + it("handles element class properties", async () => { + class Handler { + constructor(content) { + this.content = content; + } + + // noinspection JSUnusedGlobalSymbols + element(element) { + element.setInnerContent(this.content); + } + } + const res = new HTMLRewriter().on("p", new Handler("new")).transform(new Response("

test

")); + expect(await res.text()).toBe("

new

"); + }); + + const commentsMutationsInput = "

"; + const commentsMutationsExpected = { + beforeAfter: [ + "

", + "<span>before</span>", + "before html", + "", + "after html", + "<span>after</span>", + "

", + ].join(""), + replace: "

<span>replace</span>

", + replaceHtml: "

replace

", + remove: "

", + }; + + const commentPropertiesMacro = async func => { + const res = func(new HTMLRewriter(), comment => { + expect(comment.removed).toBe(false); + expect(comment.text).toBe("test"); + comment.text = "new"; + expect(comment.text).toBe("new"); + }).transform(new Response("

")); + expect(await res.text()).toBe("

"); + }; + + it("HTMLRewriter: handles comment properties", () => + commentPropertiesMacro((rw, comments) => { + rw.on("p", { comments }); + return rw; + })); + + it("selector tests", async () => { + const checkSelector = async (selector, input, expected) => { + const res = new HTMLRewriter() + .on(selector, { + element(element) { + element.setInnerContent("new"); + }, + }) + .transform(new Response(input)); + expect(await res.text()).toBe(expected); + }; + + await checkSelector("*", "

1

2

", "

new

new

"); + await checkSelector("p", "

1

2

", "

1

new

"); + await checkSelector( + "p:nth-child(2)", + "

1

2

3

", + "

1

new

3

", + ); + await checkSelector( + "p:first-child", + "

1

2

3

", + "

new

2

3

", + ); + await checkSelector( + "p:nth-of-type(2)", + "

1

2

3

4

5

", + "

1

2

new

4

5

", + ); + await checkSelector( + "p:first-of-type", + "

1

2

3

", + "

1

new

3

", + ); + await checkSelector( + "p:not(:first-child)", + "

1

2

3

", + "

1

new

new

", + ); + await checkSelector("p.red", '

1

2

', '

new

2

'); + await checkSelector("h1#header", '

1

2

', '

new

2

'); + await checkSelector("p[data-test]", "

1

2

", "

new

2

"); + await checkSelector( + 'p[data-test="one"]', + '

1

2

', + '

new

2

', + ); + await checkSelector( + 'p[data-test="one" i]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test="one" s]', + '

1

2

3

', + '

new

2

3

', + ); + await checkSelector( + 'p[data-test~="two"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test^="a"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test$="1"]', + '

1

2

3

', + '

new

2

new

', + ); + await checkSelector( + 'p[data-test*="b"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test|="a"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + "div span", + "

1

23
", + "

new

new3
", + ); + await checkSelector( + "div > span", + "

1

23
", + "

1

new3
", + ); + }); + + it("supports deleting innerContent", async () => { + expect( + await new HTMLRewriter() + .on("div", { + element(elem) { + // https://github.com/oven-sh/bun/issues/2323 + elem.setInnerContent(""); + }, + }) + .transform(new Response("
content
")) + .text(), + ).toEqual("
"); + }); + + it("supports deleting innerHTML", async () => { + expect( + await new HTMLRewriter() + .on("div", { + element(elem) { + // https://github.com/oven-sh/bun/issues/2323 + elem.setInnerContent("", { html: true }); + }, + }) + .transform(new Response("
content
")) + .text(), + ).toEqual("
"); + }); +}); -- cgit v1.2.3