children-changed-callback
Version:
Mixin to provide subclasses of HTMLElement with .childrenChangedCallback(newList, oldList) and .visibleChildren().
315 lines (281 loc) • 12.7 kB
JavaScript
import {ChildrenChangedMixin} from "../src/ChildrenChangedMixin.js";
describe('ChildrenChangedMixin', function () {
it("extend HTMLElement class and make an element", function () {
const ChildrenChangedElement = ChildrenChangedMixin(HTMLElement);
customElements.define("must-use-custom-elements-define-to-enable-constructor", ChildrenChangedElement);
const el = new ChildrenChangedElement();
expect(el.constructor.name).to.be.equal("ChildrenChangedMixin");
});
it("subclass ChildrenChangedMixin", function () {
const SubclassChildrenChangedElement = class SubclassChildrenChanged extends ChildrenChangedMixin(HTMLElement) {
test() {
return "abc";
}
};
customElements.define("subclass-children-changed", SubclassChildrenChangedElement);
const el = new SubclassChildrenChangedElement();
expect(el.constructor.name).to.be.equal("SubclassChildrenChanged");
expect(el.test()).to.be.equal("abc");
});
it("subclass ChildrenChangedMixin anonymous", function () {
const SubclassChildrenChangedElement = class extends ChildrenChangedMixin(HTMLElement) {
test() {
return "abc";
}
};
customElements.define("subclass-children-changed-element", SubclassChildrenChangedElement);
const el = new SubclassChildrenChangedElement();
expect(el.constructor.name).to.be.equal("SubclassChildrenChangedElement");
expect(el.test()).to.be.equal("abc");
});
it(".visibleChildren property", function () {
const Subclass = class Subclass extends ChildrenChangedMixin(HTMLElement) {
};
customElements.define("visible-children-property", Subclass);
const el = new Subclass();
assert(el.getVisibleChildren().length === 0);
let child = document.createElement("div");
el.appendChild(child);
el.appendChild(document.createElement("slot"));
assert(el.getVisibleChildren().length === 1);
el.removeChild(child);
assert(el.getVisibleChildren().length === 0);
});
it("ChildrenChangedMixin add DIV imperative and trigger childrenChangedCallback", function (done) {
const Subclass = class Subclass extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(1);
expect(newChildren[0].nodeName).to.be.equal("DIV");
done();
}
};
customElements.define("children-changed-div-added", Subclass);
const el = new Subclass();
el.appendChild(document.createElement("div"));
document.querySelector("body").appendChild(el);
document.querySelector("body").removeChild(el);
});
it("ChildrenChangedMixin add SLOT imperative and trigger childrenChangedCallback", function (done) {
const Subclass = class Subclass extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(0);
done();
}
};
customElements.define("children-changed-slot-added", Subclass);
const el = new Subclass();
el.appendChild(document.createElement("slot"));
document.querySelector("body").appendChild(el);
document.querySelector("body").removeChild(el);
});
it("ChildrenChangedMixin added DIV and then SLOT imperative and trigger childrenChangedCallback", function (done) {
const Subclass = class Subclass extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(1);
expect(newChildren[0].nodeName).to.be.equal("DIV");
done();
}
};
customElements.define("children-changed-div-and-slot-added", Subclass);
const el = new Subclass();
el.appendChild(document.createElement("div"));
el.appendChild(document.createElement("slot"));
document.querySelector("body").appendChild(el);
document.querySelector("body").removeChild(el);
});
it("ChildrenChangedMixin added DIV and then SLOT imperative and trigger childrenChangedCallback, mutation observer called between each invocation.", function (done) {
const Subclass = class Subclass extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(1);
expect(newChildren[0].nodeName).to.be.equal("DIV");
done();
}
};
customElements.define("children-changed-div-added-wait-and-then-slot-added", Subclass);
const el = new Subclass();
el.appendChild(document.createElement("div"));
el.appendChild(document.createElement("slot"));
document.querySelector("body").appendChild(el);
document.querySelector("body").removeChild(el);
});
it("The super inner-outer-slot test 1", function () {
const InnerElementThatObserveChildren = class extends ChildrenChangedMixin(HTMLElement) {
};
customElements.define("inner-component-1", InnerElementThatObserveChildren);
const OuterElementThatSlotsStuff = class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = `
<inner-component-1>
<slot></slot>
</inner-component-1>`;
}
};
customElements.define("outer-component-1", OuterElementThatSlotsStuff);
const outer = new OuterElementThatSlotsStuff();
const inner = outer.shadowRoot.children[0];
const innerSlot = inner.children[0];
assert(inner.getVisibleChildren().length === 0);
let slotted = document.createElement("div");
outer.appendChild(slotted);
assert(inner.getVisibleChildren().length === 1);
inner.removeChild(innerSlot);
assert(inner.getVisibleChildren().length === 0);
inner.appendChild(innerSlot);
assert(inner.getVisibleChildren().length === 1);
});
it("The super inner-outer-slot test 2", function (done) {
const InnerElementThatObserveChildren = class extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(1);
expect(newChildren[0].nodeName).to.be.equal("DIV");
done();
}
};
customElements.define("inner-component", InnerElementThatObserveChildren);
const OuterElementThatSlotsStuff = class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = `
<inner-component>
<slot></slot>
</inner-component>`;
}
};
customElements.define("outer-component", OuterElementThatSlotsStuff);
const el = new OuterElementThatSlotsStuff();
//things are not slotted until something is added to the DOM
document.querySelector("body").appendChild(el);
el.appendChild(document.createElement("div"));
document.querySelector("body").removeChild(el);
});
it("not listening for slotChange on slots that are not a direct child", function (done) {
const InnerElementThatObserveChildren = class extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(1);
expect(newChildren[0].nodeName).to.be.equal("DIV");
done();
}
};
customElements.define("inner-listener", InnerElementThatObserveChildren);
const OuterElementThatSlotsStuff = class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = `
<inner-listener>
<div>
<slot></slot>
</div>
</inner-listener>`;
}
};
customElements.define("outer-with-grandchild-slot", OuterElementThatSlotsStuff);
const el = new OuterElementThatSlotsStuff();
document.querySelector("body").appendChild(el);
el.appendChild(document.createElement("p"));
document.querySelector("body").removeChild(el);
});
it("isSlotChange", function (done) {
let counter = 0;
const InnerElementIsSlot = class extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren, isSlotchange) {
if (counter === 0) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(0);
assert(!isSlotchange);
counter++;
return;
}
if (counter === 1) {
expect(oldChildren.length).to.be.equal(0);
expect(newChildren.length).to.be.equal(1);
expect(newChildren[0].nodeName).to.be.equal("P");
assert(isSlotchange);
done();
}
}
};
customElements.define("inner-is-slot", InnerElementIsSlot);
const OuterElementIsSlot = class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = `
<inner-is-slot>
<slot></slot>
</inner-is-slot>`;
}
};
customElements.define("outer-is-slot", OuterElementIsSlot);
const el = new OuterElementIsSlot();
document.querySelector("body").appendChild(el);
Promise.resolve().then(() => {
el.appendChild(document.createElement("p"));
document.querySelector("body").removeChild(el);
});
});
it("connected-disconnected-connected. childrenChangedCallback only triggered when connected + MutationObserver only called once when micro task queued.", function (done) {
const Subclass = class Subclass extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(3);
expect(newChildren[0].nodeName).to.be.equal("DIV");
expect(newChildren[1].nodeName).to.be.equal("DIV");
expect(newChildren[2].nodeName).to.be.equal("DIV");
done();
}
};
customElements.define("connected-disconnected-connected", Subclass);
const el = new Subclass();
el.appendChild(document.createElement("div")); //is not triggered.
document.querySelector("body").appendChild(el); //childrenChangedCallback triggered on connect
document.querySelector("body").removeChild(el); //disconnect
el.appendChild(document.createElement("div")); //is not triggered.
el.appendChild(document.createElement("div")); //is not triggered.
document.querySelector("body").appendChild(el); //childrenChangedCallback triggered on connect
document.querySelector("body").removeChild(el);
});
it("connected-wait-disconnected-connected. childrenChangedCallback only triggered when connected.", function (done) {
let counter = 0;
const Subclass = class Subclass extends ChildrenChangedMixin(HTMLElement) {
childrenChangedCallback(oldChildren, newChildren) {
if (counter === 0) {
expect(oldChildren).to.be.equal(undefined);
expect(newChildren.length).to.be.equal(1);
expect(newChildren[0].nodeName).to.be.equal("DIV");
}
if (counter === 1) {
expect(oldChildren.length).to.be.equal(1);
expect(newChildren.length).to.be.equal(3);
expect(newChildren[0].nodeName).to.be.equal("DIV");
expect(newChildren[1].nodeName).to.be.equal("DIV");
expect(newChildren[2].nodeName).to.be.equal("DIV");
done();
}
counter++;
}
};
customElements.define("connected-settimeout-disconnected-connected", Subclass);
const el = new Subclass();
el.appendChild(document.createElement("div")); //is not triggered.
document.querySelector("body").appendChild(el); //childrenChangedCallback triggered on connect
document.querySelector("body").removeChild(el); //disconnect
Promise.resolve().then(() => {
el.appendChild(document.createElement("div")); //is not triggered.
el.appendChild(document.createElement("div")); //is not triggered.
document.querySelector("body").appendChild(el); //childrenChangedCallback triggered on connect
document.querySelector("body").removeChild(el);
});
});
//todo verify that eventListeners are removed when disconnected.
//todo make some tests showing that it does not go outside of its realm.. don't know how
});