summaryrefslogtreecommitdiffstats
path: root/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html')
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html432
1 files changed, 432 insertions, 0 deletions
diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html b/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html
new file mode 100644
index 0000000000..a02d0b297b
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html
@@ -0,0 +1,432 @@
+<!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>