diff options
Diffstat (limited to 'dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html')
-rw-r--r-- | dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html | 432 |
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> |