| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536 |
- 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,
- fragment,
- } from "../../src/index";
- const hasSvgClassList = "classList" in SVGElement.prototype;
- const patch = init(
- [classModule, propsModule, eventListenersModule],
- undefined,
- { experimental: { fragments: true } }
- );
- function prop<T>(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 String obj content", function () {
- const vnode = h("a", new String("b"));
- assert.equal(vnode.text, "b");
- });
- it("can create vnode with props and String obj content", function () {
- const vnode = h("a", {}, new String("b"));
- assert.equal(vnode.text, "b");
- });
- it("can create vnode with Number obj content", function () {
- const vnode = h("a", new Number(1));
- assert.equal(vnode.text, "1");
- });
- 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 vnode with array String obj content", function () {
- elm = patch(vnode0, h("a", ["b", new String("c")])).elm;
- assert.strictEqual(elm.innerHTML, "bc");
- });
- 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 = "<div>Thing 1</div>";
- 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("created document fragment", function () {
- it("is an instance of DocumentFragment", function () {
- const vnode1 = fragment(["I am", h("span", [" a", " fragment"])]);
- elm = patch(vnode0, vnode1).elm;
- assert.strictEqual(elm.nodeType, document.DOCUMENT_FRAGMENT_NODE);
- assert.strictEqual(elm.textContent, "I am a fragment");
- });
- });
- 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, { dataset: { env: "xyz" } });
- assert.strictEqual(children[1].text, "Foobar");
- });
- it("can parsing dataset and attrs", function () {
- const onlyAttrs = document.createElement("div");
- onlyAttrs.setAttribute("foo", "bar");
- assert.deepEqual(toVNode(onlyAttrs).data, { attrs: { foo: "bar" } });
- const onlyDataset = document.createElement("div");
- onlyDataset.setAttribute("data-foo", "bar");
- assert.deepEqual(toVNode(onlyDataset).data, {
- dataset: { foo: "bar" },
- });
- const onlyDatasets2 = document.createElement("div");
- onlyDatasets2.dataset.foo = "bar";
- assert.deepEqual(toVNode(onlyDatasets2).data, {
- dataset: { foo: "bar" },
- });
- const bothAttrsAndDatasets = document.createElement("div");
- bothAttrsAndDatasets.setAttribute("foo", "bar");
- bothAttrsAndDatasets.setAttribute("data-foo", "bar");
- bothAttrsAndDatasets.dataset.again = "again";
- assert.deepEqual(toVNode(bothAttrsAndDatasets).data, {
- attrs: { foo: "bar" },
- dataset: { foo: "bar", again: "again" },
- });
- });
- });
- 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("patching a fragment", function () {
- it("can patch on document fragments", function () {
- let firstChild: HTMLElement;
- const root = document.createElement("div");
- const vnode1 = fragment(["I am", h("span", [" a", " fragment"])]);
- const vnode2 = h("div", ["I am an element"]);
- const vnode3 = fragment(["fragment ", "again"]);
- root.appendChild(vnode0);
- firstChild = root.firstChild as HTMLElement;
- assert.strictEqual(firstChild, vnode0);
- elm = patch(vnode0, vnode1).elm;
- firstChild = root.firstChild as HTMLElement;
- assert.strictEqual(firstChild.textContent, "I am");
- assert.strictEqual(elm.nodeType, document.DOCUMENT_FRAGMENT_NODE);
- assert.strictEqual(elm.parent, root);
- elm = patch(vnode1, vnode2).elm;
- firstChild = root.firstChild as HTMLElement;
- assert.strictEqual(firstChild.tagName, "DIV");
- assert.strictEqual(firstChild.textContent, "I am an element");
- assert.strictEqual(elm.tagName, "DIV");
- assert.strictEqual(elm.textContent, "I am an element");
- assert.strictEqual(elm.parentNode, root);
- elm = patch(vnode2, vnode3).elm;
- firstChild = root.firstChild as HTMLElement;
- assert.strictEqual(elm.nodeType, document.DOCUMENT_FRAGMENT_NODE);
- assert.strictEqual(firstChild.textContent, "fragment ");
- assert.strictEqual(elm.parent, root);
- });
- it("allows a document fragment as a container", function () {
- const vnode0 = document.createDocumentFragment();
- const vnode1 = fragment(["I", "am", "a", h("span", ["fragment"])]);
- const vnode2 = h("div", "I am an element");
- elm = patch(vnode0, vnode1).elm;
- assert.strictEqual(elm.nodeType, document.DOCUMENT_FRAGMENT_NODE);
- elm = patch(vnode1, vnode2).elm;
- assert.strictEqual(elm.tagName, "DIV");
- });
- });
- 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.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.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);
- });
- });
- });
|