UNPKG

joicomponents

Version:

Design patterns to build native web components

386 lines (341 loc) 14.5 kB
import {SetupMixin, setupInAdvance} from "../../src/SetupMixin.js"; describe('SetupMixin', function () { it("extend SetupMixin", function () { const SetupElement = SetupMixin(HTMLElement); customElements.define("setup-extend", SetupElement); const el = new SetupElement(); expect(el.constructor.name).to.be.equal("SetupMixin"); }); //works with mocha 4.0.1 //https://stackoverflow.com/questions/39354331/how-can-i-prevent-mocha-from-trapping-unhandled-exceptions it("element that does not implement setupCallback will fail", function (done) { // Stop Mocha from handling uncaughtExceptions. // mocha.allowUncaught(); const savedHandler = window.onerror; window.onerror = undefined; // Mocha.process.removeListener("uncaughtException"); const SetupElement = class X extends SetupMixin(HTMLElement){ // setupCallback(){ // } }; customElements.define("setup-connect-to-dom-without-callback-fails", SetupElement); const el = new SetupElement(); const globError = function (err) { err.preventDefault(); err.stopPropagation(); expect(err.message).to.be.equal("Uncaught TypeError: this.setupCallback is not a function"); window.removeEventListener("error", globError); window.onerror = savedHandler; done(); }; window.addEventListener("error", globError); document.querySelector("body").appendChild(el); }); it("manually calling setupCallback() and set isSetup", function () { const SetupElement = class SetupElement extends SetupMixin(HTMLElement) { setupCallback() { this.setupValue = 123; } }; customElements.define("setup-basic", SetupElement); const el = new SetupElement(); expect(el.constructor.name).to.be.equal("SetupElement"); expect(el.isSetup).to.be.equal(false); el.setupCallback(); el.isSetup = true; expect(el.isSetup).to.be.equal(true); expect(el.setupValue).to.be.equal(123); }); it("setting isSetup twice gives error", function () { const SetupElement = SetupMixin(HTMLElement); customElements.define("setup-issetup-error-twice", SetupElement); const el = new SetupElement(); expect(el.isSetup).to.be.equal(false); el.isSetup = true; expect(() => el.isSetup = true).to.throw("SetupMixin: .isSetup property should only be changed by the SetupMixin and to true."); }); it("setting isSetup non-true gives error", function () { const SetupElement = SetupMixin(HTMLElement); customElements.define("setup-issetup-error-nontrue", SetupElement); const el = new SetupElement(); expect(el.isSetup).to.be.equal(false); expect(function () { el.isSetup = false }).to.throw("SetupMixin: .isSetup property should only be changed by the SetupMixin and to true."); }); it("adding to DOM triggers setupCallback", function () { const SetupElement = class SetupElement extends SetupMixin(HTMLElement) { setupCallback() { this.testConnect = "abc"; } }; customElements.define("setup-connect-to-dom", SetupElement); const el = new SetupElement(); expect(el.isSetup).to.be.equal(false); document.querySelector("body").appendChild(el); expect(el.isSetup).to.be.equal(true); expect(el.testConnect).to.be.equal("abc"); document.querySelector("body").removeChild(el); }); it("second connection to DOM does not triggers setupCallback", function () { let first = true; const SetupElement = class SetupElement extends SetupMixin(HTMLElement) { setupCallback() { first ? first = false : assert(false); } }; customElements.define("setup-connect-to-dom-not-twice", SetupElement); const el = new SetupElement(); expect(el.isSetup).to.be.equal(false); expect(el.isConnected).to.be.equal(false); document.querySelector("body").appendChild(el); expect(el.isConnected).to.be.equal(true); expect(el.isSetup).to.be.equal(true); document.querySelector("body").removeChild(el); expect(el.isConnected).to.be.equal(false); expect(el.isSetup).to.be.equal(true); document.querySelector("body").appendChild(el); expect(el.isConnected).to.be.equal(true); expect(el.isSetup).to.be.equal(true); document.querySelector("body").removeChild(el); expect(el.isConnected).to.be.equal(false); expect(el.isSetup).to.be.equal(true); }); it("constructor before setupCallback before the body of connectedCallback", function (done) { let state = "constructor"; const SetupElement = class SetupElement extends SetupMixin(HTMLElement) { constructor() { super(); expect(state).to.be.equal("constructor"); state = "setup"; } setupCallback() { expect(state).to.be.equal("setup"); state = "connected"; } connectedCallback() { super.connectedCallback(); expect(state).to.be.equal("connected"); done(); } }; customElements.define("setup-order", SetupElement); const el = new SetupElement(); document.querySelector("body").appendChild(el); document.querySelector("body").removeChild(el); }); }); describe('SetupMixin construction runs setupCallback and attributeChanged only after first connected', function () { const SetupElement = class SetupElement extends SetupMixin(HTMLElement) { static get observedAttributes() { return ["one"]; } constructor() { super(); this.test = "_Constructor"; } setupCallback() { this.test += "_Setup"; } attributeChangedCallback(name, oldValue, newValue) { if (!this.isSetup) return; //abort premature attributeChangedCallback this.test += "_Attribute_" + name + oldValue + newValue; } connectedCallback() { super.connectedCallback(); this.test += "_Connected"; } }; customElements.define("setup-construct", SetupElement); it("new", function () { const el = new SetupElement(); el.setAttribute("one", "nonono"); el.setAttribute("one", "two"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_Constructor"); document.querySelector("body").appendChild(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(el); }); it("document.createElement", function () { const el = document.createElement("setup-construct"); el.setAttribute("one", "nonono"); el.setAttribute("one", "two"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_Constructor"); document.querySelector("body").appendChild(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(el); }); it("cloneNode(false) on element", function () { const orig = new SetupElement(); const el = orig.cloneNode(false); el.setAttribute("one", "nonono"); el.setAttribute("one", "two"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_Constructor"); document.querySelector("body").appendChild(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(el); }); it("cloneNode(true) on parent", function () { const orig = new SetupElement(); const div = document.createElement("div"); div.appendChild(orig); const el = div.cloneNode(true).children[0]; el.setAttribute("one", "nonono"); el.setAttribute("one", "two"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_Constructor"); document.querySelector("body").appendChild(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(el); }); it("innerHTML on an unconnected element", function () { const div = document.createElement("div"); div.innerHTML = "<setup-construct one='two' one='nonono' a='b'></setup-construct>"; //the second attribute is considered redundant and skipped const el = div.children[0]; expect(el.test).to.be.equal("_Constructor"); document.querySelector("body").appendChild(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(el); }); it("innerHTML on a connected <template> element DOES NOT trigger constructor 1", function () { const div = document.createElement("div"); document.querySelector("body").appendChild(div); div.innerHTML = "<template><setup-construct one='two' a='b'></setup-construct></template>"; const el = div.children[0].content.children[0]; expect(el.test).to.be.equal(undefined); // assert(el.test === "_Constructor"); //todo not sure why constructor is not run when it is a template element. Is it always, or just sometimes?? check the spec. document.querySelector("body").appendChild(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(el); document.querySelector("body").removeChild(div); }); it("innerHTML on a connected <template> element DOES NOT trigger constructor 2", function () { const div = document.createElement("div"); document.querySelector("body").appendChild(div); div.innerHTML = "<template><setup-construct one='two' a='b'></setup-construct></template>"; const el = div.children[0].content.children[0]; expect(el.test).to.be.equal(undefined); // assert(el.test === "_Constructor"); //todo not sure why constructor is not run when it is a template element. Is it always, or just sometimes?? check the spec. const div2 = document.createElement("div"); div2.appendChild(el); expect(el.test).to.be.equal(undefined); document.querySelector("body").removeChild(div); }); it("innerHTML on a connected element", function () { const div = document.createElement("div"); document.querySelector("body").appendChild(div); div.innerHTML = "<setup-construct one='two' a='b'></setup-construct>"; const el = div.children[0]; expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(div); }); //todo to test main document parser on load with template and without template in an iframe }); describe('setupInAdvance()', function () { const SetupElement = class SetupElement extends SetupMixin(HTMLElement) { static get observedAttributes() { return ["one"] } constructor() { super(); this.test = "_Constructor"; } setupCallback() { this.test += "_Setup"; } attributeChangedCallback(name, oldValue, newValue) { if (!this.isSetup) return; this.test += "_Attribute_" + name + oldValue + newValue; } connectedCallback() { super.connectedCallback(); this.test += "_Connected"; } }; customElements.define("setup-in-advance", SetupElement); it("simple", function () { const el = new SetupElement(); el.setAttribute("one", "two"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_Constructor"); setupInAdvance(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo"); }); it("simple child", function () { const el = new SetupElement(); el.setAttribute("one", "two"); el.setAttribute("a", "b"); const div = document.createElement("div"); div.appendChild(el); expect(el.test).to.be.equal("_Constructor"); setupInAdvance(div); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo"); }); it("parent 0 attributes, child 2 attributes/1 observedAttribute", function () { const el = new SetupElement(); el.setAttribute("one", "two"); el.setAttribute("a", "b"); const outer = new SetupElement(); outer.appendChild(el); expect(outer.test).to.be.equal("_Constructor"); expect(el.test).to.be.equal("_Constructor"); setupInAdvance(outer); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo"); expect(outer.test).to.be.equal("_Constructor_Setup"); }); it("el with shadow with a div with a setup-in-advance child", function () { const WithElInShadow = class WithElInShadow extends SetupMixin(HTMLElement){ static get observedAttributes(){ return ["three"]; } constructor(){ super(); this.attachShadow({mode: "open"}); this.test = "_C"; } setupCallback(){ this.test += "_S"; this.shadowRoot.innerHTML = "<div><setup-in-advance x='y' one='inside'></setup-in-advance></div>"; } attributeChangedCallback(name, oldValue, newValue) { if (!this.isSetup) return; this.test += "_A_" + name + oldValue + newValue; } connectedCallback() { super.connectedCallback(); this.test += "_C2"; } }; customElements.define("setup-with-shadow", WithElInShadow); const el = new WithElInShadow(); el.setAttribute("three", "four"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_C"); expect(el.test.shadowRoot).to.be.equal(undefined); setupInAdvance(el); expect(el.test).to.be.equal("_C_S_A_threenullfour"); expect(el.shadowRoot.children[0].children[0].test).to.be.equal("_Constructor_Setup_Attribute_onenullinside"); }); it("fail when run twice", function () { const el = new SetupElement(); el.setAttribute("one", "two"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_Constructor"); setupInAdvance(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo"); expect(()=>setupInAdvance(el)).to.throw("SetupMixin: .isSetup property should only be changed by the SetupMixin and to true."); }); it("fail when already setup up due to connection", function () { const el = new SetupElement(); el.setAttribute("one", "two"); el.setAttribute("a", "b"); expect(el.test).to.be.equal("_Constructor"); document.querySelector("body").appendChild(el); expect(el.test).to.be.equal("_Constructor_Setup_Attribute_onenulltwo_Connected"); document.querySelector("body").removeChild(el); expect(()=>setupInAdvance(el)).to.throw("SetupMixin: .isSetup property should only be changed by the SetupMixin and to true."); }); });