Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE HTML>
<html>
<!--
-->
<head>
<title>Test for custom elements lifecycle callback</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<div id="container">
<x-hello id="hello"></x-hello>
<button id="extbutton" is="x-button"></button>
</div>
<script>
var container = document.getElementById("container");
// Tests callbacks after defining element type that is already in the document.
// create element in document -> define -> remove from document
function testRegisterUnresolved() {
var helloElem = document.getElementById("hello");
var connectedCallbackCalled = false;
var disconnectedCallbackCalled = false;
class Hello extends HTMLElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
is(this, helloElem, "The 'this' value should be the custom element.");
connectedCallbackCalled = true;
}
disconnectedCallback() {
is(connectedCallbackCalled, true, "Connected callback should be called before detached");
is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
disconnectedCallbackCalled = true;
is(this, helloElem, "The 'this' value should be the custom element.");
runNextTest();
}
attributeChangedCallback(name, oldValue, newValue) {
ok(false, "attributeChanged callback should never be called in this test.");
}
};
customElements.define("x-hello", Hello);
// Remove element from document to trigger disconnected callback.
container.removeChild(helloElem);
}
// Tests callbacks after defining an extended element type that is already in the document.
// create element in document -> define -> remove from document
function testRegisterUnresolvedExtended() {
var buttonElem = document.getElementById("extbutton");
var connectedCallbackCalled = false;
var disconnectedCallbackCalled = false;
class XButton extends HTMLButtonElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
is(this, buttonElem, "The 'this' value should be the custom element.");
connectedCallbackCalled = true;
}
disconnectedCallback() {
is(connectedCallbackCalled, true, "Connected callback should be called before detached");
is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
disconnectedCallbackCalled = true;
is(this, buttonElem, "The 'this' value should be the custom element.");
runNextTest();
}
attributeChangedCallback(name, oldValue, newValue) {
ok(false, "attributeChanged callback should never be called in this test.");
}
};
customElements.define("x-button", XButton, { extends: "button" });
// Remove element from document to trigger disconnected callback.
container.removeChild(buttonElem);
}
function testInnerHTML() {
var connectedCallbackCalled = false;
class XInnerHTML extends HTMLElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
connectedCallbackCalled = true;
}
};
customElements.define("x-inner-html", XInnerHTML);
var div = document.createElement(div);
document.documentElement.appendChild(div);
div.innerHTML = '<x-inner-html></x-inner-html>';
is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML.");
runNextTest();
}
function testInnerHTMLExtended() {
var connectedCallbackCalled = false;
class XInnerHTMLExtend extends HTMLButtonElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
connectedCallbackCalled = true;
}
};
customElements.define("x-inner-html-extended", XInnerHTMLExtend, { extends: "button" });
var div = document.createElement(div);
document.documentElement.appendChild(div);
div.innerHTML = '<button is="x-inner-html-extended"></button>';
is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML.");
runNextTest();
}
function testInnerHTMLUpgrade() {
var connectedCallbackCalled = false;
var div = document.createElement(div);
document.documentElement.appendChild(div);
div.innerHTML = '<x-inner-html-upgrade></x-inner-html-upgrade>';
class XInnerHTMLUpgrade extends HTMLElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
connectedCallbackCalled = true;
}
};
customElements.define("x-inner-html-upgrade", XInnerHTMLUpgrade);
is(connectedCallbackCalled, true, "Connected callback should be called after registering.");
runNextTest();
}
function testInnerHTMLExtendedUpgrade() {
var connectedCallbackCalled = false;
var div = document.createElement(div);
document.documentElement.appendChild(div);
div.innerHTML = '<button is="x-inner-html-extended-upgrade"></button>';
class XInnerHTMLExtnedUpgrade extends HTMLButtonElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
connectedCallbackCalled = true;
}
};
customElements.define("x-inner-html-extended-upgrade", XInnerHTMLExtnedUpgrade, { extends: "button" });
is(connectedCallbackCalled, true, "Connected callback should be called after registering.");
runNextTest();
}
// Test callback when creating element after defining an element type.
// define -> create element -> insert into document -> remove from document
function testRegisterResolved() {
var connectedCallbackCalled = false;
var disconnectedCallbackCalled = false;
class Resolved extends HTMLElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called on in this test.");
is(this, createdElement, "The 'this' value should be the custom element.");
connectedCallbackCalled = true;
}
disconnectedCallback() {
is(connectedCallbackCalled, true, "Connected callback should be called before detached");
is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
is(this, createdElement, "The 'this' value should be the custom element.");
disconnectedCallbackCalled = true;
runNextTest();
}
attributeChangedCallback() {
ok(false, "attributeChanged callback should never be called in this test.");
}
};
customElements.define("x-resolved", Resolved);
var createdElement = document.createElement("x-resolved");
is(createdElement.__proto__, Resolved.prototype, "Prototype of custom element should be the defined prototype.");
// Insert element into document to trigger attached callback.
container.appendChild(createdElement);
// Remove element from document to trigger detached callback.
container.removeChild(createdElement);
}
// Callbacks should always be the same ones when registered.
function testChangingCallback() {
var callbackCalled = false;
class TestCallback extends HTMLElement
{
attributeChangedCallback(aName, aOldValue, aNewValue) {
is(callbackCalled, false, "Callback should only be called once in this test.");
callbackCalled = true;
runNextTest();
}
static get observedAttributes() {
return ["data-foo"];
}
}
customElements.define("x-test-callback", TestCallback);
TestCallback.prototype.attributeChangedCallback = function(name, oldValue, newValue) {
ok(false, "Only callbacks at registration should be called.");
};
var elem = document.createElement("x-test-callback");
elem.setAttribute("data-foo", "bar");
}
function testAttributeChanged() {
var createdElement;
// Sequence of callback arguments that we expect from attribute changed callback
// after changing attributes values in a specific order.
var expectedCallbackArguments = [
// [oldValue, newValue]
[null, "newvalue"], // Setting the attribute value to "newvalue"
["newvalue", "nextvalue"], // Changing the attribute value from "newvalue" to "nextvalue"
["nextvalue", ""], // Changing the attribute value from "nextvalue" to empty string
["", null], // Removing the attribute.
];
class AttrChange extends HTMLElement
{
attributeChangedCallback(name, oldValue, newValue) {
is(this, createdElement, "The 'this' value should be the custom element.");
ok(expectedCallbackArguments.length, "Attribute changed callback should not be called more than expected.");
is(name, "changeme", "name arugment in attribute changed callback should be the name of the changed attribute.");
var expectedArgs = expectedCallbackArguments.shift();
is(oldValue, expectedArgs[0], "The old value argument should match the expected value.");
is(newValue, expectedArgs[1], "The new value argument should match the expected value.");
if (expectedCallbackArguments.length === 0) {
// Done with the attribute changed callback test.
runNextTest();
}
}
static get observedAttributes() {
return ["changeme"];
}
}
customElements.define("x-attrchange", AttrChange);
createdElement = document.createElement("x-attrchange");
createdElement.setAttribute("changeme", "newvalue");
createdElement.setAttribute("changeme", "nextvalue");
createdElement.setAttribute("changeme", "");
createdElement.removeAttribute("changeme");
}
function testAttributeChangedExtended() {
var callbackCalled = false;
class ExtnededAttributeChange extends HTMLButtonElement
{
attributeChangedCallback(name, oldValue, newValue) {
is(callbackCalled, false, "Callback should only be called once in this test.");
callbackCalled = true;
runNextTest();
}
static get observedAttributes() {
return ["foo"];
}
}
customElements.define("x-extended-attribute-change", ExtnededAttributeChange,
{ extends: "button" });
var elem = document.createElement("button", {is: "x-extended-attribute-change"});
elem.setAttribute("foo", "bar");
}
function testStyleAttributeChange() {
var expectedCallbackArguments = [
// [name, oldValue, newValue]
["style", null, "font-size: 10px;"],
["style", "font-size: 10px;", "font-size: 20px;"],
["style", "font-size: 20px;", "font-size: 30px;"],
];
customElements.define("x-style-attribute-change", class extends HTMLElement {
attributeChangedCallback(name, oldValue, newValue) {
if (expectedCallbackArguments.length === 0) {
ok(false, "Got unexpected attributeChangedCallback?");
return;
}
let expectedArgument = expectedCallbackArguments.shift();
is(name, expectedArgument[0],
"The name argument should match the expected value.");
is(oldValue, expectedArgument[1],
"The old value argument should match the expected value.");
is(newValue, expectedArgument[2],
"The new value argument should match the expected value.");
}
static get observedAttributes() {
return ["style"];
}
});
var elem = document.createElement("x-style-attribute-change");
elem.style.fontSize = "10px";
elem.style.fontSize = "20px";
elem.style.fontSize = "30px";
ok(expectedCallbackArguments.length === 0,
"The attributeChangedCallback should be fired synchronously.");
runNextTest();
}
// Creates a custom element that is an upgrade candidate (no registration)
// and mutate the element in ways that would call callbacks for registered
// elements.
function testUpgradeCandidate() {
var createdElement = document.createElement("x-upgrade-candidate");
container.appendChild(createdElement);
createdElement.setAttribute("foo", "bar");
container.removeChild(createdElement);
ok(true, "Nothing bad should happen when trying to mutating upgrade candidates.");
runNextTest();
}
function testNotInDocEnterLeave() {
class DestinedForFragment extends HTMLElement {
connectedCallback() {
ok(false, "Connected callback should not be called.");
}
disconnectedCallback() {
ok(false, "Disconnected callback should not be called.");
}
};
var createdElement = document.createElement("x-destined-for-fragment");
customElements.define("x-destined-for-fragment", DestinedForFragment);
var fragment = new DocumentFragment();
fragment.appendChild(createdElement);
fragment.removeChild(createdElement);
var divNotInDoc = document.createElement("div");
divNotInDoc.appendChild(createdElement);
divNotInDoc.removeChild(createdElement);
runNextTest();
}
function testEnterLeaveView() {
var connectedCallbackCalled = false;
var disconnectedCallbackCalled = false;
class ElementInDiv extends HTMLElement {
connectedCallback() {
is(connectedCallbackCalled, false, "Connected callback should only be called on in this test.");
connectedCallbackCalled = true;
}
disconnectedCallback() {
is(connectedCallbackCalled, true, "Connected callback should be called before detached");
is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
disconnectedCallbackCalled = true;
runNextTest();
}
};
var div = document.createElement("div");
customElements.define("x-element-in-div", ElementInDiv);
var customElement = document.createElement("x-element-in-div");
div.appendChild(customElement);
is(connectedCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the connected callback.");
container.appendChild(div);
container.removeChild(div);
}
var testFunctions = [
testRegisterUnresolved,
testRegisterUnresolvedExtended,
testInnerHTML,
testInnerHTMLExtended,
testInnerHTMLUpgrade,
testInnerHTMLExtendedUpgrade,
testRegisterResolved,
testAttributeChanged,
testAttributeChangedExtended,
testStyleAttributeChange,
testUpgradeCandidate,
testChangingCallback,
testNotInDocEnterLeave,
testEnterLeaveView,
SimpleTest.finish
];
function runNextTest() {
if (testFunctions.length) {
var nextTestFunction = testFunctions.shift();
info(`Start ${nextTestFunction.name} ...`);
nextTestFunction();
}
}
SimpleTest.waitForExplicitFinish();
runNextTest();
</script>
</body>
</html>