![JAR search and dependency download from the Maven repository](/logo.png)
package.test.unit.core.ts Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of snabbdom Show documentation
Show all versions of snabbdom Show documentation
A virtual DOM library with focus on simplicity, modularity, powerful features and performance.
The newest version!
import { assert } from "chai";
import shuffle from "lodash.shuffle";
import {
init,
classModule,
propsModule,
styleModule,
eventListenersModule,
h,
toVNode,
vnode,
VNode,
htmlDomApi,
CreateHook,
InsertHook,
PrePatchHook,
RemoveHook,
InitHook,
DestroyHook,
UpdateHook,
Key,
} from "../../src/index";
const hasSvgClassList = "classList" in SVGElement.prototype;
const patch = init([classModule, propsModule, eventListenersModule]);
function prop(name: string) {
return function (obj: { [index: string]: T }) {
return obj[name];
};
}
function map(fn: any, list: any[]) {
const ret = [];
for (let i = 0; i < list.length; ++i) {
ret[i] = fn(list[i]);
}
return ret;
}
const inner = prop("innerHTML");
describe("snabbdom", function () {
let elm: any, vnode0: any;
beforeEach(function () {
elm = document.createElement("div");
vnode0 = elm;
});
describe("hyperscript", function () {
it("can create vnode with proper tag", function () {
assert.strictEqual(h("div").sel, "div");
assert.strictEqual(h("a").sel, "a");
});
it("can create vnode with children", function () {
const vnode = h("div", [h("span#hello"), h("b.world")]);
assert.strictEqual(vnode.sel, "div");
const children = vnode.children as [VNode, VNode];
assert.strictEqual(children[0].sel, "span#hello");
assert.strictEqual(children[1].sel, "b.world");
});
it("can create vnode with one child vnode", function () {
const vnode = h("div", h("span#hello"));
assert.strictEqual(vnode.sel, "div");
const children = vnode.children as [VNode];
assert.strictEqual(children[0].sel, "span#hello");
});
it("can create vnode with props and one child vnode", function () {
const vnode = h("div", {}, h("span#hello"));
assert.strictEqual(vnode.sel, "div");
const children = vnode.children as [VNode];
assert.strictEqual(children[0].sel, "span#hello");
});
it("can create vnode with text content", function () {
const vnode = h("a", ["I am a string"]);
const children = vnode.children as [VNode];
assert.strictEqual(children[0].text, "I am a string");
});
it("can create vnode with text content in string", function () {
const vnode = h("a", "I am a string");
assert.strictEqual(vnode.text, "I am a string");
});
it("can create vnode with props and text content in string", function () {
const vnode = h("a", {}, "I am a string");
assert.strictEqual(vnode.text, "I am a string");
});
it("can create vnode with null props", function () {
let vnode = h("a", null);
assert.deepEqual(vnode.data, {});
vnode = h("a", null, ["I am a string"]);
const children = vnode.children as [VNode];
assert.strictEqual(children[0].text, "I am a string");
});
it("can create vnode for comment", function () {
const vnode = h("!", "test");
assert.strictEqual(vnode.sel, "!");
assert.strictEqual(vnode.text, "test");
});
});
describe("created element", function () {
it("has tag", function () {
elm = patch(vnode0, h("div")).elm;
assert.strictEqual(elm.tagName, "DIV");
});
it("has different tag and id", function () {
const elm = document.createElement("div");
vnode0.appendChild(elm);
const vnode1 = h("span#id");
const patched = patch(elm, vnode1).elm as HTMLSpanElement;
assert.strictEqual(patched.tagName, "SPAN");
assert.strictEqual(patched.id, "id");
});
it("has id", function () {
elm = patch(vnode0, h("div", [h("div#unique")])).elm;
assert.strictEqual(elm.firstChild.id, "unique");
});
it("has correct namespace", function () {
const SVGNamespace = "http://www.w3.org/2000/svg";
const XHTMLNamespace = "http://www.w3.org/1999/xhtml";
elm = patch(vnode0, h("div", [h("div", { ns: SVGNamespace })])).elm;
assert.strictEqual(elm.firstChild.namespaceURI, SVGNamespace);
// verify that svg tag automatically gets svg namespace
elm = patch(
vnode0,
h("svg", [
h("foreignObject", [h("div", ["I am HTML embedded in SVG"])]),
])
).elm;
assert.strictEqual(elm.namespaceURI, SVGNamespace);
assert.strictEqual(elm.firstChild.namespaceURI, SVGNamespace);
assert.strictEqual(
elm.firstChild.firstChild.namespaceURI,
XHTMLNamespace
);
// verify that svg tag with extra selectors gets svg namespace
elm = patch(vnode0, h("svg#some-id")).elm;
assert.strictEqual(elm.namespaceURI, SVGNamespace);
// verify that non-svg tag beginning with 'svg' does NOT get namespace
elm = patch(vnode0, h("svg-custom-el")).elm;
assert.notStrictEqual(elm.namespaceURI, SVGNamespace);
});
it("receives classes in selector", function () {
elm = patch(vnode0, h("div", [h("i.am.a.class")])).elm;
assert(elm.firstChild.classList.contains("am"));
assert(elm.firstChild.classList.contains("a"));
assert(elm.firstChild.classList.contains("class"));
});
it("receives classes in class property", function () {
elm = patch(
vnode0,
h("i", { class: { am: true, a: true, class: true, not: false } })
).elm;
assert(elm.classList.contains("am"));
assert(elm.classList.contains("a"));
assert(elm.classList.contains("class"));
assert(!elm.classList.contains("not"));
});
it("receives classes in selector when namespaced", function () {
if (!hasSvgClassList) {
this.skip();
} else {
elm = patch(vnode0, h("svg", [h("g.am.a.class.too")])).elm;
assert(elm.firstChild.classList.contains("am"));
assert(elm.firstChild.classList.contains("a"));
assert(elm.firstChild.classList.contains("class"));
}
});
it("receives classes in class property when namespaced", function () {
if (!hasSvgClassList) {
this.skip();
} else {
elm = patch(
vnode0,
h("svg", [
h("g", {
class: { am: true, a: true, class: true, not: false, too: true },
}),
])
).elm;
assert(elm.firstChild.classList.contains("am"));
assert(elm.firstChild.classList.contains("a"));
assert(elm.firstChild.classList.contains("class"));
assert(!elm.firstChild.classList.contains("not"));
}
});
it("handles classes from both selector and property", function () {
elm = patch(vnode0, h("div", [h("i.has", { class: { classes: true } })]))
.elm;
assert(elm.firstChild.classList.contains("has"), "has `has` class");
assert(
elm.firstChild.classList.contains("classes"),
"has `classes` class"
);
});
it("can create elements with text content", function () {
elm = patch(vnode0, h("div", ["I am a string"])).elm;
assert.strictEqual(elm.innerHTML, "I am a string");
});
it("can create elements with span and text content", function () {
elm = patch(vnode0, h("a", [h("span"), "I am a string"])).elm;
assert.strictEqual(elm.childNodes[0].tagName, "SPAN");
assert.strictEqual(elm.childNodes[1].textContent, "I am a string");
});
it("can create elements with props", function () {
elm = patch(vnode0, h("a", { props: { src: "http://localhost/" } })).elm;
assert.strictEqual(elm.src, "http://localhost/");
});
it("can create an element created inside an iframe", function (done) {
// Only run if srcdoc is supported.
const frame = document.createElement("iframe");
if (typeof frame.srcdoc !== "undefined") {
frame.srcdoc = "Thing 1";
frame.onload = function () {
const div0 = frame.contentDocument!.body.querySelector(
"div"
) as HTMLDivElement;
patch(div0, h("div", "Thing 2"));
const div1 = frame.contentDocument!.body.querySelector(
"div"
) as HTMLDivElement;
assert.strictEqual(div1.textContent, "Thing 2");
frame.remove();
done();
};
document.body.appendChild(frame);
} else {
done();
}
});
it("is a patch of the root element", function () {
const elmWithIdAndClass = document.createElement("div");
elmWithIdAndClass.id = "id";
elmWithIdAndClass.className = "class";
const vnode1 = h("div#id.class", [h("span", "Hi")]);
elm = patch(elmWithIdAndClass, vnode1).elm;
assert.strictEqual(elm, elmWithIdAndClass);
assert.strictEqual(elm.tagName, "DIV");
assert.strictEqual(elm.id, "id");
assert.strictEqual(elm.className, "class");
});
it("can create comments", function () {
elm = patch(vnode0, h("!", "test")).elm;
assert.strictEqual(elm.nodeType, document.COMMENT_NODE);
assert.strictEqual(elm.textContent, "test");
});
});
describe("patching an element", function () {
it("changes the elements classes", function () {
const vnode1 = h("i", { class: { i: true, am: true, horse: true } });
const vnode2 = h("i", { class: { i: true, am: true, horse: false } });
patch(vnode0, vnode1);
elm = patch(vnode1, vnode2).elm;
assert(elm.classList.contains("i"));
assert(elm.classList.contains("am"));
assert(!elm.classList.contains("horse"));
});
it("changes classes in selector", function () {
const vnode1 = h("i", { class: { i: true, am: true, horse: true } });
const vnode2 = h("i", { class: { i: true, am: true, horse: false } });
patch(vnode0, vnode1);
elm = patch(vnode1, vnode2).elm;
assert(elm.classList.contains("i"));
assert(elm.classList.contains("am"));
assert(!elm.classList.contains("horse"));
});
it("preserves memoized classes", function () {
const cachedClass = { i: true, am: true, horse: false };
const vnode1 = h("i", { class: cachedClass });
const vnode2 = h("i", { class: cachedClass });
elm = patch(vnode0, vnode1).elm;
assert(elm.classList.contains("i"));
assert(elm.classList.contains("am"));
assert(!elm.classList.contains("horse"));
elm = patch(vnode1, vnode2).elm;
assert(elm.classList.contains("i"));
assert(elm.classList.contains("am"));
assert(!elm.classList.contains("horse"));
});
it("removes missing classes", function () {
const vnode1 = h("i", { class: { i: true, am: true, horse: true } });
const vnode2 = h("i", { class: { i: true, am: true } });
patch(vnode0, vnode1);
elm = patch(vnode1, vnode2).elm;
assert(elm.classList.contains("i"));
assert(elm.classList.contains("am"));
assert(!elm.classList.contains("horse"));
});
it("changes an elements props", function () {
const vnode1 = h("a", { props: { src: "http://other/" } });
const vnode2 = h("a", { props: { src: "http://localhost/" } });
patch(vnode0, vnode1);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.src, "http://localhost/");
});
it("can set prop value to `0`", function () {
const patch = init([propsModule, styleModule]);
const view = (scrollTop: number) =>
h(
"div",
{
style: { height: "100px", overflowY: "scroll" },
props: { scrollTop },
},
[h("div", { style: { height: "200px" } })]
);
const vnode1 = view(0);
const mountPoint = document.body.appendChild(
document.createElement("div")
);
const { elm } = patch(mountPoint, vnode1);
if (!(elm instanceof HTMLDivElement)) throw new Error();
assert.strictEqual(elm.scrollTop, 0);
const vnode2 = view(20);
patch(vnode1, vnode2);
assert.isAtLeast(elm.scrollTop, 18);
assert.isAtMost(elm.scrollTop, 20);
const vnode3 = view(0);
patch(vnode2, vnode3);
assert.strictEqual(elm.scrollTop, 0);
document.body.removeChild(mountPoint);
});
it("can set prop value to empty string", function () {
const vnode1 = h("p", { props: { textContent: "foo" } });
const { elm } = patch(vnode0, vnode1);
if (!(elm instanceof HTMLParagraphElement)) throw new Error();
assert.strictEqual(elm.textContent, "foo");
const vnode2 = h("p", { props: { textContent: "" } });
patch(vnode1, vnode2);
assert.strictEqual(elm.textContent, "");
});
it("preserves memoized props", function () {
const cachedProps = { src: "http://other/" };
const vnode1 = h("a", { props: cachedProps });
const vnode2 = h("a", { props: cachedProps });
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.src, "http://other/");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.src, "http://other/");
});
it("removes custom props", function () {
const vnode1 = h("a", { props: { src: "http://other/" } });
const vnode2 = h("a");
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(elm.src, undefined);
});
it("cannot remove native props", function () {
const vnode1 = h("a", { props: { href: "http://example.com/" } });
const vnode2 = h("a");
const { elm: elm1 } = patch(vnode0, vnode1);
if (!(elm1 instanceof HTMLAnchorElement)) throw new Error();
assert.strictEqual(elm1.href, "http://example.com/");
const { elm: elm2 } = patch(vnode1, vnode2);
if (!(elm2 instanceof HTMLAnchorElement)) throw new Error();
assert.strictEqual(elm2.href, "http://example.com/");
});
it("does not delete custom props", function () {
const vnode1 = h("p", { props: { a: "foo" } });
const vnode2 = h("p");
const { elm } = patch(vnode0, vnode1);
if (!(elm instanceof HTMLParagraphElement)) throw new Error();
assert.strictEqual((elm as any).a, "foo");
patch(vnode1, vnode2);
assert.strictEqual((elm as any).a, "foo");
});
describe("custom elements", function () {
if ("customElements" in window) {
describe("customized built-in element", function () {
const isSafari = /^((?!chrome|android).)*safari/i.test(
navigator.userAgent
);
if (!isSafari) {
class A extends HTMLParagraphElement {}
class B extends HTMLParagraphElement {}
before(function () {
if ("customElements" in window) {
customElements.define("p-a", A, { extends: "p" });
customElements.define("p-b", B, { extends: "p" });
}
});
it("can create custom elements", function () {
if ("customElements" in window) {
const vnode1 = h("p", { is: "p-a" });
elm = patch(vnode0, vnode1).elm;
assert(elm instanceof A);
} else {
this.skip();
}
});
it("handles changing is attribute", function () {
const vnode1 = h("p", { is: "p-a" });
const vnode2 = h("p", { is: "p-b" });
elm = patch(vnode0, vnode1).elm;
assert(elm instanceof A);
elm = patch(vnode1, vnode2).elm;
assert(elm instanceof B);
});
} else {
it.skip("safari does not support customized built-in elements", () => {
assert(false);
});
}
});
} else {
it.skip("browser does not support custom elements", () => {
assert(false);
});
}
});
describe("using toVNode()", function () {
it("can remove previous children of the root element", function () {
const h2 = document.createElement("h2");
h2.textContent = "Hello";
const prevElm = document.createElement("div");
prevElm.id = "id";
prevElm.className = "class";
prevElm.appendChild(h2);
const nextVNode = h("div#id.class", [h("span", "Hi")]);
elm = patch(toVNode(prevElm), nextVNode).elm;
assert.strictEqual(elm, prevElm);
assert.strictEqual(elm.tagName, "DIV");
assert.strictEqual(elm.id, "id");
assert.strictEqual(elm.className, "class");
assert.strictEqual(elm.childNodes.length, 1);
assert.strictEqual(elm.childNodes[0].tagName, "SPAN");
assert.strictEqual(elm.childNodes[0].textContent, "Hi");
});
it("can support patching in a DocumentFragment", function () {
const prevElm = document.createDocumentFragment();
const nextVNode = vnode(
"",
{},
[h("div#id.class", [h("span", "Hi")])],
undefined,
prevElm as any
);
elm = patch(toVNode(prevElm), nextVNode).elm;
assert.strictEqual(elm, prevElm);
assert.strictEqual(elm.nodeType, 11);
assert.strictEqual(elm.childNodes.length, 1);
assert.strictEqual(elm.childNodes[0].tagName, "DIV");
assert.strictEqual(elm.childNodes[0].id, "id");
assert.strictEqual(elm.childNodes[0].className, "class");
assert.strictEqual(elm.childNodes[0].childNodes.length, 1);
assert.strictEqual(elm.childNodes[0].childNodes[0].tagName, "SPAN");
assert.strictEqual(elm.childNodes[0].childNodes[0].textContent, "Hi");
});
it("can remove some children of the root element", function () {
const h2 = document.createElement("h2");
h2.textContent = "Hello";
const prevElm = document.createElement("div");
prevElm.id = "id";
prevElm.className = "class";
const text = document.createTextNode("Foobar");
const reference = {};
(text as any).testProperty = reference; // ensures we dont recreate the Text Node
prevElm.appendChild(text);
prevElm.appendChild(h2);
const nextVNode = h("div#id.class", ["Foobar"]);
elm = patch(toVNode(prevElm), nextVNode).elm;
assert.strictEqual(elm, prevElm);
assert.strictEqual(elm.tagName, "DIV");
assert.strictEqual(elm.id, "id");
assert.strictEqual(elm.className, "class");
assert.strictEqual(elm.childNodes.length, 1);
assert.strictEqual(elm.childNodes[0].nodeType, 3);
assert.strictEqual(elm.childNodes[0].wholeText, "Foobar");
assert.strictEqual(elm.childNodes[0].testProperty, reference);
});
it("can remove text elements", function () {
const h2 = document.createElement("h2");
h2.textContent = "Hello";
const prevElm = document.createElement("div");
prevElm.id = "id";
prevElm.className = "class";
const text = document.createTextNode("Foobar");
prevElm.appendChild(text);
prevElm.appendChild(h2);
const nextVNode = h("div#id.class", [h("h2", "Hello")]);
elm = patch(toVNode(prevElm), nextVNode).elm;
assert.strictEqual(elm, prevElm);
assert.strictEqual(elm.tagName, "DIV");
assert.strictEqual(elm.id, "id");
assert.strictEqual(elm.className, "class");
assert.strictEqual(elm.childNodes.length, 1);
assert.strictEqual(elm.childNodes[0].nodeType, 1);
assert.strictEqual(elm.childNodes[0].textContent, "Hello");
});
it("can work with domApi", function () {
const domApi = {
...htmlDomApi,
tagName: function (elm: Element) {
return "x-" + elm.tagName.toUpperCase();
},
};
const h2 = document.createElement("h2");
h2.id = "hx";
h2.setAttribute("data-env", "xyz");
const text = document.createTextNode("Foobar");
const elm = document.createElement("div");
elm.id = "id";
elm.className = "class other";
elm.setAttribute("data", "value");
elm.appendChild(h2);
elm.appendChild(text);
const vnode = toVNode(elm, domApi);
assert.strictEqual(vnode.sel, "x-div#id.class.other");
assert.deepEqual(vnode.data, { attrs: { data: "value" } });
const children = vnode.children as [VNode, VNode];
assert.strictEqual(children[0].sel, "x-h2#hx");
assert.deepEqual(children[0].data, { attrs: { "data-env": "xyz" } });
assert.strictEqual(children[1].text, "Foobar");
});
});
describe("updating children with keys", function () {
function spanNum(n?: null | Key) {
if (n == null) {
return n;
} else if (typeof n === "string") {
return h("span", {}, n);
} else if (typeof n === "number") {
return h("span", { key: n }, n.toString());
} else {
return h("span", { key: n }, "symbol");
}
}
describe("addition of elements", function () {
it("appends elements", function () {
const vnode1 = h("span", [1].map(spanNum));
const vnode2 = h("span", [1, 2, 3].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 1);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 3);
assert.strictEqual(elm.children[1].innerHTML, "2");
assert.strictEqual(elm.children[2].innerHTML, "3");
});
it("prepends elements", function () {
const vnode1 = h("span", [4, 5].map(spanNum));
const vnode2 = h("span", [1, 2, 3, 4, 5].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 2);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2", "3", "4", "5"]);
});
it("add elements in the middle", function () {
const vnode1 = h("span", [1, 2, 4, 5].map(spanNum));
const vnode2 = h("span", [1, 2, 3, 4, 5].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 4);
assert.strictEqual(elm.children.length, 4);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2", "3", "4", "5"]);
});
it("add elements at begin and end", function () {
const vnode1 = h("span", [2, 3, 4].map(spanNum));
const vnode2 = h("span", [1, 2, 3, 4, 5].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 3);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2", "3", "4", "5"]);
});
it("adds children to parent with no children", function () {
const vnode1 = h("span", { key: "span" });
const vnode2 = h("span", { key: "span" }, [1, 2, 3].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 0);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2", "3"]);
});
it("removes all children from parent", function () {
const vnode1 = h("span", { key: "span" }, [1, 2, 3].map(spanNum));
const vnode2 = h("span", { key: "span" });
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2", "3"]);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 0);
});
it("update one child with same key but different sel", function () {
const vnode1 = h("span", { key: "span" }, [1, 2, 3].map(spanNum));
const vnode2 = h("span", { key: "span" }, [
spanNum(1),
h("i", { key: 2 }, "2"),
spanNum(3),
]);
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2", "3"]);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2", "3"]);
assert.strictEqual(elm.children.length, 3);
assert.strictEqual(elm.children[1].tagName, "I");
});
});
describe("removal of elements", function () {
it("removes elements from the beginning", function () {
const vnode1 = h("span", [1, 2, 3, 4, 5].map(spanNum));
const vnode2 = h("span", [3, 4, 5].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 5);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["3", "4", "5"]);
});
it("removes elements from the end", function () {
const vnode1 = h("span", [1, 2, 3, 4, 5].map(spanNum));
const vnode2 = h("span", [1, 2, 3].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 5);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 3);
assert.strictEqual(elm.children[0].innerHTML, "1");
assert.strictEqual(elm.children[1].innerHTML, "2");
assert.strictEqual(elm.children[2].innerHTML, "3");
});
it("removes elements from the middle", function () {
const vnode1 = h("span", [1, 2, 3, 4, 5].map(spanNum));
const vnode2 = h("span", [1, 2, 4, 5].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 5);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 4);
assert.deepEqual(elm.children[0].innerHTML, "1");
assert.strictEqual(elm.children[0].innerHTML, "1");
assert.strictEqual(elm.children[1].innerHTML, "2");
assert.strictEqual(elm.children[2].innerHTML, "4");
assert.strictEqual(elm.children[3].innerHTML, "5");
});
});
describe("element reordering", function () {
it("moves element forward", function () {
const vnode1 = h("span", [1, 2, 3, 4].map(spanNum));
const vnode2 = h("span", [2, 3, 1, 4].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 4);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 4);
assert.strictEqual(elm.children[0].innerHTML, "2");
assert.strictEqual(elm.children[1].innerHTML, "3");
assert.strictEqual(elm.children[2].innerHTML, "1");
assert.strictEqual(elm.children[3].innerHTML, "4");
});
it("moves element to end", function () {
const vnode1 = h("span", [1, 2, 3].map(spanNum));
const vnode2 = h("span", [2, 3, 1].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 3);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 3);
assert.strictEqual(elm.children[0].innerHTML, "2");
assert.strictEqual(elm.children[1].innerHTML, "3");
assert.strictEqual(elm.children[2].innerHTML, "1");
});
it("moves element backwards", function () {
const vnode1 = h("span", [1, 2, 3, 4].map(spanNum));
const vnode2 = h("span", [1, 4, 2, 3].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 4);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 4);
assert.strictEqual(elm.children[0].innerHTML, "1");
assert.strictEqual(elm.children[1].innerHTML, "4");
assert.strictEqual(elm.children[2].innerHTML, "2");
assert.strictEqual(elm.children[3].innerHTML, "3");
});
it("swaps first and last", function () {
const vnode1 = h("span", [1, 2, 3, 4].map(spanNum));
const vnode2 = h("span", [4, 2, 3, 1].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 4);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 4);
assert.strictEqual(elm.children[0].innerHTML, "4");
assert.strictEqual(elm.children[1].innerHTML, "2");
assert.strictEqual(elm.children[2].innerHTML, "3");
assert.strictEqual(elm.children[3].innerHTML, "1");
});
});
describe("combinations of additions, removals and reorderings", function () {
it("move to left and replace", function () {
const vnode1 = h("span", [1, 2, 3, 4, 5].map(spanNum));
const vnode2 = h("span", [4, 1, 2, 3, 6].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 5);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 5);
assert.strictEqual(elm.children[0].innerHTML, "4");
assert.strictEqual(elm.children[1].innerHTML, "1");
assert.strictEqual(elm.children[2].innerHTML, "2");
assert.strictEqual(elm.children[3].innerHTML, "3");
assert.strictEqual(elm.children[4].innerHTML, "6");
});
it("moves to left and leaves hole", function () {
const vnode1 = h("span", [1, 4, 5].map(spanNum));
const vnode2 = h("span", [4, 6].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 3);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["4", "6"]);
});
it("handles moved and set to undefined element ending at the end", function () {
const vnode1 = h("span", [2, 4, 5].map(spanNum));
const vnode2 = h("span", [4, 5, 3].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 3);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 3);
assert.strictEqual(elm.children[0].innerHTML, "4");
assert.strictEqual(elm.children[1].innerHTML, "5");
assert.strictEqual(elm.children[2].innerHTML, "3");
});
it("moves a key in non-keyed nodes with a size up", function () {
const vnode1 = h("span", [1, "a", "b", "c"].map(spanNum));
const vnode2 = h("span", ["d", "a", "b", "c", 1, "e"].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.childNodes.length, 4);
assert.strictEqual(elm.textContent, "1abc");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.childNodes.length, 6);
assert.strictEqual(elm.textContent, "dabc1e");
});
it("accepts symbol as key", function () {
const vnode1 = h("span", [Symbol()].map(spanNum));
const vnode2 = h(
"span",
[Symbol("1"), Symbol("2"), Symbol("3")].map(spanNum)
);
elm = patch(vnode0, vnode1).elm;
assert.equal(elm.children.length, 1);
elm = patch(vnode1, vnode2).elm;
assert.equal(elm.children.length, 3);
assert.equal(elm.children[1].innerHTML, "symbol");
assert.equal(elm.children[2].innerHTML, "symbol");
});
});
it("reverses elements", function () {
const vnode1 = h("span", [1, 2, 3, 4, 5, 6, 7, 8].map(spanNum));
const vnode2 = h("span", [8, 7, 6, 5, 4, 3, 2, 1].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 8);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), [
"8",
"7",
"6",
"5",
"4",
"3",
"2",
"1",
]);
});
it("something", function () {
const vnode1 = h("span", [0, 1, 2, 3, 4, 5].map(spanNum));
const vnode2 = h("span", [4, 3, 2, 1, 5, 0].map(spanNum));
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 6);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), [
"4",
"3",
"2",
"1",
"5",
"0",
]);
});
it("handles random shuffles", function () {
let n;
let i;
const arr = [];
const opacities: string[] = [];
const elms = 14;
const samples = 5;
function spanNumWithOpacity(n: number, o: string) {
return h("span", { key: n, style: { opacity: o } }, n.toString());
}
for (n = 0; n < elms; ++n) {
arr[n] = n;
}
for (n = 0; n < samples; ++n) {
const vnode1 = h(
"span",
arr.map(function (n) {
return spanNumWithOpacity(n, "1");
})
);
const shufArr = shuffle(arr.slice(0));
let elm: HTMLDivElement | HTMLSpanElement = document.createElement(
"div"
);
elm = patch(elm, vnode1).elm as HTMLSpanElement;
for (i = 0; i < elms; ++i) {
assert.strictEqual(elm.children[i].innerHTML, i.toString());
opacities[i] = Math.random().toFixed(5).toString();
}
const vnode2 = h(
"span",
arr.map(function (n) {
return spanNumWithOpacity(shufArr[n], opacities[n]);
})
);
elm = patch(vnode1, vnode2).elm as HTMLSpanElement;
for (i = 0; i < elms; ++i) {
assert.strictEqual(
elm.children[i].innerHTML,
shufArr[i].toString()
);
const opacity = (elm.children[i] as HTMLSpanElement).style.opacity;
assert.strictEqual(opacities[i].indexOf(opacity), 0);
}
}
});
it("supports null/undefined children", function () {
const vnode1 = h("i", [0, 1, 2, 3, 4, 5].map(spanNum));
const vnode2 = h(
"i",
[null, 2, undefined, null, 1, 0, null, 5, 4, null, 3, undefined].map(
spanNum
)
);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 6);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), [
"2",
"1",
"0",
"5",
"4",
"3",
]);
});
it("supports all null/undefined children", function () {
const vnode1 = h("i", [0, 1, 2, 3, 4, 5].map(spanNum));
const vnode2 = h("i", [null, null, undefined, null, null, undefined]);
const vnode3 = h("i", [5, 4, 3, 2, 1, 0].map(spanNum));
patch(vnode0, vnode1);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 0);
elm = patch(vnode2, vnode3).elm;
assert.deepEqual(map(inner, elm.children), [
"5",
"4",
"3",
"2",
"1",
"0",
]);
});
it("handles random shuffles with null/undefined children", function () {
let i;
let j;
let r;
let len;
let arr;
const maxArrLen = 15;
const samples = 5;
let vnode1 = vnode0;
let vnode2;
for (i = 0; i < samples; ++i, vnode1 = vnode2) {
len = Math.floor(Math.random() * maxArrLen);
arr = [];
for (j = 0; j < len; ++j) {
if ((r = Math.random()) < 0.5) arr[j] = String(j);
else if (r < 0.75) arr[j] = null;
else arr[j] = undefined;
}
shuffle(arr);
vnode2 = h("div", arr.map(spanNum));
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(
map(inner, elm.children),
arr.filter(function (x) {
return x != null;
})
);
}
});
});
describe("updating children without keys", function () {
it("appends elements", function () {
const vnode1 = h("div", [h("span", "Hello")]);
const vnode2 = h("div", [h("span", "Hello"), h("span", "World")]);
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["Hello"]);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["Hello", "World"]);
});
it("handles unmoved text nodes", function () {
const vnode1 = h("div", ["Text", h("span", "Span")]);
const vnode2 = h("div", ["Text", h("span", "Span")]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text");
});
it("handles changing text children", function () {
const vnode1 = h("div", ["Text", h("span", "Span")]);
const vnode2 = h("div", ["Text2", h("span", "Span")]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text2");
});
it("handles unmoved comment nodes", function () {
const vnode1 = h("div", [h("!", "Text"), h("span", "Span")]);
const vnode2 = h("div", [h("!", "Text"), h("span", "Span")]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text");
});
it("handles changing comment text", function () {
const vnode1 = h("div", [h("!", "Text"), h("span", "Span")]);
const vnode2 = h("div", [h("!", "Text2"), h("span", "Span")]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Text2");
});
it("handles changing empty comment", function () {
const vnode1 = h("div", [h("!"), h("span", "Span")]);
const vnode2 = h("div", [h("!", "Test"), h("span", "Span")]);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.childNodes[0].textContent, "");
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.childNodes[0].textContent, "Test");
});
it("prepends element", function () {
const vnode1 = h("div", [h("span", "World")]);
const vnode2 = h("div", [h("span", "Hello"), h("span", "World")]);
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["World"]);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["Hello", "World"]);
});
it("prepends element of different tag type", function () {
const vnode1 = h("div", [h("span", "World")]);
const vnode2 = h("div", [h("div", "Hello"), h("span", "World")]);
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["World"]);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(prop("tagName"), elm.children), ["DIV", "SPAN"]);
assert.deepEqual(map(inner, elm.children), ["Hello", "World"]);
});
it("removes elements", function () {
const vnode1 = h("div", [
h("span", "One"),
h("span", "Two"),
h("span", "Three"),
]);
const vnode2 = h("div", [h("span", "One"), h("span", "Three")]);
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["One", "Two", "Three"]);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["One", "Three"]);
});
it("removes a single text node", function () {
const vnode1 = h("div", "One");
const vnode2 = h("div");
patch(vnode0, vnode1);
assert.strictEqual(elm.textContent, "One");
patch(vnode1, vnode2);
assert.strictEqual(elm.textContent, "");
});
it("removes a single text node when children are updated", function () {
const vnode1 = h("div", "One");
const vnode2 = h("div", [h("div", "Two"), h("span", "Three")]);
patch(vnode0, vnode1);
assert.strictEqual(elm.textContent, "One");
patch(vnode1, vnode2);
assert.deepEqual(map(prop("textContent"), elm.childNodes), [
"Two",
"Three",
]);
});
it("removes a text node among other elements", function () {
const vnode1 = h("div", ["One", h("span", "Two")]);
const vnode2 = h("div", [h("div", "Three")]);
patch(vnode0, vnode1);
assert.deepEqual(map(prop("textContent"), elm.childNodes), [
"One",
"Two",
]);
patch(vnode1, vnode2);
assert.strictEqual(elm.childNodes.length, 1);
assert.strictEqual(elm.childNodes[0].tagName, "DIV");
assert.strictEqual(elm.childNodes[0].textContent, "Three");
});
it("reorders elements", function () {
const vnode1 = h("div", [
h("span", "One"),
h("div", "Two"),
h("b", "Three"),
]);
const vnode2 = h("div", [
h("b", "Three"),
h("span", "One"),
h("div", "Two"),
]);
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["One", "Two", "Three"]);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(prop("tagName"), elm.children), [
"B",
"SPAN",
"DIV",
]);
assert.deepEqual(map(inner, elm.children), ["Three", "One", "Two"]);
});
it("supports null/undefined children", function () {
const vnode1 = h("i", [null, h("i", "1"), h("i", "2"), null]);
const vnode2 = h("i", [
h("i", "2"),
undefined,
undefined,
h("i", "1"),
undefined,
]);
const vnode3 = h("i", [
null,
h("i", "1"),
undefined,
null,
h("i", "2"),
undefined,
null,
]);
elm = patch(vnode0, vnode1).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2"]);
elm = patch(vnode1, vnode2).elm;
assert.deepEqual(map(inner, elm.children), ["2", "1"]);
elm = patch(vnode2, vnode3).elm;
assert.deepEqual(map(inner, elm.children), ["1", "2"]);
});
it("supports all null/undefined children", function () {
const vnode1 = h("i", [h("i", "1"), h("i", "2")]);
const vnode2 = h("i", [null, undefined]);
const vnode3 = h("i", [h("i", "2"), h("i", "1")]);
patch(vnode0, vnode1);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 0);
elm = patch(vnode2, vnode3).elm;
assert.deepEqual(map(inner, elm.children), ["2", "1"]);
});
});
});
describe("hooks", function () {
describe("element hooks", function () {
it("calls `create` listener before inserted into parent but after children", function () {
const result = [];
const cb: CreateHook = (empty, vnode) => {
assert(vnode.elm instanceof Element);
assert.strictEqual((vnode.elm as HTMLDivElement).children.length, 2);
assert.strictEqual(vnode.elm!.parentNode, null);
result.push(vnode);
};
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", { hook: { create: cb } }, [
h("span", "Child 1"),
h("span", "Child 2"),
]),
h("span", "Can't touch me"),
]);
patch(vnode0, vnode1);
assert.strictEqual(1, result.length);
});
it("calls `insert` listener after both parents, siblings and children have been inserted", function () {
const result = [];
const cb: InsertHook = (vnode) => {
assert(vnode.elm instanceof Element);
assert.strictEqual((vnode.elm as HTMLDivElement).children.length, 2);
assert.strictEqual(vnode.elm!.parentNode!.children.length, 3);
result.push(vnode);
};
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", { hook: { insert: cb } }, [
h("span", "Child 1"),
h("span", "Child 2"),
]),
h("span", "Can touch me"),
]);
patch(vnode0, vnode1);
assert.strictEqual(1, result.length);
});
it("calls `prepatch` listener", function () {
const result = [];
const cb: PrePatchHook = (oldVnode, vnode) => {
assert.strictEqual(oldVnode, vnode1.children![1]);
assert.strictEqual(vnode, vnode2.children![1]);
result.push(vnode);
};
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", { hook: { prepatch: cb } }, [
h("span", "Child 1"),
h("span", "Child 2"),
]),
]);
const vnode2 = h("div", [
h("span", "First sibling"),
h("div", { hook: { prepatch: cb } }, [
h("span", "Child 1"),
h("span", "Child 2"),
]),
]);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(result.length, 1);
});
it("calls `postpatch` after `prepatch` listener", function () {
let pre = 0;
let post = 0;
function preCb() {
pre++;
}
function postCb() {
assert.strictEqual(pre, post + 1);
post++;
}
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", { hook: { prepatch: preCb, postpatch: postCb } }, [
h("span", "Child 1"),
h("span", "Child 2"),
]),
]);
const vnode2 = h("div", [
h("span", "First sibling"),
h("div", { hook: { prepatch: preCb, postpatch: postCb } }, [
h("span", "Child 1"),
h("span", "Child 2"),
]),
]);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(pre, 1);
assert.strictEqual(post, 1);
});
it("calls `update` listener", function () {
const result1: VNode[] = [];
const result2: VNode[] = [];
function cb(result: VNode[], oldVnode: VNode, vnode: VNode) {
if (result.length > 0) {
console.log(result[result.length - 1]);
console.log(oldVnode);
assert.strictEqual(result[result.length - 1], oldVnode);
}
result.push(vnode);
}
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", { hook: { update: cb.bind(null, result1) } }, [
h("span", "Child 1"),
h("span", { hook: { update: cb.bind(null, result2) } }, "Child 2"),
]),
]);
const vnode2 = h("div", [
h("span", "First sibling"),
h("div", { hook: { update: cb.bind(null, result1) } }, [
h("span", "Child 1"),
h("span", { hook: { update: cb.bind(null, result2) } }, "Child 2"),
]),
]);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(result1.length, 1);
assert.strictEqual(result2.length, 1);
});
it("calls `remove` listener", function () {
const result = [];
const cb: RemoveHook = (vnode, rm) => {
const parent = vnode.elm!.parentNode as HTMLDivElement;
assert(vnode.elm instanceof Element);
assert.strictEqual((vnode.elm as HTMLDivElement).children.length, 2);
assert.strictEqual(parent.children.length, 2);
result.push(vnode);
rm();
assert.strictEqual(parent.children.length, 1);
};
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", { hook: { remove: cb } }, [
h("span", "Child 1"),
h("span", "Child 2"),
]),
]);
const vnode2 = h("div", [h("span", "First sibling")]);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(1, result.length);
});
it("calls `destroy` listener when patching text node over node with children", function () {
let calls = 0;
function cb() {
calls++;
}
const vnode1 = h("div", [
h("div", { hook: { destroy: cb } }, [h("span", "Child 1")]),
]);
const vnode2 = h("div", "Text node");
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(calls, 1);
});
it("calls `init` and `prepatch` listeners on root", function () {
let count = 0;
const init: InitHook = (vnode) => {
assert.strictEqual(vnode, vnode2);
count += 1;
};
const prepatch: PrePatchHook = (oldVnode, vnode) => {
assert.strictEqual(vnode, vnode1);
count += 1;
};
const vnode1 = h("div", { hook: { init: init, prepatch: prepatch } });
patch(vnode0, vnode1);
assert.strictEqual(1, count);
const vnode2 = h("span", { hook: { init: init, prepatch: prepatch } });
patch(vnode1, vnode2);
assert.strictEqual(2, count);
});
it("removes element when all remove listeners are done", function () {
let rm1, rm2, rm3;
const patch = init([
{
remove: function (_, rm) {
rm1 = rm;
},
},
{
remove: function (_, rm) {
rm2 = rm;
},
},
]);
const vnode1 = h("div", [
h("a", {
hook: {
remove: function (_, rm) {
rm3 = rm;
},
},
}),
]);
const vnode2 = h("div", []);
elm = patch(vnode0, vnode1).elm;
assert.strictEqual(elm.children.length, 1);
elm = patch(vnode1, vnode2).elm;
assert.strictEqual(elm.children.length, 1);
(rm1 as any)();
assert.strictEqual(elm.children.length, 1);
(rm3 as any)();
assert.strictEqual(elm.children.length, 1);
(rm2 as any)();
assert.strictEqual(elm.children.length, 0);
});
it("invokes remove hook on replaced root", function () {
const result = [];
const parent = document.createElement("div");
const vnode0 = document.createElement("div");
parent.appendChild(vnode0);
const cb: RemoveHook = (vnode, rm) => {
result.push(vnode);
rm();
};
const vnode1 = h("div", { hook: { remove: cb } }, [
h("b", "Child 1"),
h("i", "Child 2"),
]);
const vnode2 = h("span", [h("b", "Child 1"), h("i", "Child 2")]);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(1, result.length);
});
});
describe("module hooks", function () {
it("invokes `pre` and `post` hook", function () {
const result: string[] = [];
const patch = init([
{
pre: function () {
result.push("pre");
},
},
{
post: function () {
result.push("post");
},
},
]);
const vnode1 = h("div");
patch(vnode0, vnode1);
assert.deepEqual(result, ["pre", "post"]);
});
it("invokes global `destroy` hook for all removed children", function () {
const result = [];
const cb: DestroyHook = (vnode) => {
result.push(vnode);
};
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", [
h("span", { hook: { destroy: cb } }, "Child 1"),
h("span", "Child 2"),
]),
]);
const vnode2 = h("div");
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(result.length, 1);
});
it("handles text vnodes with `undefined` `data` property", function () {
const vnode1 = h("div", [" "]);
const vnode2 = h("div", []);
patch(vnode0, vnode1);
patch(vnode1, vnode2);
});
it("invokes `destroy` module hook for all removed children", function () {
let created = 0;
let destroyed = 0;
const patch = init([
{
create: function () {
created++;
},
},
{
destroy: function () {
destroyed++;
},
},
]);
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", [h("span", "Child 1"), h("span", "Child 2")]),
]);
const vnode2 = h("div");
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(created, 4);
assert.strictEqual(destroyed, 4);
});
it("does not invoke `create` and `remove` module hook for text nodes", function () {
let created = 0;
let removed = 0;
const patch = init([
{
create: function () {
created++;
},
},
{
remove: function () {
removed++;
},
},
]);
const vnode1 = h("div", [
h("span", "First child"),
"",
h("span", "Third child"),
]);
const vnode2 = h("div");
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(created, 2);
assert.strictEqual(removed, 2);
});
it("does not invoke `destroy` module hook for text nodes", function () {
let created = 0;
let destroyed = 0;
const patch = init([
{
create: function () {
created++;
},
},
{
destroy: function () {
destroyed++;
},
},
]);
const vnode1 = h("div", [
h("span", "First sibling"),
h("div", [h("span", "Child 1"), h("span", ["Text 1", "Text 2"])]),
]);
const vnode2 = h("div");
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(created, 4);
assert.strictEqual(destroyed, 4);
});
});
});
describe("short circuiting", function () {
it("does not update strictly equal vnodes", function () {
const result = [];
const cb: UpdateHook = (vnode) => {
result.push(vnode);
};
const vnode1 = h("div", [
h("span", { hook: { update: cb } }, "Hello"),
h("span", "there"),
]);
patch(vnode0, vnode1);
patch(vnode1, vnode1);
assert.strictEqual(result.length, 0);
});
it("does not update strictly equal children", function () {
const result = [];
function cb(vnode: VNode) {
result.push(vnode);
}
const vnode1 = h("div", [
h("span", { hook: { patch: cb } as any }, "Hello"),
h("span", "there"),
]);
const vnode2 = h("div");
vnode2.children = vnode1.children;
patch(vnode0, vnode1);
patch(vnode1, vnode2);
assert.strictEqual(result.length, 0);
});
});
});
© 2015 - 2025 Weber Informatics LLC | Privacy Policy