From 9c7a2ffbe3444a409e3151cf1a90b7e81aff1b7c Mon Sep 17 00:00:00 2001 From: binrysearch Date: Sun, 28 Jul 2024 21:57:30 +0100 Subject: [PATCH] prettier --- src/packages/dom/van.test.ts | 2606 ++++++++++++++++++++-------------- src/packages/dom/van.ts | 8 +- 2 files changed, 1505 insertions(+), 1109 deletions(-) diff --git a/src/packages/dom/van.test.ts b/src/packages/dom/van.test.ts index 90dc45198..27f46b8aa 100644 --- a/src/packages/dom/van.test.ts +++ b/src/packages/dom/van.test.ts @@ -32,7 +32,6 @@ const createHiddenDom = () => { return dom; }; - describe("van", () => { describe("tag", () => { it("should create basic tag", () => { @@ -102,253 +101,311 @@ describe("van", () => { }); it("should change connect onclick handler with state", async () => { - const dom = div() - van.add(createHiddenDom(), dom) + const dom = div(); + van.add(createHiddenDom(), dom); // TODO: fix the any type here. It should ideally be an EventListener | null - const handler = van.state((() => van.add(dom, p("Button clicked!")))) - van.add(dom, button({ onclick: handler })) - dom.querySelector("button")!.click() - expect(dom.outerHTML).toBe("

Button clicked!

") - - handler.val = () => van.add(dom, div("Button clicked!")) - await sleep(waitMsForDerivations) - dom.querySelector("button")!.click() - expect(dom.outerHTML).toBe("

Button clicked!

Button clicked!
") - - handler.val = null - await sleep(waitMsForDerivations) - dom.querySelector("button")!.click() - expect(dom.outerHTML).toBe("

Button clicked!

Button clicked!
") - }); - - it('should not change onclick handler when state is disconnected', async () => { - const dom = div() - const handler = van.state(() => van.add(dom, p("Button clicked!"))) - van.add(dom, button({ onclick: handler })) - dom.querySelector("button")!.click() - expect(dom.outerHTML).toBe("

Button clicked!

") - - handler.val = () => van.add(dom, div("Button clicked!")) - await sleep(waitMsForDerivations) - dom.querySelector("button")!.click() + const handler = van.state(( + (() => van.add(dom, p("Button clicked!"))) + )); + van.add(dom, button({ onclick: handler })); + dom.querySelector("button")!.click(); + expect(dom.outerHTML).toBe( + "

Button clicked!

" + ); + + handler.val = () => van.add(dom, div("Button clicked!")); + await sleep(waitMsForDerivations); + dom.querySelector("button")!.click(); + expect(dom.outerHTML).toBe( + "

Button clicked!

Button clicked!
" + ); + + handler.val = null; + await sleep(waitMsForDerivations); + dom.querySelector("button")!.click(); + expect(dom.outerHTML).toBe( + "

Button clicked!

Button clicked!
" + ); + }); + + it("should not change onclick handler when state is disconnected", async () => { + const dom = div(); + const handler = van.state(() => van.add(dom, p("Button clicked!"))); + van.add(dom, button({ onclick: handler })); + dom.querySelector("button")!.click(); + expect(dom.outerHTML).toBe( + "

Button clicked!

" + ); + + handler.val = () => van.add(dom, div("Button clicked!")); + await sleep(waitMsForDerivations); + dom.querySelector("button")!.click(); // The onclick handler won't change as dom is not connected to document, as a result, the

element will be added - expect(dom.outerHTML).toBe("

Button clicked!

Button clicked!

") + expect(dom.outerHTML).toBe( + "

Button clicked!

Button clicked!

" + ); }); it("should update props from a derived state", async () => { - const host = van.state("example.com") - const path = van.state("/hello") - const dom = a({ href: () => `https://${host.val}${path.val}` }, "Test Link") - van.add(createHiddenDom(), dom) - expect(dom.href).toBe("https://example.com/hello") - host.val = "vanjs.org" - path.val = "/start" - await sleep(waitMsForDerivations) - expect(dom.href).toBe("https://vanjs.org/start") - }); - - it('should not update props from a disconnected derived state', async () => { - const host = van.state("example.com") - const path = van.state("/hello") - const dom = a({ href: () => `https://${host.val}${path.val}` }, "Test Link") - expect(dom.href).toBe("https://example.com/hello") - host.val = "vanjs.org" - path.val = "/start" - await sleep(waitMsForDerivations) + const host = van.state("example.com"); + const path = van.state("/hello"); + const dom = a( + { href: () => `https://${host.val}${path.val}` }, + "Test Link" + ); + van.add(createHiddenDom(), dom); + expect(dom.href).toBe("https://example.com/hello"); + host.val = "vanjs.org"; + path.val = "/start"; + await sleep(waitMsForDerivations); + expect(dom.href).toBe("https://vanjs.org/start"); + }); + + it("should not update props from a disconnected derived state", async () => { + const host = van.state("example.com"); + const path = van.state("/hello"); + const dom = a( + { href: () => `https://${host.val}${path.val}` }, + "Test Link" + ); + expect(dom.href).toBe("https://example.com/hello"); + host.val = "vanjs.org"; + path.val = "/start"; + await sleep(waitMsForDerivations); // href won't change as dom is not connected to document - expect(dom.href).toBe("https://example.com/hello") + expect(dom.href).toBe("https://example.com/hello"); }); it("should update props when partial state", async () => { - const host = van.state("example.com") - const path = "/hello" - const dom = a({ href: () => `https://${host.val}${path}` }, "Test Link") - van.add(createHiddenDom(), dom) - expect(dom.href).toBe("https://example.com/hello") - host.val = "vanjs.org" - await sleep(waitMsForDerivations) - expect(dom.href).toBe("https://vanjs.org/hello") + const host = van.state("example.com"); + const path = "/hello"; + const dom = a({ href: () => `https://${host.val}${path}` }, "Test Link"); + van.add(createHiddenDom(), dom); + expect(dom.href).toBe("https://example.com/hello"); + host.val = "vanjs.org"; + await sleep(waitMsForDerivations); + expect(dom.href).toBe("https://vanjs.org/hello"); }); it("should not update props when partial state is disconnected", async () => { - const host = van.state("example.com") - const path = "/hello" - const dom = a({ href: () => `https://${host.val}${path}` }, "Test Link") - expect(dom.href).toBe("https://example.com/hello") - host.val = "vanjs.org" - await sleep(waitMsForDerivations) + const host = van.state("example.com"); + const path = "/hello"; + const dom = a({ href: () => `https://${host.val}${path}` }, "Test Link"); + expect(dom.href).toBe("https://example.com/hello"); + host.val = "vanjs.org"; + await sleep(waitMsForDerivations); // href won't change as dom is not connected to document - expect(dom.href).toBe("https://example.com/hello") + expect(dom.href).toBe("https://example.com/hello"); }); it("should render correctly when connected state throws an error", async () => { - const text = van.state("hello") + const text = van.state("hello"); const dom = div( div( { class: () => { - if (text.val === "fail") throw new Error() - return text.val + if (text.val === "fail") throw new Error(); + return text.val; }, "data-name": text, }, - text, + text ), div( { class: () => { - if (text.val === "fail") throw new Error() - return text.val + if (text.val === "fail") throw new Error(); + return text.val; }, "data-name": text, }, - text, - ), - ) - van.add(createHiddenDom(), dom) - expect(dom.outerHTML).toBe('
hello
hello
') + text + ) + ); + van.add(createHiddenDom(), dom); + expect(dom.outerHTML).toBe( + '
hello
hello
' + ); - text.val = "fail" - await sleep(waitMsForDerivations) + text.val = "fail"; + await sleep(waitMsForDerivations); // The binding function for `class` property throws an error. // We want to validate the `class` property won't be updated because of the error, // but other properties and child nodes are updated as usual. - expect(dom.outerHTML).toBe('
fail
fail
') + expect(dom.outerHTML).toBe( + '
fail
fail
' + ); }); - it('should render correct when disconnected state throws an error', async () => { - const text = van.state("hello") + it("should render correct when disconnected state throws an error", async () => { + const text = van.state("hello"); const dom = div( div( { class: () => { - if (text.val === "fail") throw new Error() - return text.val + if (text.val === "fail") throw new Error(); + return text.val; }, "data-name": text, }, - text, + text ), div( { class: () => { - if (text.val === "fail") throw new Error() - return text.val + if (text.val === "fail") throw new Error(); + return text.val; }, "data-name": text, }, - text, - ), - ) - expect(dom.outerHTML).toBe('
hello
hello
') + text + ) + ); + expect(dom.outerHTML).toBe( + '
hello
hello
' + ); - text.val = "fail" - await sleep(waitMsForDerivations) + text.val = "fail"; + await sleep(waitMsForDerivations); // `dom` won't change as it's not connected to document - expect(dom.outerHTML).toBe('
hello
hello
') + expect(dom.outerHTML).toBe( + '
hello
hello
' + ); }); - it('should change and trigger onclick handler when state is connected', async () => { + it("should change and trigger onclick handler when state is connected", async () => { const hiddenDom = createHiddenDom(); - const elementName = van.state("p") - van.add(hiddenDom, button({ - onclick: van.derive(() => { - const name = elementName.val - return name ? () => van.add(hiddenDom, van.tags[name]("Button clicked!")) : null - }), - })) - hiddenDom.querySelector("button")!.click() - expect(hiddenDom.innerHTML).toBe("

Button clicked!

") + const elementName = van.state("p"); + van.add( + hiddenDom, + button({ + onclick: van.derive(() => { + const name = elementName.val; + return name + ? () => van.add(hiddenDom, van.tags[name]("Button clicked!")) + : null; + }), + }) + ); + hiddenDom.querySelector("button")!.click(); + expect(hiddenDom.innerHTML).toBe( + "

Button clicked!

" + ); - elementName.val = "div" - await sleep(waitMsForDerivations) - hiddenDom.querySelector("button")!.click() - expect(hiddenDom.innerHTML).toBe("

Button clicked!

Button clicked!
") + elementName.val = "div"; + await sleep(waitMsForDerivations); + hiddenDom.querySelector("button")!.click(); + expect(hiddenDom.innerHTML).toBe( + "

Button clicked!

Button clicked!
" + ); - elementName.val = "" - await sleep(waitMsForDerivations) - hiddenDom.querySelector("button")!.click() - expect(hiddenDom.innerHTML).toBe("

Button clicked!

Button clicked!
") + elementName.val = ""; + await sleep(waitMsForDerivations); + hiddenDom.querySelector("button")!.click(); + expect(hiddenDom.innerHTML).toBe( + "

Button clicked!

Button clicked!
" + ); }); - + it("should not change onclick handler when state is disconnected", async () => { - const dom = div() - const elementName = van.state("p") - van.add(dom, button({ - onclick: van.derive(() => { - const name = elementName.val - return name ? () => van.add(dom, van.tags[name]("Button clicked!")) : null - }), - })) - dom.querySelector("button")!.click() - expect(dom.innerHTML).toBe("

Button clicked!

") + const dom = div(); + const elementName = van.state("p"); + van.add( + dom, + button({ + onclick: van.derive(() => { + const name = elementName.val; + return name + ? () => van.add(dom, van.tags[name]("Button clicked!")) + : null; + }), + }) + ); + dom.querySelector("button")!.click(); + expect(dom.innerHTML).toBe("

Button clicked!

"); - elementName.val = "div" - await sleep(waitMsForDerivations) + elementName.val = "div"; + await sleep(waitMsForDerivations); // The onclick handler won't change as `dom` is not connected to document, // as a result, the

element will be added. - dom.querySelector("button")!.click() - expect(dom.innerHTML).toBe("

Button clicked!

Button clicked!

") + dom.querySelector("button")!.click(); + expect(dom.innerHTML).toBe( + "

Button clicked!

Button clicked!

" + ); }); it("should update data attributes when state is connected", async () => { - const lineNum = van.state(1) - const dom = div({ - "data-type": "line", - "data-id": lineNum, - "data-line": () => `line=${lineNum.val}`, - }, - "This is a test line", - ) - van.add(createHiddenDom(), dom) - expect(dom.outerHTML).toBe('
This is a test line
') - - lineNum.val = 3 - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe('
This is a test line
') - }); - - it('should not update data attributes when state is disconnected', async () => { - const lineNum = van.state(1) - const dom = div({ - "data-type": "line", - "data-id": lineNum, - "data-line": () => `line=${lineNum.val}`, - }, - "This is a test line", - ) - expect(dom.outerHTML).toBe('
This is a test line
') - - lineNum.val = 3 - await sleep(waitMsForDerivations) + const lineNum = van.state(1); + const dom = div( + { + "data-type": "line", + "data-id": lineNum, + "data-line": () => `line=${lineNum.val}`, + }, + "This is a test line" + ); + van.add(createHiddenDom(), dom); + expect(dom.outerHTML).toBe( + '
This is a test line
' + ); + + lineNum.val = 3; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + '
This is a test line
' + ); + }); + + it("should not update data attributes when state is disconnected", async () => { + const lineNum = van.state(1); + const dom = div( + { + "data-type": "line", + "data-id": lineNum, + "data-line": () => `line=${lineNum.val}`, + }, + "This is a test line" + ); + expect(dom.outerHTML).toBe( + '
This is a test line
' + ); + + lineNum.val = 3; + await sleep(waitMsForDerivations); // Attributes won't change as dom is not connected to document - expect(dom.outerHTML).toBe('
This is a test line
') + expect(dom.outerHTML).toBe( + '
This is a test line
' + ); }); - it('should update readonly props when state is connected', async () => { - const form = van.state("form1") - const dom = button({ form }, "Button") - van.add(createHiddenDom(), dom) - expect(dom.outerHTML).toBe('') + it("should update readonly props when state is connected", async () => { + const form = van.state("form1"); + const dom = button({ form }, "Button"); + van.add(createHiddenDom(), dom); + expect(dom.outerHTML).toBe(''); - form.val = "form2" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe('') + form.val = "form2"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe(''); - expect(input({ list: "datalist1" }).outerHTML).toBe('') - }) + expect(input({ list: "datalist1" }).outerHTML).toBe( + '' + ); + }); - it('should not update readonly props when state is disconnected', async () => { - const form = van.state("form1") - const dom = button({ form }, "Button") - expect(dom.outerHTML).toBe('') + it("should not update readonly props when state is disconnected", async () => { + const form = van.state("form1"); + const dom = button({ form }, "Button"); + expect(dom.outerHTML).toBe(''); - form.val = "form2" - await sleep(waitMsForDerivations) + form.val = "form2"; + await sleep(waitMsForDerivations); // Attributes won't change as dom is not connected to document - expect(dom.outerHTML).toBe('') + expect(dom.outerHTML).toBe(''); - expect(input({ list: "datalist1" }).outerHTML).toBe('') + expect(input({ list: "datalist1" }).outerHTML).toBe( + '' + ); }); it("should add custom event handler", () => { @@ -378,7 +435,7 @@ describe("van", () => { ); }); - it('should add state derived custom event handler', async () => { + it("should add state derived custom event handler", async () => { const handlerType = van.state(1); const hiddenDom = createHiddenDom(); van.add( @@ -404,66 +461,72 @@ describe("van", () => { ); }); - it('should add child as connected state', async () => { + it("should add child as connected state", async () => { const hiddenDom = createHiddenDom(); - const line2 = van.state("Line 2") - const dom = div( - pre("Line 1"), - pre(line2), - pre("Line 3") - ) - van.add(hiddenDom, dom) - expect(dom.outerHTML).toBe("
Line 1
Line 2
Line 3
") + const line2 = van.state("Line 2"); + const dom = div(pre("Line 1"), pre(line2), pre("Line 3")); + van.add(hiddenDom, dom); + expect(dom.outerHTML).toBe( + "
Line 1
Line 2
Line 3
" + ); - line2.val = "Line 2: Extra Stuff" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
Line 1
Line 2: Extra Stuff
Line 3
") + line2.val = "Line 2: Extra Stuff"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "
Line 1
Line 2: Extra Stuff
Line 3
" + ); // null to remove text DOM - line2.val = null - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
Line 1
Line 3
") + line2.val = null; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "
Line 1
Line 3
" + ); // Resetting the state won't bring the text DOM back - line2.val = "Line 2" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
Line 1
Line 3
") + line2.val = "Line 2"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "
Line 1
Line 3
" + ); }); - it('should not update child when state is disconnected', async () => { - const line2 = van.state("Line 2") - const dom = div( - pre("Line 1"), - pre(line2), - pre("Line 3") - ) - expect(dom.outerHTML).toBe("
Line 1
Line 2
Line 3
") - - line2.val = "Line 2: Extra Stuff" - await sleep(waitMsForDerivations) + it("should not update child when state is disconnected", async () => { + const line2 = van.state("Line 2"); + const dom = div(pre("Line 1"), pre(line2), pre("Line 3")); + expect(dom.outerHTML).toBe( + "
Line 1
Line 2
Line 3
" + ); + + line2.val = "Line 2: Extra Stuff"; + await sleep(waitMsForDerivations); // Content won't change as dom is not connected to document - expect(dom.outerHTML).toBe("
Line 1
Line 2
Line 3
") + expect(dom.outerHTML).toBe( + "
Line 1
Line 2
Line 3
" + ); - line2.val = null - await sleep(waitMsForDerivations) + line2.val = null; + await sleep(waitMsForDerivations); // Content won't change as dom is not connected to document - expect(dom.outerHTML).toBe("
Line 1
Line 2
Line 3
") + expect(dom.outerHTML).toBe( + "
Line 1
Line 2
Line 3
" + ); }); - it('should not delete dom when child is a state', async () => { - const text = van.state("Text") - const dom = p(text) - van.add(createHiddenDom(), dom) - expect(dom.outerHTML).toBe("

Text

") - text.val = "" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

") - text.val = "Text" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Text

") + it("should not delete dom when child is a state", async () => { + const text = van.state("Text"); + const dom = p(text); + van.add(createHiddenDom(), dom); + expect(dom.outerHTML).toBe("

Text

"); + text.val = ""; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("

"); + text.val = "Text"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("

Text

"); }); - it('should create svg elements', () => { + it("should create svg elements", () => { const { circle, path, svg } = van.tags("http://www.w3.org/2000/svg"); const dom = svg( { width: "16px", viewBox: "0 0 50 50" }, @@ -503,14 +566,24 @@ describe("van", () => { ); }); - it('should create math elements', () => { - const { math, mi, mn, mo, mrow, msup } = van.tags("http://www.w3.org/1998/Math/MathML") - const dom = math(msup(mi("e"), mrow(mi("i"), mi("π"))), mo("+"), mn("1"), mo("="), mn("0")) - expect(dom.outerHTML).toBe('eiπ+1=0') - }) + it("should create math elements", () => { + const { math, mi, mn, mo, mrow, msup } = van.tags( + "http://www.w3.org/1998/Math/MathML" + ); + const dom = math( + msup(mi("e"), mrow(mi("i"), mi("π"))), + mo("+"), + mn("1"), + mo("="), + mn("0") + ); + expect(dom.outerHTML).toBe( + "eiπ+1=0" + ); + }); }); - describe('add', () => { + describe("add", () => { it("should add elements to the document", () => { const dom = ul(); expect(van.add(dom, li("Item 1"), li("Item 2"))).toBe(dom); @@ -526,30 +599,34 @@ describe("van", () => { ); }); - it('should add nested elements', () => { - const dom = ul() - expect(van.add(dom, [li("Item 1"), li("Item 2")])).toBe(dom) - expect(dom.outerHTML).toBe("
  • Item 1
  • Item 2
") + it("should add nested elements", () => { + const dom = ul(); + expect(van.add(dom, [li("Item 1"), li("Item 2")])).toBe(dom); + expect(dom.outerHTML).toBe("
  • Item 1
  • Item 2
"); }); - it('should add deeply nested elements', () => { + it("should add deeply nested elements", () => { const dom = ul(); van.add(dom, [li("Item 1"), li("Item 2")]); // Deeply nested - expect(van.add(dom, [[li("Item 3"), [li("Item 4")]], li("Item 5")])).toBe(dom) - expect(dom.outerHTML).toBe("
  • Item 1
  • Item 2
  • Item 3
  • Item 4
  • Item 5
") + expect(van.add(dom, [[li("Item 3"), [li("Item 4")]], li("Item 5")])).toBe( + dom + ); + expect(dom.outerHTML).toBe( + "
  • Item 1
  • Item 2
  • Item 3
  • Item 4
  • Item 5
" + ); }); - it('should ignore empty array', () => { - const dom = ul() + it("should ignore empty array", () => { + const dom = ul(); van.add(dom, [li("Item 1"), li("Item 2")]); // No-op if no children specified - expect(van.add(dom, [[[]]])).toBe(dom) - expect(dom.outerHTML).toBe("
  • Item 1
  • Item 2
") + expect(van.add(dom, [[[]]])).toBe(dom); + expect(dom.outerHTML).toBe("
  • Item 1
  • Item 2
"); }); - it('should ignore null or undefined children', () => { - const dom = ul() + it("should ignore null or undefined children", () => { + const dom = ul(); expect( van.add(dom, li("Item 1"), li("Item 2"), undefined, li("Item 3"), null) ).toBe(dom); @@ -570,8 +647,8 @@ describe("van", () => { ); }); - it('should ignore nested null or undefined children', () => { - const dom = ul() + it("should ignore nested null or undefined children", () => { + const dom = ul(); van.add(dom, li("Item 1"), li("Item 2"), undefined, li("Item 3"), null); van.add(dom, [li("Item 4"), li("Item 5"), undefined, li("Item 6"), null]); expect( @@ -587,618 +664,762 @@ describe("van", () => { ); }); - it('should add children as connected state', async () => { + it("should add children as connected state", async () => { const hiddenDom = createHiddenDom(); - const line2 = van.state("Line 2") - expect(van.add(hiddenDom, - pre("Line 1"), - pre(line2), - pre("Line 3") - )).toBe(hiddenDom) - expect(hiddenDom.outerHTML).toBe('') - - line2.val = "Line 2: Extra Stuff" - await sleep(waitMsForDerivations) - expect(hiddenDom.outerHTML).toBe('') + const line2 = van.state("Line 2"); + expect(van.add(hiddenDom, pre("Line 1"), pre(line2), pre("Line 3"))).toBe( + hiddenDom + ); + expect(hiddenDom.outerHTML).toBe( + '' + ); + + line2.val = "Line 2: Extra Stuff"; + await sleep(waitMsForDerivations); + expect(hiddenDom.outerHTML).toBe( + '' + ); // null to remove text DOM - line2.val = null - await sleep(waitMsForDerivations) - expect(hiddenDom.outerHTML).toBe('') + line2.val = null; + await sleep(waitMsForDerivations); + expect(hiddenDom.outerHTML).toBe( + '' + ); // Resetting the state won't bring the text DOM back - line2.val = "Line 2" - await sleep(waitMsForDerivations) - expect(hiddenDom.outerHTML).toBe('') - }); - - it('should not change children when state is disconnected', async () => { - const line2 = van.state("Line 2") - const dom = div() - expect(van.add(dom, - pre("Line 1"), - pre(line2), - pre("Line 3") - )).toBe(dom) - expect(dom.outerHTML).toBe("
Line 1
Line 2
Line 3
") - - line2.val = "Line 2: Extra Stuff" - await sleep(waitMsForDerivations) + line2.val = "Line 2"; + await sleep(waitMsForDerivations); + expect(hiddenDom.outerHTML).toBe( + '' + ); + }); + + it("should not change children when state is disconnected", async () => { + const line2 = van.state("Line 2"); + const dom = div(); + expect(van.add(dom, pre("Line 1"), pre(line2), pre("Line 3"))).toBe(dom); + expect(dom.outerHTML).toBe( + "
Line 1
Line 2
Line 3
" + ); + + line2.val = "Line 2: Extra Stuff"; + await sleep(waitMsForDerivations); // Content won't change as dom is not connected to document - expect(dom.outerHTML).toBe("
Line 1
Line 2
Line 3
") + expect(dom.outerHTML).toBe( + "
Line 1
Line 2
Line 3
" + ); - line2.val = null - await sleep(waitMsForDerivations) + line2.val = null; + await sleep(waitMsForDerivations); // Content won't change as dom is not connected to document - expect(dom.outerHTML).toBe("
Line 1
Line 2
Line 3
") + expect(dom.outerHTML).toBe( + "
Line 1
Line 2
Line 3
" + ); }); }); - describe('state', () => { - it('should return the correct oldVal and val', async () => { + describe("state", () => { + it("should return the correct oldVal and val", async () => { const hiddenDom = createHiddenDom(); - const s = van.state("State Version 1") - expect(s.val).toBe("State Version 1") - expect(s.oldVal).toBe("State Version 1") + const s = van.state("State Version 1"); + expect(s.val).toBe("State Version 1"); + expect(s.oldVal).toBe("State Version 1"); // If the state object doesn't have any bindings, we directly update the `oldVal` - s.val = "State Version 2" - expect(s.val).toBe("State Version 2") - expect(s.oldVal).toBe("State Version 2") + s.val = "State Version 2"; + expect(s.val).toBe("State Version 2"); + expect(s.oldVal).toBe("State Version 2"); - van.add(hiddenDom, s) + van.add(hiddenDom, s); // If the state object has some bindings, `oldVal` refers to its old value until DOM update completes - s.val = "State Version 3" - expect(s.val).toBe("State Version 3") - expect(s.oldVal).toBe("State Version 2") - await sleep(waitMsForDerivations) - expect(s.val).toBe("State Version 3") - expect(s.oldVal).toBe("State Version 3") + s.val = "State Version 3"; + expect(s.val).toBe("State Version 3"); + expect(s.oldVal).toBe("State Version 2"); + await sleep(waitMsForDerivations); + expect(s.val).toBe("State Version 3"); + expect(s.oldVal).toBe("State Version 3"); }); - it('should not trigger derived states when rawVal is set', async () => { + it("should not trigger derived states when rawVal is set", async () => { const hiddenDom = createHiddenDom(); - const history: number[] = [] - const a = van.state(3), b = van.state(5) - const s = van.derive(() => a.rawVal! + b.val!) - van.derive(() => history.push(a.rawVal! + b.val!)) + const history: number[] = []; + const a = van.state(3), + b = van.state(5); + const s = van.derive(() => a.rawVal! + b.val!); + van.derive(() => history.push(a.rawVal! + b.val!)); - van.add(hiddenDom, + van.add( + hiddenDom, input({ type: "text", value: () => a.rawVal! + b.val! }), p(() => a.rawVal! + b.val!) - ) + ); - await sleep(waitMsForDerivations) - expect(s.val).toBe(8) - expect(history).toStrictEqual([8]) - expect(hiddenDom.querySelector("input")!.value).toBe("8") - expect(hiddenDom.querySelector("p")!.innerHTML).toBe("8") + await sleep(waitMsForDerivations); + expect(s.val).toBe(8); + expect(history).toStrictEqual([8]); + expect(hiddenDom.querySelector("input")!.value).toBe("8"); + expect(hiddenDom.querySelector("p")!.innerHTML).toBe("8"); // Changing the `val` of `a` won't trigger the derived states, side effects, state-derived // properties and state-derived child nodes, as the value of `a` is accessed via `a.rawVal`. - ++a.val! - await sleep(waitMsForDerivations) - expect(s.val).toBe(8) - expect(history).toStrictEqual([8]) - expect(hiddenDom.querySelector("input")!.value).toBe("8") - expect(hiddenDom.querySelector("p")!.innerHTML).toBe("8") + ++a.val!; + await sleep(waitMsForDerivations); + expect(s.val).toBe(8); + expect(history).toStrictEqual([8]); + expect(hiddenDom.querySelector("input")!.value).toBe("8"); + expect(hiddenDom.querySelector("p")!.innerHTML).toBe("8"); // Changing the `val` of `b` will trigger the derived states, side effects, state-derived // properties and state-derived child nodes, as the value of `b` is accessed via `b.rawVal`. - ++b.val! - await sleep(waitMsForDerivations) - expect(s.val).toBe(10) - expect(history).toStrictEqual([8,10]) - expect(hiddenDom.querySelector("input")!.value).toBe("10") - expect(hiddenDom.querySelector("p")!.innerHTML).toBe("10") + ++b.val!; + await sleep(waitMsForDerivations); + expect(s.val).toBe(10); + expect(history).toStrictEqual([8, 10]); + expect(hiddenDom.querySelector("input")!.value).toBe("10"); + expect(hiddenDom.querySelector("p")!.innerHTML).toBe("10"); }); }); - describe('derive', () => { - it('should trigger callback when val changes', async () => { - const history: string[] = [] - const s = van.state("This") - van.derive(() => history.push(s.val!)) - expect(history).toStrictEqual(["This"]) + describe("derive", () => { + it("should trigger callback when val changes", async () => { + const history: string[] = []; + const s = van.state("This"); + van.derive(() => history.push(s.val!)); + expect(history).toStrictEqual(["This"]); - s.val = "is" - await sleep(waitMsForDerivations) - expect(history).toStrictEqual(["This","is"]); + s.val = "is"; + await sleep(waitMsForDerivations); + expect(history).toStrictEqual(["This", "is"]); - s.val = "a" - await sleep(waitMsForDerivations) - expect(history).toStrictEqual(["This","is","a"]); + s.val = "a"; + await sleep(waitMsForDerivations); + expect(history).toStrictEqual(["This", "is", "a"]); - s.val = "test" - await sleep(waitMsForDerivations) - expect(history).toStrictEqual(["This","is","a","test"]) + s.val = "test"; + await sleep(waitMsForDerivations); + expect(history).toStrictEqual(["This", "is", "a", "test"]); - s.val = "test" - await sleep(waitMsForDerivations) - expect(history).toStrictEqual(["This","is","a","test"]) + s.val = "test"; + await sleep(waitMsForDerivations); + expect(history).toStrictEqual(["This", "is", "a", "test"]); - s.val = "test2" + s.val = "test2"; // "Test2" won't be added into `history` as `s` will be set to "test3" immediately - s.val = "test3" - await sleep(waitMsForDerivations) - expect(history).toStrictEqual(["This","is","a","test","test3"]) + s.val = "test3"; + await sleep(waitMsForDerivations); + expect(history).toStrictEqual(["This", "is", "a", "test", "test3"]); }); it("should trigger derived state callback when val changes", async () => { - const numItems = van.state(0) - const items = van.derive(() => [...Array(numItems.val).keys()].map(i => `Item ${i + 1}`)) - const selectedIndex = van.derive(() => (items.val, 0)) - const selectedItem = van.derive(() => items.val![selectedIndex.val!]) - - numItems.val = 3 - await sleep(waitMsForDerivations) - expect(numItems.val).toBe(3) - expect(items.val!.join(",")).toBe("Item 1,Item 2,Item 3") - expect(selectedIndex.val).toBe(0) - expect(selectedItem.val).toBe("Item 1") - - selectedIndex.val = 2 - await sleep(waitMsForDerivations) - expect(selectedIndex.val).toBe(2) - expect(selectedItem.val).toBe("Item 3") - - numItems.val = 5 - await sleep(waitMsForDerivations) - expect(numItems.val).toBe(5) - expect(items.val!.join(",")).toBe("Item 1,Item 2,Item 3,Item 4,Item 5") - expect(selectedIndex.val).toBe(0) - expect(selectedItem.val).toBe("Item 1") - - selectedIndex.val = 3 - await sleep(waitMsForDerivations) - expect(selectedIndex.val).toBe(3) - expect(selectedItem.val).toBe("Item 4") - }); - - it('should trigger compute conditional derived state', async () => { - const cond = van.state(true) - const a = van.state(1), b = van.state(2), c = van.state(3), d = van.state(4) - let numEffectTriggered = 0 - const sum = van.derive(() => (++numEffectTriggered, cond.val ? a.val! + b.val! : c.val! + d.val!)) - - expect(sum.val).toBe(3) - expect(numEffectTriggered).toBe(1) - - a.val = 11 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(13) - expect(numEffectTriggered).toBe(2) - - b.val = 12 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(23) - expect(numEffectTriggered).toBe(3) + const numItems = van.state(0); + const items = van.derive(() => + [...Array(numItems.val).keys()].map((i) => `Item ${i + 1}`) + ); + const selectedIndex = van.derive(() => (items.val, 0)); + const selectedItem = van.derive(() => items.val![selectedIndex.val!]); + + numItems.val = 3; + await sleep(waitMsForDerivations); + expect(numItems.val).toBe(3); + expect(items.val!.join(",")).toBe("Item 1,Item 2,Item 3"); + expect(selectedIndex.val).toBe(0); + expect(selectedItem.val).toBe("Item 1"); + + selectedIndex.val = 2; + await sleep(waitMsForDerivations); + expect(selectedIndex.val).toBe(2); + expect(selectedItem.val).toBe("Item 3"); + + numItems.val = 5; + await sleep(waitMsForDerivations); + expect(numItems.val).toBe(5); + expect(items.val!.join(",")).toBe("Item 1,Item 2,Item 3,Item 4,Item 5"); + expect(selectedIndex.val).toBe(0); + expect(selectedItem.val).toBe("Item 1"); + + selectedIndex.val = 3; + await sleep(waitMsForDerivations); + expect(selectedIndex.val).toBe(3); + expect(selectedItem.val).toBe("Item 4"); + }); + + it("should trigger compute conditional derived state", async () => { + const cond = van.state(true); + const a = van.state(1), + b = van.state(2), + c = van.state(3), + d = van.state(4); + let numEffectTriggered = 0; + const sum = van.derive( + () => ( + ++numEffectTriggered, cond.val ? a.val! + b.val! : c.val! + d.val! + ) + ); + + expect(sum.val).toBe(3); + expect(numEffectTriggered).toBe(1); + + a.val = 11; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(13); + expect(numEffectTriggered).toBe(2); + + b.val = 12; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(23); + expect(numEffectTriggered).toBe(3); // Changing c or d won't triggered the effect as they're not its current dependencies - c.val = 13 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(23) - expect(numEffectTriggered).toBe(3) - - d.val = 14 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(23) - expect(numEffectTriggered).toBe(3) - - cond.val = false - await sleep(waitMsForDerivations) - expect(sum.val).toBe(27) - expect(numEffectTriggered).toBe(4) - - c.val = 23 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(37) - expect(numEffectTriggered).toBe(5) - - d.val = 24 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(47) - expect(numEffectTriggered).toBe(6) + c.val = 13; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(23); + expect(numEffectTriggered).toBe(3); + + d.val = 14; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(23); + expect(numEffectTriggered).toBe(3); + + cond.val = false; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(27); + expect(numEffectTriggered).toBe(4); + + c.val = 23; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(37); + expect(numEffectTriggered).toBe(5); + + d.val = 24; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(47); + expect(numEffectTriggered).toBe(6); // Changing a or b won't triggered the effect as they're not its current dependencies - a.val = 21 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(47) - expect(numEffectTriggered).toBe(6) + a.val = 21; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(47); + expect(numEffectTriggered).toBe(6); - b.val = 22 - await sleep(waitMsForDerivations) - expect(sum.val).toBe(47) - expect(numEffectTriggered).toBe(6) + b.val = 22; + await sleep(waitMsForDerivations); + expect(sum.val).toBe(47); + expect(numEffectTriggered).toBe(6); }); - it('should not change state when derive throws error', async () => { - const s0 = van.state(1) - const s1 = van.derive(() => s0.val! * 2) + it("should not change state when derive throws error", async () => { + const s0 = van.state(1); + const s1 = van.derive(() => s0.val! * 2); const s2 = van.derive(() => { - if (s0.val! > 1) throw new Error() - return s0.val - }) - const s3 = van.derive(() => s0.val! * s0.val!) + if (s0.val! > 1) throw new Error(); + return s0.val; + }); + const s3 = van.derive(() => s0.val! * s0.val!); - expect(s1.val).toBe(2) - expect(s2.val).toBe(1) - expect(s3.val).toBe(1) + expect(s1.val).toBe(2); + expect(s2.val).toBe(1); + expect(s3.val).toBe(1); - s0.val = 3 - await sleep(waitMsForDerivations) + s0.val = 3; + await sleep(waitMsForDerivations); // The derivation function for `s2` throws an error. // We want to validate the `val` of `s2` remains the same because of the error, // but other derived states are updated as usual. - expect(s1.val).toBe(6) - expect(s2.val).toBe(1) - expect(s3.val).toBe(9) + expect(s1.val).toBe(6); + expect(s2.val).toBe(1); + expect(s3.val).toBe(9); }); - it('should update dom when derived state changes', async () => { + it("should update dom when derived state changes", async () => { const hiddenDom = createHiddenDom(); const CheckboxCounter = () => { - const checked = van.state(false), numChecked = van.state(0) + const checked = van.state(false), + numChecked = van.state(0); van.derive(() => { - if (checked.val) ++numChecked.val! - }) + if (checked.val) ++numChecked.val!; + }); return div( - input({ type: "checkbox", checked, onclick: e => checked.val = ((e as Event).target as HTMLInputElement).checked }), - " Checked ", numChecked, " times. ", - button({ onclick: () => numChecked.val = 0 }, "Reset"), - ) - } + input({ + type: "checkbox", + checked, + onclick: (e) => + (checked.val = ((e as Event).target as HTMLInputElement).checked), + }), + " Checked ", + numChecked, + " times. ", + button({ onclick: () => (numChecked.val = 0) }, "Reset") + ); + }; - van.add(hiddenDom, CheckboxCounter()) + van.add(hiddenDom, CheckboxCounter()); - expect(hiddenDom.innerHTML).toBe('
Checked 0 times.
') + expect(hiddenDom.innerHTML).toBe( + '
Checked 0 times.
' + ); - hiddenDom.querySelector("input")!.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('
Checked 1 times.
') + hiddenDom.querySelector("input")!.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '
Checked 1 times.
' + ); - hiddenDom.querySelector("input")!.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('
Checked 1 times.
') + hiddenDom.querySelector("input")!.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '
Checked 1 times.
' + ); - hiddenDom.querySelector("input")!.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('
Checked 2 times.
') + hiddenDom.querySelector("input")!.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '
Checked 2 times.
' + ); - hiddenDom.querySelector("button")!.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('
Checked 0 times.
') - }) + hiddenDom.querySelector("button")!.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '
Checked 0 times.
' + ); + }); - it('should batch derived state updates', async () => { - const a = van.state(3), b = van.state(5) - let numDerivations = 0 + it("should batch derived state updates", async () => { + const a = van.state(3), + b = van.state(5); + let numDerivations = 0; const s = van.derive(() => { - ++numDerivations - return a.val! + b.val! - }) + ++numDerivations; + return a.val! + b.val!; + }); - expect(s.val).toBe(8) - expect(numDerivations).toBe(1) + expect(s.val).toBe(8); + expect(numDerivations).toBe(1); // Both `a` and `b` will change. `s` will only be re-derived once - ++a.val!, ++b.val! - await sleep(waitMsForDerivations) - expect(s.val).toBe(10) - expect(numDerivations).toBe(2) + ++a.val!, ++b.val!; + await sleep(waitMsForDerivations); + expect(s.val).toBe(10); + expect(numDerivations).toBe(2); // `a` will change, and then change back. No derivation will happen - ++a.val!, --a.val! - await sleep(waitMsForDerivations) - expect(s.val).toBe(10) - expect(numDerivations).toBe(2) + ++a.val!, --a.val!; + await sleep(waitMsForDerivations); + expect(s.val).toBe(10); + expect(numDerivations).toBe(2); }); - it('should batch multilayer derived state updates', async () => { + it("should batch multilayer derived state updates", async () => { const hiddenDom = createHiddenDom(); - const a = van.state(1), b = van.derive(() => a.val! * a.val!) - const c = van.derive(() => b.val! * b.val!), d = van.derive(() => c.val! * c.val!) + const a = van.state(1), + b = van.derive(() => a.val! * a.val!); + const c = van.derive(() => b.val! * b.val!), + d = van.derive(() => c.val! * c.val!); - let numSDerived = 0, numSSquaredDerived = 0 + let numSDerived = 0, + numSSquaredDerived = 0; const s = van.derive(() => { - ++numSDerived - return a.val! + b.val! + c.val! + d.val! - }) - - van.add(hiddenDom, "a = ", a, " b = ", b, " c = ", c, " d = ", d, " s = ", s, - " s^2 = ", () => { - ++numSSquaredDerived - return s.val! * s.val! + ++numSDerived; + return a.val! + b.val! + c.val! + d.val!; + }); + + van.add( + hiddenDom, + "a = ", + a, + " b = ", + b, + " c = ", + c, + " d = ", + d, + " s = ", + s, + " s^2 = ", + () => { + ++numSSquaredDerived; + return s.val! * s.val!; } - ) + ); - expect(hiddenDom.innerHTML).toBe("a = 1 b = 1 c = 1 d = 1 s = 4 s^2 = 16") - expect(numSDerived).toBe(1) - expect(numSSquaredDerived).toBe(1) + expect(hiddenDom.innerHTML).toBe( + "a = 1 b = 1 c = 1 d = 1 s = 4 s^2 = 16" + ); + expect(numSDerived).toBe(1); + expect(numSSquaredDerived).toBe(1); - ++a.val! - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe("a = 2 b = 4 c = 16 d = 256 s = 278 s^2 = 77284") + ++a.val!; + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + "a = 2 b = 4 c = 16 d = 256 s = 278 s^2 = 77284" + ); // `s` is derived 4 times, triggered by `a`, `b`, `c`, `d`, respectively. - expect(numSDerived).toBe(5) + expect(numSDerived).toBe(5); // `s^2` (the `s` derived Text node), is only derived once per one DOM update cycle. - expect(numSSquaredDerived).toBe(2) + expect(numSSquaredDerived).toBe(2); }); - it('should stop updating when there is a cycle in the derivation', async () => { - const a = van.state(1);; - const b = van.derive(() => a.val! + 1) - van.derive(() => a.val = b.val! + 1) + it("should stop updating when there is a cycle in the derivation", async () => { + const a = van.state(1); + const b = van.derive(() => a.val! + 1); + van.derive(() => (a.val = b.val! + 1)); // `a` and `b` are circular dependency. But derivations will stop after limited number of // iterations. - ++a.val! - await sleep(waitMsForDerivations) - expect(a.val).toBe(104) - expect(b.val).toBe(103) + ++a.val!; + await sleep(waitMsForDerivations); + expect(a.val).toBe(104); + expect(b.val).toBe(103); }); - it('should dynamically update dom based on derived state', async () => { + it("should dynamically update dom based on derived state", async () => { const hiddenDom = createHiddenDom(); - const verticalPlacement = van.state(false) - const button1Text = van.state("Button 1"), button2Text = van.state("Button 2"), button3Text = van.state("Button 3") - - const domFunc = () => verticalPlacement.val ? div( - div(button(button1Text)), - div(button(button2Text)), - div(button(button3Text)), - ) : div( - button(button1Text), button(button2Text), button(button3Text), - ) - expect(van.add(hiddenDom, domFunc)).toBe(hiddenDom) - - const dom = hiddenDom.firstChild - expect(dom.outerHTML).toBe("
") - button2Text.val = "Button 2: Extra" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
") - - verticalPlacement.val = true - await sleep(waitMsForDerivations) + const verticalPlacement = van.state(false); + const button1Text = van.state("Button 1"), + button2Text = van.state("Button 2"), + button3Text = van.state("Button 3"); + + const domFunc = () => + verticalPlacement.val + ? div( + div(button(button1Text)), + div(button(button2Text)), + div(button(button3Text)) + ) + : div(button(button1Text), button(button2Text), button(button3Text)); + expect(van.add(hiddenDom, domFunc)).toBe(hiddenDom); + + const dom = hiddenDom.firstChild; + expect(dom.outerHTML).toBe( + "
" + ); + button2Text.val = "Button 2: Extra"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "
" + ); + + verticalPlacement.val = true; + await sleep(waitMsForDerivations); // dom is disconnected from the document thus it won't be updated - expect(dom.outerHTML).toBe("
") - expect((hiddenDom.firstChild).outerHTML).toBe("
") - button2Text.val = "Button 2: Extra Extra" - await sleep(waitMsForDerivations) + expect(dom.outerHTML).toBe( + "
" + ); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + button2Text.val = "Button 2: Extra Extra"; + await sleep(waitMsForDerivations); // Since dom is disconnected from document, its inner button won't be reactive to state changes - expect(dom.outerHTML).toBe("
") - expect((hiddenDom.firstChild).outerHTML).toBe("
") + expect(dom.outerHTML).toBe( + "
" + ); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); }); - it('should update dom based on conditional derived state', async () => { + it("should update dom based on conditional derived state", async () => { const hiddenDom = createHiddenDom(); - const cond = van.state(true) - const button1 = van.state("Button 1"), button2 = van.state("Button 2") - const button3 = van.state("Button 3"), button4 = van.state("Button 4") - let numFuncCalled = 0 - const domFunc = () => (++numFuncCalled, cond.val ? - div(button(button1.val), button(button2.val)) : - div(button(button3.val), button(button4.val))) - expect(van.add(hiddenDom, domFunc)).toBe(hiddenDom) - - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(1) - - button1.val = "Button 1-1" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(2) - - button2.val = "Button 2-1" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(3) + const cond = van.state(true); + const button1 = van.state("Button 1"), + button2 = van.state("Button 2"); + const button3 = van.state("Button 3"), + button4 = van.state("Button 4"); + let numFuncCalled = 0; + const domFunc = () => ( + ++numFuncCalled, + cond.val + ? div(button(button1.val), button(button2.val)) + : div(button(button3.val), button(button4.val)) + ); + expect(van.add(hiddenDom, domFunc)).toBe(hiddenDom); + + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(1); + + button1.val = "Button 1-1"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(2); + + button2.val = "Button 2-1"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(3); // Changing button3 or button4 won't triggered the effect as they're not its current dependencies - button3.val = "Button 3-1" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(3) - - button4.val = "Button 4-1" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(3) - - cond.val = false - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(4) - - button3.val = "Button 3-2" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(5) - - button4.val = "Button 4-2" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(6) + button3.val = "Button 3-1"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(3); + + button4.val = "Button 4-1"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(3); + + cond.val = false; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(4); + + button3.val = "Button 3-2"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(5); + + button4.val = "Button 4-2"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(6); // Changing button1 or button2 won't triggered the effect as they're not its current dependencies - button1.val = "Button 1-2" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(6) + button1.val = "Button 1-2"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(6); - button1.val = "Button 2-2" - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe("
") - expect(numFuncCalled).toBe(6) + button1.val = "Button 2-2"; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + "
" + ); + expect(numFuncCalled).toBe(6); }); - it('should rearrange dom when state changes', async () => { + it("should rearrange dom when state changes", async () => { const hiddenDom = createHiddenDom(); - const numItems = van.state(0) - const items = van.derive(() => [...Array(numItems.val).keys()].map(i => `Item ${i + 1}`)) - const selectedIndex = van.derive(() => (items.val, 0)) + const numItems = van.state(0); + const items = van.derive(() => + [...Array(numItems.val).keys()].map((i) => `Item ${i + 1}`) + ); + const selectedIndex = van.derive(() => (items.val, 0)); const domFunc = (dom?: Element) => { // If items aren't changed, we don't need to regenerate the entire dom if (dom && items.val === items.oldVal) { const itemDoms = dom.childNodes; - (itemDoms[selectedIndex.oldVal!]).classList.remove("selected"); - (itemDoms[selectedIndex.val!]).classList.add("selected") - return dom + (itemDoms[selectedIndex.oldVal!]).classList.remove( + "selected" + ); + (itemDoms[selectedIndex.val!]).classList.add("selected"); + return dom; } return ul( - items.val!.map((item: string, i: number) => li({ class: i === selectedIndex.val ? "selected" : "" }, item)) - ) - } - van.add(hiddenDom, domFunc) + items.val!.map((item: string, i: number) => + li({ class: i === selectedIndex.val ? "selected" : "" }, item) + ) + ); + }; + van.add(hiddenDom, domFunc); - numItems.val = 3 - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe('
  • Item 1
  • Item 2
  • Item 3
') - const rootDom1stIteration = hiddenDom.firstChild + numItems.val = 3; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + '
  • Item 1
  • Item 2
  • Item 3
' + ); + const rootDom1stIteration = hiddenDom.firstChild; - selectedIndex.val = 1 - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe('
  • Item 1
  • Item 2
  • Item 3
') + selectedIndex.val = 1; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + '
  • Item 1
  • Item 2
  • Item 3
' + ); // Items aren't changed, thus we don't need to regenerate the dom - expect(hiddenDom.firstChild!).toBe(rootDom1stIteration) + expect(hiddenDom.firstChild!).toBe(rootDom1stIteration); - numItems.val = 5 - await sleep(waitMsForDerivations) + numItems.val = 5; + await sleep(waitMsForDerivations); // Items are changed, thus the dom for the list is regenerated - expect((hiddenDom.firstChild).outerHTML).toBe('
  • Item 1
  • Item 2
  • Item 3
  • Item 4
  • Item 5
') - expect(hiddenDom.firstChild !== rootDom1stIteration) + expect((hiddenDom.firstChild).outerHTML).toBe( + '
  • Item 1
  • Item 2
  • Item 3
  • Item 4
  • Item 5
' + ); + expect(hiddenDom.firstChild !== rootDom1stIteration); // rootDom1stIteration is disconnected from the document and remain unchanged - expect(rootDom1stIteration.outerHTML).toBe('
  • Item 1
  • Item 2
  • Item 3
') - const rootDom2ndIteration = hiddenDom.firstChild! + expect(rootDom1stIteration.outerHTML).toBe( + '
  • Item 1
  • Item 2
  • Item 3
' + ); + const rootDom2ndIteration = hiddenDom.firstChild!; - selectedIndex.val = 2 - await sleep(waitMsForDerivations) - expect((hiddenDom.firstChild).outerHTML).toBe('
  • Item 1
  • Item 2
  • Item 3
  • Item 4
  • Item 5
') + selectedIndex.val = 2; + await sleep(waitMsForDerivations); + expect((hiddenDom.firstChild).outerHTML).toBe( + '
  • Item 1
  • Item 2
  • Item 3
  • Item 4
  • Item 5
' + ); // Items aren't changed, thus we don't need to regenerate the dom - expect(hiddenDom.firstChild!).toBe(rootDom2ndIteration) + expect(hiddenDom.firstChild!).toBe(rootDom2ndIteration); // rootDom1stIteration won't be updated as it has already been disconnected from the document - expect(rootDom1stIteration.outerHTML).toBe('
  • Item 1
  • Item 2
  • Item 3
') + expect(rootDom1stIteration.outerHTML).toBe( + '
  • Item 1
  • Item 2
  • Item 3
' + ); }); - it('should remove dom when it returns null', async () => { + it("should remove dom when it returns null", async () => { const hiddenDom = createHiddenDom(); - const line1 = van.state("Line 1"), line2 = van.state("Line 2"), line3 = van.state("Line 3"), line4 = van.state(""), line5 = van.state(null) + const line1 = van.state("Line 1"), + line2 = van.state("Line 2"), + line3 = van.state("Line 3"), + line4 = van.state(""), + line5 = van.state(null); const dom = div( - () => line1.val === "" ? null : p(line1.val), - () => line2.val === "" ? null : p(line2.val), + () => (line1.val === "" ? null : p(line1.val)), + () => (line2.val === "" ? null : p(line2.val)), p(line3), // line4 won't appear in the DOM tree as its initial value is null - () => line4.val === "" ? null : p(line4.val), + () => (line4.val === "" ? null : p(line4.val)), // line5 won't appear in the DOM tree as its initial value is null - p(line5), - ) - van.add(hiddenDom, dom) + p(line5) + ); + van.add(hiddenDom, dom); - expect(dom.outerHTML).toBe("

Line 1

Line 2

Line 3

") + expect(dom.outerHTML).toBe( + "

Line 1

Line 2

Line 3

" + ); // Delete Line 2 - line2.val = "" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

Line 3

") + line2.val = ""; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "

Line 1

Line 3

" + ); // Deleted dom won't be brought back, even the underlying state is changed back - line2.val = "Line 2" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

Line 3

") + line2.val = "Line 2"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "

Line 1

Line 3

" + ); // Delete Line 3 - line3.val = null - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

") + line3.val = null; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("

Line 1

"); // Deleted dom won't be brought back, even the underlying state is changed back - line3.val = "Line 3" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

") - }) + line3.val = "Line 3"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("

Line 1

"); + }); - it('should remove dom when it returns undefined', async () => { + it("should remove dom when it returns undefined", async () => { const hiddenDom = createHiddenDom(); - const line1 = van.state("Line 1"), line2 = van.state("Line 2"), line3 = van.state("Line 3"), line4 = van.state(""), line5 = van.state(undefined) + const line1 = van.state("Line 1"), + line2 = van.state("Line 2"), + line3 = van.state("Line 3"), + line4 = van.state(""), + line5 = van.state(undefined); const dom = div( - () => line1.val === "" ? null : p(line1.val), - () => line2.val === "" ? null : p(line2.val), + () => (line1.val === "" ? null : p(line1.val)), + () => (line2.val === "" ? null : p(line2.val)), p(line3), // line4 won't appear in the DOM tree as its initial value is null - () => line4.val === "" ? null : p(line4.val), + () => (line4.val === "" ? null : p(line4.val)), // line5 won't appear in the DOM tree as its initial value is null - p(line5), - ) - van.add(hiddenDom, dom) + p(line5) + ); + van.add(hiddenDom, dom); - expect(dom.outerHTML).toBe("

Line 1

Line 2

Line 3

") + expect(dom.outerHTML).toBe( + "

Line 1

Line 2

Line 3

" + ); // Delete Line 2 - line2.val = "" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

Line 3

") + line2.val = ""; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "

Line 1

Line 3

" + ); // Deleted dom won't be brought back, even the underlying state is changed back - line2.val = "Line 2" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

Line 3

") + line2.val = "Line 2"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + "

Line 1

Line 3

" + ); // Delete Line 3 - line3.val = undefined - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

") + line3.val = undefined; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("

Line 1

"); // Deleted dom won't be brought back, even the underlying state is changed back - line3.val = "Line 3" - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("

Line 1

") + line3.val = "Line 3"; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("

Line 1

"); }); - it('should not remove dom when it returns 0', async () => { + it("should not remove dom when it returns 0", async () => { const hiddenDom = createHiddenDom(); - const state1 = van.state(0), state2 = van.state(1) - const dom = div(state1, () => 1 - state1.val!, state2, () => 1 - state2.val!) - van.add(hiddenDom, dom) - - expect(dom.outerHTML).toBe("
0110
") - - state1.val = 1, state2.val = 0 - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
1001
") - }) - - it('should update dom when primitive state changes', async () => { + const state1 = van.state(0), + state2 = van.state(1); + const dom = div( + state1, + () => 1 - state1.val!, + state2, + () => 1 - state2.val! + ); + van.add(hiddenDom, dom); + + expect(dom.outerHTML).toBe("
0110
"); + + (state1.val = 1), (state2.val = 0); + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("
1001
"); + }); + + it("should update dom when primitive state changes", async () => { const hiddenDom = createHiddenDom(); - const a = van.state(1), b = van.state(2), deleted = van.state(false) - const dom = div(() => deleted.val ? null : a.val! + b.val!) - expect(dom.outerHTML).toBe("
3
") - van.add(hiddenDom, dom) + const a = van.state(1), + b = van.state(2), + deleted = van.state(false); + const dom = div(() => (deleted.val ? null : a.val! + b.val!)); + expect(dom.outerHTML).toBe("
3
"); + van.add(hiddenDom, dom); - a.val = 6 - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
8
") + a.val = 6; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("
8
"); - b.val = 5 - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
11
") + b.val = 5; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("
11
"); - deleted.val = true - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
") + deleted.val = true; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("
"); // Deleted dom won't be brought back, even the underlying state is changed back - deleted.val = false - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe("
") - }) + deleted.val = false; + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe("
"); + }); - it('should not update when state is not connected', async () => { + it("should not update when state is not connected", async () => { const hiddenDom = createHiddenDom(); - const part1 = "👋Hello ", part2 = van.state("🗺️World") + const part1 = "👋Hello ", + part2 = van.state("🗺️World"); expect( van.add( @@ -1207,135 +1428,154 @@ describe("van", () => { ) ).toBe(hiddenDom); - const dom = hiddenDom.firstChild - expect(dom.textContent!).toBe("👋Hello 🗺️World, from: 👋Hello 🗺️World") - expect(hiddenDom.innerHTML).toBe("👋Hello 🗺️World, from: 👋Hello 🗺️World") + const dom = hiddenDom.firstChild; + expect(dom.textContent!).toBe("👋Hello 🗺️World, from: 👋Hello 🗺️World"); + expect(hiddenDom.innerHTML).toBe( + "👋Hello 🗺️World, from: 👋Hello 🗺️World" + ); - part2.val = "🍦VanJS" - await sleep(waitMsForDerivations) + part2.val = "🍦VanJS"; + await sleep(waitMsForDerivations); // dom is disconnected from the document thus it won't be updated - expect(dom.textContent!).toBe("👋Hello 🗺️World, from: 👋Hello 🗺️World") - expect(hiddenDom.innerHTML).toBe("👋Hello 🍦VanJS, from: 👋Hello 🗺️World") + expect(dom.textContent!).toBe("👋Hello 🗺️World, from: 👋Hello 🗺️World"); + expect(hiddenDom.innerHTML).toBe( + "👋Hello 🍦VanJS, from: 👋Hello 🗺️World" + ); }); - it('should not update dom when oldVal is referenced', async () => { + it("should not update dom when oldVal is referenced", async () => { const hiddenDom = createHiddenDom(); - const text = van.state("Old Text") + const text = van.state("Old Text"); - expect(van.add(hiddenDom, () => `From: "${text.oldVal}" to: "${text.val}"`)).toBe(hiddenDom) + expect( + van.add(hiddenDom, () => `From: "${text.oldVal}" to: "${text.val}"`) + ).toBe(hiddenDom); - const dom = hiddenDom.firstChild - expect(dom.textContent!).toBe('From: "Old Text" to: "Old Text"') - expect(hiddenDom.innerHTML).toBe('From: "Old Text" to: "Old Text"') + const dom = hiddenDom.firstChild; + expect(dom.textContent!).toBe('From: "Old Text" to: "Old Text"'); + expect(hiddenDom.innerHTML).toBe('From: "Old Text" to: "Old Text"'); - text.val = "New Text" - await sleep(waitMsForDerivations) + text.val = "New Text"; + await sleep(waitMsForDerivations); // dom is disconnected from the document thus it won't be updated - expect(dom.textContent).toBe('From: "Old Text" to: "Old Text"') - expect(hiddenDom.innerHTML).toBe('From: "Old Text" to: "New Text"') + expect(dom.textContent).toBe('From: "Old Text" to: "Old Text"'); + expect(hiddenDom.innerHTML).toBe('From: "Old Text" to: "New Text"'); }); - it('should not update when state derived children throws error', async () => { + it("should not update when state derived children throws error", async () => { const hiddenDom = createHiddenDom(); - const num = van.state(0) + const num = van.state(0); - expect(van.add(hiddenDom, - num, - () => { - if (num.val! > 0) throw new Error() - return span("ok") - }, - num - )).toBe(hiddenDom) + expect( + van.add( + hiddenDom, + num, + () => { + if (num.val! > 0) throw new Error(); + return span("ok"); + }, + num + ) + ).toBe(hiddenDom); - expect(hiddenDom.innerHTML).toBe("0ok0") + expect(hiddenDom.innerHTML).toBe("0ok0"); - num.val = 1 - await sleep(waitMsForDerivations) + num.val = 1; + await sleep(waitMsForDerivations); // The binding function 2nd child of hiddenDom throws an error. // We want to validate the 2nd child won't be updated because of the error, // but other DOM nodes are updated as usual - expect(hiddenDom.innerHTML).toBe("1ok1") + expect(hiddenDom.innerHTML).toBe("1ok1"); }); }); - describe('hydrate', () => { - it('should hydrate the given dom with the provided state', async () => { + describe("hydrate", () => { + it("should hydrate the given dom with the provided state", async () => { const hiddenDom = createHiddenDom(); const Counter = (init: number) => { - const counter = van.state(init) - return button({ "data-counter": counter, onclick: () => ++counter.val! }, - () => `Count: ${counter.val}`, - ) - } + const counter = van.state(init); + return button( + { "data-counter": counter, onclick: () => ++counter.val! }, + () => `Count: ${counter.val}` + ); + }; // Static DOM before hydration - hiddenDom.innerHTML = Counter(5).outerHTML + hiddenDom.innerHTML = Counter(5).outerHTML; // Before hydration, the counter is not reactive - hiddenDom.querySelector("button")!.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('') + hiddenDom.querySelector("button")!.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '' + ); - van.hydrate(hiddenDom.querySelector("button")!, - (dom: HTMLElement) => Counter(Number(dom.getAttribute("data-counter")))) + van.hydrate(hiddenDom.querySelector("button")!, (dom: HTMLElement) => + Counter(Number(dom.getAttribute("data-counter"))) + ); // After hydration, the counter is reactive - hiddenDom.querySelector("button")!.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('') - }); + hiddenDom.querySelector("button")!.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '' + ); + }); - it('should remove dom when it returns null', async () => { + it("should remove dom when it returns null", async () => { const hiddenDom = createHiddenDom(); // Remove the DOM node upon hydration - van.add(hiddenDom, div()) - van.hydrate(hiddenDom.querySelector("div")!, () => null) - expect(hiddenDom.innerHTML).toBe("") + van.add(hiddenDom, div()); + van.hydrate(hiddenDom.querySelector("div")!, () => null); + expect(hiddenDom.innerHTML).toBe(""); // Remove the DOM node after the state update - van.add(hiddenDom, div()) - const s = van.state(1) - van.hydrate(hiddenDom.querySelector("div"), () => s.val === 1 ? pre() : null) - expect(hiddenDom.innerHTML).toBe("
")
-      s.val = 2
-      await sleep(waitMsForDerivations)
-      expect(hiddenDom.innerHTML).toBe("")
-  })
-
-  it('should remove dom when it returns undefined', async () => {
-  const hiddenDom = createHiddenDom();
+      van.add(hiddenDom, div());
+      const s = van.state(1);
+      van.hydrate(hiddenDom.querySelector("div"), () =>
+        s.val === 1 ? pre() : null
+      );
+      expect(hiddenDom.innerHTML).toBe("
");
+      s.val = 2;
+      await sleep(waitMsForDerivations);
+      expect(hiddenDom.innerHTML).toBe("");
+    });
+
+    it("should remove dom when it returns undefined", async () => {
+      const hiddenDom = createHiddenDom();
       // Remove the DOM node upon hydration
-      van.add(hiddenDom, div())
-      van.hydrate(hiddenDom.querySelector("div")!, () => undefined)
-      expect(hiddenDom.innerHTML).toBe("")
+      van.add(hiddenDom, div());
+      van.hydrate(hiddenDom.querySelector("div")!, () => undefined);
+      expect(hiddenDom.innerHTML).toBe("");
 
       // Remove the DOM node after the state update
-      van.add(hiddenDom, div())
-      const s = van.state(1)
-      van.hydrate(hiddenDom.querySelector("div"), () => s.val === 1 ? pre() : undefined)
-      expect(hiddenDom.innerHTML).toBe("
")
-      s.val = 2
-      await sleep(waitMsForDerivations)
-      expect(hiddenDom.innerHTML).toBe("")
-  });
+      van.add(hiddenDom, div());
+      const s = van.state(1);
+      van.hydrate(hiddenDom.querySelector("div"), () =>
+        s.val === 1 ? pre() : undefined
+      );
+      expect(hiddenDom.innerHTML).toBe("
");
+      s.val = 2;
+      await sleep(waitMsForDerivations);
+      expect(hiddenDom.innerHTML).toBe("");
+    });
 
-  it('should not remove dom when it returns 0', async () => {
-  const hiddenDom = createHiddenDom();
-      van.add(hiddenDom, div(), div())
+    it("should not remove dom when it returns 0", async () => {
+      const hiddenDom = createHiddenDom();
+      van.add(hiddenDom, div(), div());
 
-      const s = van.state(0)
-      const [dom1, dom2] = hiddenDom.querySelectorAll("div")
+      const s = van.state(0);
+      const [dom1, dom2] = hiddenDom.querySelectorAll("div");
 
-      van.hydrate(dom1, (() => s.val))
-      van.hydrate(dom2, (() => 1 - s.val!))
-      expect(hiddenDom.innerHTML).toBe("01")
+      van.hydrate(dom1, (() => s.val));
+      van.hydrate(dom2, (() => 1 - s.val!));
+      expect(hiddenDom.innerHTML).toBe("01");
 
-      s.val = 1
-      await sleep(waitMsForDerivations)
-      expect(hiddenDom.innerHTML).toBe("10")
-  });
+      s.val = 1;
+      await sleep(waitMsForDerivations);
+      expect(hiddenDom.innerHTML).toBe("10");
+    });
   });
 
   describe("gc", () => {
@@ -1558,341 +1798,464 @@ describe("van", () => {
   });
 
   describe("e2e", () => {
-    it('should render Counter and update dom accordingly', async () => {
+    it("should render Counter and update dom accordingly", async () => {
       const hiddenDom = createHiddenDom();
       const Counter = () => {
-        const counter = van.state(0)
+        const counter = van.state(0);
         return div(
           div("❤️: ", counter),
           button({ onclick: () => ++counter.val! }, "👍"),
-          button({ onclick: () => --counter.val! }, "👎"),
-        )
-      }
+          button({ onclick: () => --counter.val! }, "👎")
+        );
+      };
 
-      van.add(hiddenDom, Counter())
+      van.add(hiddenDom, Counter());
 
-      expect((hiddenDom.firstChild).querySelector("div")!.innerHTML).toBe("❤️: 0")
+      expect(
+        (hiddenDom.firstChild).querySelector("div")!.innerHTML
+      ).toBe("❤️: 0");
 
-      const [incrementBtn, decrementBtn] = hiddenDom.getElementsByTagName("button")
+      const [incrementBtn, decrementBtn] =
+        hiddenDom.getElementsByTagName("button");
 
-      incrementBtn.click()
-      await sleep(waitMsForDerivations)
-      expect((hiddenDom.firstChild).querySelector("div")!.innerHTML).toBe("❤️: 1")
+      incrementBtn.click();
+      await sleep(waitMsForDerivations);
+      expect(
+        (hiddenDom.firstChild).querySelector("div")!.innerHTML
+      ).toBe("❤️: 1");
 
-      incrementBtn.click()
-      await sleep(waitMsForDerivations)
-      expect((hiddenDom.firstChild).querySelector("div")!.innerHTML).toBe("❤️: 2")
+      incrementBtn.click();
+      await sleep(waitMsForDerivations);
+      expect(
+        (hiddenDom.firstChild).querySelector("div")!.innerHTML
+      ).toBe("❤️: 2");
 
-      decrementBtn.click()
-      await sleep(waitMsForDerivations)
-      expect((hiddenDom.firstChild).querySelector("div")!.innerHTML).toBe("❤️: 1")
+      decrementBtn.click();
+      await sleep(waitMsForDerivations);
+      expect(
+        (hiddenDom.firstChild).querySelector("div")!.innerHTML
+      ).toBe("❤️: 1");
     });
 
-    it('should render ul li', () => {
+    it("should render ul li", () => {
       const List = ({ items }: { items: string[] }) =>
         ul(items.map((it: any) => li(it)));
       expect(List({ items: ["Item 1", "Item 2", "Item 3"] }).outerHTML).toBe(
         "
  • Item 1
  • Item 2
  • Item 3
" ); - }) + }); - it('should render table', () => { - const Table = ({ head, data }: - { head?: readonly string[], data: readonly (string | number)[][] }) => table( - head ? thead(tr(head.map(h => th(h)))) : [], - tbody(data.map(row => tr( - row.map(col => td(col)) - ))), - ) + it("should render table", () => { + const Table = ({ + head, + data, + }: { + head?: readonly string[]; + data: readonly (string | number)[][]; + }) => + table( + head ? thead(tr(head.map((h) => th(h)))) : [], + tbody(data.map((row) => tr(row.map((col) => td(col))))) + ); - expect(Table({ - head: ["ID", "Name", "Country"], - data: [ - [1, "John Doe", "US"], - [2, "Jane Smith", "CA"], - [3, "Bob Johnson", "AU"], - ], - }).outerHTML).toBe("
IDNameCountry
1John DoeUS
2Jane SmithCA
3Bob JohnsonAU
") - - expect(Table({ - data: [ - [1, "John Doe", "US"], - [2, "Jane Smith", "CA"], - ], - }).outerHTML).toBe("
1John DoeUS
2Jane SmithCA
") - }) - - it('should render and update dom after changing state', async () => { + expect( + Table({ + head: ["ID", "Name", "Country"], + data: [ + [1, "John Doe", "US"], + [2, "Jane Smith", "CA"], + [3, "Bob Johnson", "AU"], + ], + }).outerHTML + ).toBe( + "
IDNameCountry
1John DoeUS
2Jane SmithCA
3Bob JohnsonAU
" + ); + + expect( + Table({ + data: [ + [1, "John Doe", "US"], + [2, "Jane Smith", "CA"], + ], + }).outerHTML + ).toBe( + "
1John DoeUS
2Jane SmithCA
" + ); + }); + + it("should render and update dom after changing state", async () => { const hiddenDom = createHiddenDom(); // Create a new state object with init value 1 - const counter = van.state(1) + const counter = van.state(1); // Log whenever the value of the state is updated - van.derive(() => console.log(`Counter: ${counter.val}`)) + van.derive(() => console.log(`Counter: ${counter.val}`)); // Derived state - const counterSquared = van.derive(() => counter.val! * counter.val!) + const counterSquared = van.derive(() => counter.val! * counter.val!); // Used as a child node - const dom1 = div(counter) + const dom1 = div(counter); // Used as a property - const dom2 = input({ type: "number", value: counter, disabled: true }) + const dom2 = input({ type: "number", value: counter, disabled: true }); // Used in a state-derived property - const dom3 = div({ style: () => `font-size: ${counter.val}em;` }, "Text") + const dom3 = div({ style: () => `font-size: ${counter.val}em;` }, "Text"); // Used in a state-derived child - const dom4 = div(counter, sup(2), () => ` = ${counterSquared.val}`) + const dom4 = div(counter, sup(2), () => ` = ${counterSquared.val}`); // Button to increment the value of the state - const incrementBtn = button({ onclick: () => ++counter.val! }, "Increment") - const resetBtn = button({ onclick: () => counter.val = 1 }, "Reset") + const incrementBtn = button( + { onclick: () => ++counter.val! }, + "Increment" + ); + const resetBtn = button({ onclick: () => (counter.val = 1) }, "Reset"); - van.add(hiddenDom, incrementBtn, resetBtn, dom1, dom2, dom3, dom4) + van.add(hiddenDom, incrementBtn, resetBtn, dom1, dom2, dom3, dom4); - expect(hiddenDom.innerHTML).toBe('
1
Text
12 = 1
') - expect(dom2.value).toBe("1") + expect(hiddenDom.innerHTML).toBe( + '
1
Text
12 = 1
' + ); + expect(dom2.value).toBe("1"); - incrementBtn.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('
2
Text
22 = 4
') - expect(dom2.value).toBe("2") + incrementBtn.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '
2
Text
22 = 4
' + ); + expect(dom2.value).toBe("2"); - incrementBtn.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('
3
Text
32 = 9
') - expect(dom2.value).toBe("3") + incrementBtn.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '
3
Text
32 = 9
' + ); + expect(dom2.value).toBe("3"); - resetBtn.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('
1
Text
12 = 1
') - expect(dom2.value).toBe("1") - }) + resetBtn.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '
1
Text
12 = 1
' + ); + expect(dom2.value).toBe("1"); + }); - it('should update dom based on derived state', async () => { + it("should update dom based on derived state", async () => { const hiddenDom = createHiddenDom(); const DerivedState = () => { - const text = van.state("VanJS") - const length = van.derive(() => text.val!.length) + const text = van.state("VanJS"); + const length = van.derive(() => text.val!.length); return span( "The length of ", - input({ type: "text", value: text, oninput: (e: any) => text.val = e.target.value }), - " is ", length, ".", - ) - } + input({ + type: "text", + value: text, + oninput: (e: any) => (text.val = e.target.value), + }), + " is ", + length, + "." + ); + }; - van.add(hiddenDom, DerivedState()) - const dom = (hiddenDom.firstChild) - expect(dom.outerHTML).toBe('The length of is 5.') + van.add(hiddenDom, DerivedState()); + const dom = hiddenDom.firstChild; + expect(dom.outerHTML).toBe( + 'The length of is 5.' + ); - const inputDom = dom.querySelector("input")! - inputDom.value = "Mini-Van" - inputDom.dispatchEvent(new Event("input")) + const inputDom = dom.querySelector("input")!; + inputDom.value = "Mini-Van"; + inputDom.dispatchEvent(new Event("input")); - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe('The length of is 8.') - }) + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + 'The length of is 8.' + ); + }); - it('should update props based on state', async () => { + it("should update props based on state", async () => { const hiddenDom = createHiddenDom(); const ConnectedProps = () => { - const text = van.state("") + const text = van.state(""); return span( - input({ type: "text", value: text, oninput: (e: any) => text.val = e.target.value }), - input({ type: "text", value: text, oninput: (e: any) => text.val = e.target.value }), - ) - } - van.add(hiddenDom, ConnectedProps()) - - const [input1, input2] = hiddenDom.querySelectorAll("input") - input1.value += "123" - input1.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - expect(input1.value).toBe("123") - expect(input2.value).toBe("123") - - input2.value += "abc" - input2.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - expect(input1.value).toBe("123abc") - expect(input2.value).toBe("123abc") - }) - - it('should update css based on state', async () => { + input({ + type: "text", + value: text, + oninput: (e: any) => (text.val = e.target.value), + }), + input({ + type: "text", + value: text, + oninput: (e: any) => (text.val = e.target.value), + }) + ); + }; + van.add(hiddenDom, ConnectedProps()); + + const [input1, input2] = hiddenDom.querySelectorAll("input"); + input1.value += "123"; + input1.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + expect(input1.value).toBe("123"); + expect(input2.value).toBe("123"); + + input2.value += "abc"; + input2.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + expect(input1.value).toBe("123abc"); + expect(input2.value).toBe("123abc"); + }); + + it("should update css based on state", async () => { const hiddenDom = createHiddenDom(); const FontPreview = () => { - const size = van.state(16), color = van.state("black") + const size = van.state(16), + color = van.state("black"); return span( "Size: ", input({ - type: "range", min: 10, max: 36, value: size, - oninput: (e: any) => size.val = Number((e.target).value) + type: "range", + min: 10, + max: 36, + value: size, + oninput: (e: any) => + (size.val = Number((e.target).value)), }), " Color: ", - select({ oninput: (e: any) => color.val = (e.target).value, value: color }, - ["black", "blue", "green", "red", "brown"].map(c => option({ value: c }, c)), + select( + { + oninput: (e: any) => + (color.val = (e.target).value), + value: color, + }, + ["black", "blue", "green", "red", "brown"].map((c) => + option({ value: c }, c) + ) ), span( { class: "preview", style: () => `font-size: ${size.val}px; color: ${color.val};`, - }, " Hello 🍦VanJS"), - ) - } - van.add(hiddenDom, FontPreview()) + }, + " Hello 🍦VanJS" + ) + ); + }; + van.add(hiddenDom, FontPreview()); expect((hiddenDom.querySelector("span.preview")).style.cssText).toBe( "font-size: 16px; color: black;" ); - hiddenDom.querySelector("input")!.value = "20" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) + hiddenDom.querySelector("input")!.value = "20"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); expect((hiddenDom.querySelector("span.preview")).style.cssText).toBe( "font-size: 20px; color: black;" ); - hiddenDom.querySelector("select")!.value = "blue" - hiddenDom.querySelector("select")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) + hiddenDom.querySelector("select")!.value = "blue"; + hiddenDom.querySelector("select")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); expect((hiddenDom.querySelector("span.preview")).style.cssText).toBe( "font-size: 20px; color: blue;" ); }); - it('should bind event listener based on derived state', async () => { + it("should bind event listener based on derived state", async () => { const hiddenDom = createHiddenDom(); const Counter = () => { - const counter = van.state(0) - const action = van.state("👍") + const counter = van.state(0); + const action = van.state("👍"); return span( - "❤️ ", counter, " ", - select({ oninput: (e: any) => action.val = e.target.value, value: action }, - option({ value: "👍" }, "👍"), option({ value: "👎" }, "👎"), - ), " ", - button({ - onclick: van.derive(() => action.val === "👍" ? - () => ++counter.val! : () => --counter.val!) - }, "Run"), - ) - } + "❤️ ", + counter, + " ", + select( + { + oninput: (e: any) => (action.val = e.target.value), + value: action, + }, + option({ value: "👍" }, "👍"), + option({ value: "👎" }, "👎") + ), + " ", + button( + { + onclick: van.derive(() => + action.val === "👍" + ? () => ++counter.val! + : () => --counter.val! + ), + }, + "Run" + ) + ); + }; - van.add(hiddenDom, Counter()) - const dom = (hiddenDom.firstChild) - expect(dom.outerHTML).toBe('❤️ 0 ') + van.add(hiddenDom, Counter()); + const dom = hiddenDom.firstChild; + expect(dom.outerHTML).toBe( + '❤️ 0 ' + ); - dom.querySelector("button")!.click() - dom.querySelector("button")!.click() - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe('❤️ 2 ') + dom.querySelector("button")!.click(); + dom.querySelector("button")!.click(); + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + '❤️ 2 ' + ); - dom.querySelector("select")!.value = "👎" - dom.querySelector("select")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - dom.querySelector("button")!.click() - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe('❤️ 1 ') + dom.querySelector("select")!.value = "👎"; + dom.querySelector("select")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + dom.querySelector("button")!.click(); + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + '❤️ 1 ' + ); }); - it('should render nested ul li', async () => { + it("should render nested ul li", async () => { const hiddenDom = createHiddenDom(); const SortedList = () => { - const items = van.state("a,b,c"), sortedBy = van.state("Ascending") + const items = van.state("a,b,c"), + sortedBy = van.state("Ascending"); return span( "Comma-separated list: ", input({ - oninput: (e: any) => items.val = (e.target).value, - type: "text", value: items - }), " ", - select({ oninput: (e: any) => sortedBy.val = (e.target).value, value: sortedBy }, + oninput: (e: any) => + (items.val = (e.target).value), + type: "text", + value: items, + }), + " ", + select( + { + oninput: (e: any) => + (sortedBy.val = (e.target).value), + value: sortedBy, + }, option({ value: "Ascending" }, "Ascending"), - option({ value: "Descending" }, "Descending"), + option({ value: "Descending" }, "Descending") ), // A State-derived child node - () => sortedBy.val === "Ascending" ? - ul(items.val!.split(",").sort().map(i => li(i))) : - ul(items.val!.split(",").sort().reverse().map(i => li(i))), - ) - } - van.add(hiddenDom, SortedList()) + () => + sortedBy.val === "Ascending" + ? ul( + items + .val!.split(",") + .sort() + .map((i) => li(i)) + ) + : ul( + items + .val!.split(",") + .sort() + .reverse() + .map((i) => li(i)) + ) + ); + }; + van.add(hiddenDom, SortedList()); - hiddenDom.querySelector("input")!.value = "a,b,c,d" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) + hiddenDom.querySelector("input")!.value = "a,b,c,d"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); expect(hiddenDom.querySelector("ul")!.outerHTML).toBe( - "
  • a
  • b
  • c
  • d
") + "
  • a
  • b
  • c
  • d
" + ); - hiddenDom.querySelector("select")!.value = "Descending" - hiddenDom.querySelector("select")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) + hiddenDom.querySelector("select")!.value = "Descending"; + hiddenDom.querySelector("select")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); expect(hiddenDom.querySelector("ul")!.outerHTML).toBe( - "
  • d
  • c
  • b
  • a
") - }) + "
  • d
  • c
  • b
  • a
" + ); + }); - it('should render editable ul li', async() => { + it("should render editable ul li", async () => { const hiddenDom = createHiddenDom(); - const ListItem = ({ text }: {text: string}) => { - const deleted = van.state(false) - return () => deleted.val ? null : li( - text, - a({ onclick: () => deleted.val = true }, "❌"), - ) - } + const ListItem = ({ text }: { text: string }) => { + const deleted = van.state(false); + return () => + deleted.val + ? null + : li(text, a({ onclick: () => (deleted.val = true) }, "❌")); + }; const EditableList = () => { - const listDom = ul() - const textDom = input({ type: "text" }) + const listDom = ul(); + const textDom = input({ type: "text" }); return div( - textDom, " ", - button({ onclick: () => van.add(listDom, ListItem({ text: textDom.value })) }, "➕"), - listDom, - ) - } - van.add(hiddenDom, EditableList()) - - hiddenDom.querySelector("input")!.value = "abc" - hiddenDom.querySelector("button")!.click() - hiddenDom.querySelector("input")!.value = "123" - hiddenDom.querySelector("button")!.click() - hiddenDom.querySelector("input")!.value = "def" - hiddenDom.querySelector("button")!.click() - await sleep(waitMsForDerivations) + textDom, + " ", + button( + { + onclick: () => + van.add(listDom, ListItem({ text: textDom.value })), + }, + "➕" + ), + listDom + ); + }; + van.add(hiddenDom, EditableList()); + + hiddenDom.querySelector("input")!.value = "abc"; + hiddenDom.querySelector("button")!.click(); + hiddenDom.querySelector("input")!.value = "123"; + hiddenDom.querySelector("button")!.click(); + hiddenDom.querySelector("input")!.value = "def"; + hiddenDom.querySelector("button")!.click(); + await sleep(waitMsForDerivations); expect(hiddenDom.querySelector("ul")!.outerHTML).toBe( - "") + "" + ); { - [...hiddenDom.querySelectorAll("li")].find(e => e.innerHTML.startsWith("123"))! - .querySelector("a")!.click() - await sleep(waitMsForDerivations) + [...hiddenDom.querySelectorAll("li")] + .find((e) => e.innerHTML.startsWith("123"))! + .querySelector("a")! + .click(); + await sleep(waitMsForDerivations); expect(hiddenDom.querySelector("ul")!.outerHTML).toBe( - "") + "" + ); } { - [...hiddenDom.querySelectorAll("li")].find(e => e.innerHTML.startsWith("abc"))! - .querySelector("a")!.click() - await sleep(waitMsForDerivations) + [...hiddenDom.querySelectorAll("li")] + .find((e) => e.innerHTML.startsWith("abc"))! + .querySelector("a")! + .click(); + await sleep(waitMsForDerivations); expect(hiddenDom.querySelector("ul")!.outerHTML).toBe( - "") + "" + ); } { - [...hiddenDom.querySelectorAll("li")].find(e => e.innerHTML.startsWith("def"))! - .querySelector("a")!.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.querySelector("ul")!.outerHTML).toBe("
    ") + [...hiddenDom.querySelectorAll("li")] + .find((e) => e.innerHTML.startsWith("def"))! + .querySelector("a")! + .click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.querySelector("ul")!.outerHTML).toBe("
      "); } - }) + }); - it('should update dom based on polymorphic state', async () => { - const stateProto = Object.getPrototypeOf(van.state()) + it("should update dom based on polymorphic state", async () => { + const stateProto = Object.getPrototypeOf(van.state()); const hiddenDom = createHiddenDom(); - let numYellowButtonClicked = 0 + let numYellowButtonClicked = 0; const val = (v: T | State | (() => T)) => { - const protoOfV = Object.getPrototypeOf(v ?? 0) - if (protoOfV === stateProto) return (>v).val - if (protoOfV === Function.prototype) return (<() => T>v)() - return v - } + const protoOfV = Object.getPrototypeOf(v ?? 0); + if (protoOfV === stateProto) return (>v).val; + if (protoOfV === Function.prototype) return (<() => T>v)(); + return v; + }; const Button = ({ color, @@ -1909,150 +2272,183 @@ describe("van", () => { ); const App = () => { - const colorState = van.state("green") - const textState = van.state("Turn Red") + const colorState = van.state("green"); + const textState = van.state("Turn Red"); const turnRed = () => { - colorState.val = "red" - textState.val = "Turn Green" - onclickState.val = turnGreen - } + colorState.val = "red"; + textState.val = "Turn Green"; + onclickState.val = turnGreen; + }; const turnGreen = () => { - colorState.val = "green" - textState.val = "Turn Red" - onclickState.val = turnRed - } - const onclickState = van.state(turnRed) + colorState.val = "green"; + textState.val = "Turn Red"; + onclickState.val = turnRed; + }; + const onclickState = van.state(turnRed); - const lightness = van.state(255) + const lightness = van.state(255); return span( - Button({ color: "yellow", text: "Click Me", onclick: () => ++numYellowButtonClicked }), " ", - Button({ color: colorState, text: textState, onclick: onclickState }), " ", Button({ - color: () => `rgb(${lightness.val}, ${lightness.val}, ${lightness.val})`, - text: "Get Darker", - onclick: () => lightness.val = Math.max(lightness.val! - 10, 0), + color: "yellow", + text: "Click Me", + onclick: () => ++numYellowButtonClicked, }), - ) - } + " ", + Button({ color: colorState, text: textState, onclick: onclickState }), + " ", + Button({ + color: () => + `rgb(${lightness.val}, ${lightness.val}, ${lightness.val})`, + text: "Get Darker", + onclick: () => (lightness.val = Math.max(lightness.val! - 10, 0)), + }) + ); + }; - van.add(hiddenDom, App()) + van.add(hiddenDom, App()); - expect(hiddenDom.innerHTML).toBe(' ') - const [button1, button2, button3] = hiddenDom.querySelectorAll("button") + expect(hiddenDom.innerHTML).toBe( + ' ' + ); + const [button1, button2, button3] = hiddenDom.querySelectorAll("button"); - button1.click() - expect(numYellowButtonClicked).toBe(1) - button1.click() - expect(numYellowButtonClicked).toBe(2) + button1.click(); + expect(numYellowButtonClicked).toBe(1); + button1.click(); + expect(numYellowButtonClicked).toBe(2); - button2.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe(' ') - button2.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe(' ') + button2.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + ' ' + ); + button2.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + ' ' + ); - button3.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe(' ') - button3.click() - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe(' ') + button3.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + ' ' + ); + button3.click(); + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + ' ' + ); }); - it('should update dom based on state', async () => { + it("should update dom based on state", async () => { const hiddenDom = createHiddenDom(); const TurnBold = () => { - const vanJS = van.state("VanJS") + const vanJS = van.state("VanJS"); return span( - button({ onclick: () => vanJS.val = b("VanJS") }, "Turn Bold"), - " Welcome to ", vanJS, ". ", vanJS, " is awesome!" - ) - } + button({ onclick: () => (vanJS.val = b("VanJS")) }, "Turn Bold"), + " Welcome to ", + vanJS, + ". ", + vanJS, + " is awesome!" + ); + }; - van.add(hiddenDom, TurnBold()) - const dom = (hiddenDom.firstChild) - expect(dom.outerHTML).toBe(" Welcome to VanJS. VanJS is awesome!") + van.add(hiddenDom, TurnBold()); + const dom = hiddenDom.firstChild; + expect(dom.outerHTML).toBe( + " Welcome to VanJS. VanJS is awesome!" + ); - dom.querySelector("button")!.click() - await sleep(waitMsForDerivations) - expect(dom.outerHTML).toBe(" Welcome to . VanJS is awesome!") - }) + dom.querySelector("button")!.click(); + await sleep(waitMsForDerivations); + expect(dom.outerHTML).toBe( + " Welcome to . VanJS is awesome!" + ); + }); - it('should batch updates', async () => { + it("should batch updates", async () => { const hiddenDom = createHiddenDom(); - const name = van.state("") + const name = van.state(""); const Name1 = () => { - const numRendered = van.state(0) - return div( - () => { - ++numRendered.val! - return name.val!.trim().length === 0 ? - p("Please enter your name") : - p("Hello ", b(name)) - }, - p(i("The

      element has been rendered ", numRendered, " time(s).")), - ) - } + const numRendered = van.state(0); + return div(() => { + ++numRendered.val!; + return name.val!.trim().length === 0 + ? p("Please enter your name") + : p("Hello ", b(name)); + }, p(i("The

      element has been rendered ", numRendered, " time(s)."))); + }; const Name2 = () => { - const numRendered = van.state(0) - const isNameEmpty = van.derive(() => name.val!.trim().length === 0) - return div( - () => { - ++numRendered.val! - return isNameEmpty.val ? - p("Please enter your name") : - p("Hello ", b(name)) - }, - p(i("The

      element has been rendered ", numRendered, " time(s).")), - ) - } + const numRendered = van.state(0); + const isNameEmpty = van.derive(() => name.val!.trim().length === 0); + return div(() => { + ++numRendered.val!; + return isNameEmpty.val + ? p("Please enter your name") + : p("Hello ", b(name)); + }, p(i("The

      element has been rendered ", numRendered, " time(s)."))); + }; - van.add(hiddenDom, - p("Your name is: ", input({ type: "text", value: name, oninput: (e: any) => name.val = e.target.value })), + van.add( + hiddenDom, + p( + "Your name is: ", + input({ + type: "text", + value: name, + oninput: (e: any) => (name.val = e.target.value), + }) + ), Name1(), - Name2(), - ) - await sleep(waitMsForDerivations) + Name2() + ); + await sleep(waitMsForDerivations); expect(hiddenDom.innerHTML).toBe( '

      Your name is:

      Please enter your name

      The <p> element has been rendered 1 time(s).

      Please enter your name

      The <p> element has been rendered 1 time(s).

      ' ); - hiddenDom.querySelector("input")!.value = "T" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - hiddenDom.querySelector("input")!.value = "Ta" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - hiddenDom.querySelector("input")!.value = "Tao" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('

      Your name is:

      Hello Tao

      The <p> element has been rendered 4 time(s).

      Hello Tao

      The <p> element has been rendered 2 time(s).

      ') - - hiddenDom.querySelector("input")!.value = "" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations * 2) - expect(hiddenDom.innerHTML).toBe('

      Your name is:

      Please enter your name

      The <p> element has been rendered 5 time(s).

      Please enter your name

      The <p> element has been rendered 3 time(s).

      ') - - hiddenDom.querySelector("input")!.value = "X" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - hiddenDom.querySelector("input")!.value = "Xi" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - hiddenDom.querySelector("input")!.value = "Xin" - hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")) - await sleep(waitMsForDerivations) - - await sleep(waitMsForDerivations) - expect(hiddenDom.innerHTML).toBe('

      Your name is:

      Hello Xin

      The <p> element has been rendered 8 time(s).

      Hello Xin

      The <p> element has been rendered 4 time(s).

      ') - }) + hiddenDom.querySelector("input")!.value = "T"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + hiddenDom.querySelector("input")!.value = "Ta"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + hiddenDom.querySelector("input")!.value = "Tao"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '

      Your name is:

      Hello Tao

      The <p> element has been rendered 4 time(s).

      Hello Tao

      The <p> element has been rendered 2 time(s).

      ' + ); + + hiddenDom.querySelector("input")!.value = ""; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations * 2); + expect(hiddenDom.innerHTML).toBe( + '

      Your name is:

      Please enter your name

      The <p> element has been rendered 5 time(s).

      Please enter your name

      The <p> element has been rendered 3 time(s).

      ' + ); + + hiddenDom.querySelector("input")!.value = "X"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + hiddenDom.querySelector("input")!.value = "Xi"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + hiddenDom.querySelector("input")!.value = "Xin"; + hiddenDom.querySelector("input")!.dispatchEvent(new Event("input")); + await sleep(waitMsForDerivations); + + await sleep(waitMsForDerivations); + expect(hiddenDom.innerHTML).toBe( + '

      Your name is:

      Hello Xin

      The <p> element has been rendered 8 time(s).

      Hello Xin

      The <p> element has been rendered 4 time(s).

      ' + ); + }); it("should hydrate the given element", async () => { const stateProto = Object.getPrototypeOf(van.state()); @@ -2181,5 +2577,5 @@ describe("van", () => { expect(styledCounter.innerHTML).toBe(counterHTML(2, "🔼🔽")); expect(styledCounter !== prevStyledCounter); }); - }) + }); }); diff --git a/src/packages/dom/van.ts b/src/packages/dom/van.ts index 49a940e75..00c2182b4 100644 --- a/src/packages/dom/van.ts +++ b/src/packages/dom/van.ts @@ -452,8 +452,8 @@ const add = (dom: Element, ...children: readonly ChildDom[]): Element => { protoOfC === stateProto ? bind(() => c.val) : protoOfC === funcProto - ? bind(c) - : c; + ? bind(c) + : c; child != _undefined && dom.append(child); } return dom; @@ -494,8 +494,8 @@ const tag = (ns: string | null, name: string, ...args: any): Element => { dom.addEventListener(event, v); } : propSetter - ? propSetter.bind(dom) - : dom.setAttribute.bind(dom, k); + ? propSetter.bind(dom) + : dom.setAttribute.bind(dom, k); let protoOfV = protoOf(v ?? 0);