432 lines
14 KiB
HTML
432 lines
14 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<!--
|
|
https://bugzilla.mozilla.org/show_bug.cgi?id=783129
|
|
-->
|
|
<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>
|
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
|
|
<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>
|