diff options
Diffstat (limited to 'testing/web-platform/tests/custom-elements/CustomElementRegistry.html')
-rw-r--r-- | testing/web-platform/tests/custom-elements/CustomElementRegistry.html | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/testing/web-platform/tests/custom-elements/CustomElementRegistry.html b/testing/web-platform/tests/custom-elements/CustomElementRegistry.html new file mode 100644 index 0000000000..5b75fc651f --- /dev/null +++ b/testing/web-platform/tests/custom-elements/CustomElementRegistry.html @@ -0,0 +1,755 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom Elements: CustomElementRegistry interface</title> +<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> +<meta name="assert" content="CustomElementRegistry interface must exist"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> + +test(function () { + assert_true('define' in CustomElementRegistry.prototype, '"define" exists on CustomElementRegistry.prototype'); + assert_true('define' in customElements, '"define" exists on window.customElements'); +}, 'CustomElementRegistry interface must have define as a method'); + +test(function () { + assert_throws_js(TypeError, function () { customElements.define('badname', 1); }, + 'customElements.define must throw a TypeError when the element interface is a number'); + assert_throws_js(TypeError, function () { customElements.define('badname', '123'); }, + 'customElements.define must throw a TypeError when the element interface is a string'); + assert_throws_js(TypeError, function () { customElements.define('badname', {}); }, + 'customElements.define must throw a TypeError when the element interface is an object'); + assert_throws_js(TypeError, function () { customElements.define('badname', []); }, + 'customElements.define must throw a TypeError when the element interface is an array'); +}, 'customElements.define must throw when the element interface is not a constructor'); + +test(function () { + customElements.define('custom-html-element', HTMLElement); +}, 'customElements.define must not throw the constructor is HTMLElement'); + +test(function () { + class MyCustomElement extends HTMLElement {}; + + assert_throws_dom('SyntaxError', function () { customElements.define(null, MyCustomElement); }, + 'customElements.define must throw a SyntaxError if the tag name is null'); + assert_throws_dom('SyntaxError', function () { customElements.define('', MyCustomElement); }, + 'customElements.define must throw a SyntaxError if the tag name is empty'); + assert_throws_dom('SyntaxError', function () { customElements.define('abc', MyCustomElement); }, + 'customElements.define must throw a SyntaxError if the tag name does not contain "-"'); + assert_throws_dom('SyntaxError', function () { customElements.define('a-Bc', MyCustomElement); }, + 'customElements.define must throw a SyntaxError if the tag name contains an upper case letter'); + + var builtinTagNames = [ + 'annotation-xml', + 'color-profile', + 'font-face', + 'font-face-src', + 'font-face-uri', + 'font-face-format', + 'font-face-name', + 'missing-glyph' + ]; + + for (var tagName of builtinTagNames) { + assert_throws_dom('SyntaxError', function () { customElements.define(tagName, MyCustomElement); }, + 'customElements.define must throw a SyntaxError if the tag name is "' + tagName + '"'); + } + +}, 'customElements.define must throw with an invalid name'); + +test(function () { + class SomeCustomElement extends HTMLElement {}; + + var calls = []; + var OtherCustomElement = new Proxy(class extends HTMLElement {}, { + get: function (target, name) { + calls.push(name); + return target[name]; + } + }) + + customElements.define('some-custom-element', SomeCustomElement); + assert_throws_dom('NotSupportedError', function () { customElements.define('some-custom-element', OtherCustomElement); }, + 'customElements.define must throw a NotSupportedError if the specified tag name is already used'); + assert_array_equals(calls, [], 'customElements.define must validate the custom element name before getting the prototype of the constructor'); + +}, 'customElements.define must throw when there is already a custom element of the same name'); + +test(function () { + class AnotherCustomElement extends HTMLElement {}; + + customElements.define('another-custom-element', AnotherCustomElement); + assert_throws_dom('NotSupportedError', function () { customElements.define('some-other-element', AnotherCustomElement); }, + 'customElements.define must throw a NotSupportedError if the specified class already defines an element'); + +}, 'customElements.define must throw a NotSupportedError when there is already a custom element with the same class'); + +test(function () { + var outerCalls = []; + var OuterCustomElement = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + outerCalls.push(name); + customElements.define('inner-custom-element', InnerCustomElement); + return target[name]; + } + }); + var innerCalls = []; + var InnerCustomElement = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + outerCalls.push(name); + return target[name]; + } + }); + + assert_throws_dom('NotSupportedError', function () { customElements.define('outer-custom-element', OuterCustomElement); }, + 'customElements.define must throw a NotSupportedError if the specified class already defines an element'); + assert_array_equals(outerCalls, ['prototype'], 'customElements.define must get "prototype"'); + assert_array_equals(innerCalls, [], + 'customElements.define must throw a NotSupportedError when element definition is running flag is set' + + ' before getting the prototype of the constructor'); + +}, 'customElements.define must throw a NotSupportedError when element definition is running flag is set'); + +test(function () { + var calls = []; + var ElementWithBadInnerConstructor = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + calls.push(name); + customElements.define('inner-custom-element', 1); + return target[name]; + } + }); + + assert_throws_js(TypeError, function () { + customElements.define('element-with-bad-inner-constructor', ElementWithBadInnerConstructor); + }, 'customElements.define must throw a NotSupportedError if IsConstructor(constructor) is false'); + + assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"'); +}, 'customElements.define must check IsConstructor on the constructor before checking the element definition is running flag'); + +test(function () { + var calls = []; + var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + calls.push(name); + customElements.define('badname', class extends HTMLElement {}); + return target[name]; + } + }); + + assert_throws_dom('SyntaxError', function () { + customElements.define('element-with-bad-inner-name', ElementWithBadInnerName); + }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name'); + + assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"'); +}, 'customElements.define must validate the custom element name before checking the element definition is running flag'); + +test(function () { + var unresolvedElement = document.createElement('constructor-calls-define'); + document.body.appendChild(unresolvedElement); + var elementUpgradedDuringUpgrade = document.createElement('defined-during-upgrade'); + document.body.appendChild(elementUpgradedDuringUpgrade); + + var DefinedDuringUpgrade = class extends HTMLElement { }; + + class ConstructorCallsDefine extends HTMLElement { + constructor() { + customElements.define('defined-during-upgrade', DefinedDuringUpgrade); + assert_false(unresolvedElement instanceof ConstructorCallsDefine); + assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade); + super(); + assert_true(unresolvedElement instanceof ConstructorCallsDefine); + assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade); + } + } + + assert_false(unresolvedElement instanceof ConstructorCallsDefine); + assert_false(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade); + + customElements.define('constructor-calls-define', ConstructorCallsDefine); +}, 'customElements.define unset the element definition is running flag before upgrading custom elements'); + +(function () { + var testCase = async_test('customElements.define must not throw' + +' when defining another custom element in a different global object during Get(constructor, "prototype")'); + + var iframe = document.createElement('iframe'); + iframe.onload = function () { + testCase.step(function () { + var InnerCustomElement = class extends iframe.contentWindow.HTMLElement {}; + var calls = []; + var proxy = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + calls.push(name); + if (name === "prototype") { + iframe.contentWindow.customElements.define('another-custom-element', InnerCustomElement); + } + return target[name]; + } + }) + customElements.define('element-with-inner-element-define', proxy); + assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated'], + 'customElements.define must get "prototype", "disabledFeatures", and "formAssociated" on the constructor'); + assert_true(iframe.contentDocument.createElement('another-custom-element') instanceof InnerCustomElement); + }); + document.body.removeChild(iframe); + testCase.done(); + } + + document.body.appendChild(iframe); +})(); + +test(function () { + var calls = []; + var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + calls.push(name); + customElements.define('badname', class extends HTMLElement {}); + return target[name]; + } + }); + + assert_throws_dom('SyntaxError', function () { + customElements.define('element-with-bad-inner-name', ElementWithBadInnerName); + }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name'); + + assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"'); +}, ''); + +test(function () { + var calls = []; + var proxy = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + calls.push(name); + return target[name]; + } + }); + customElements.define('proxy-element', proxy); + assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated']); +}, 'customElements.define must get "prototype", "disabledFeatures", and "formAssociated" property of the constructor'); + +test(function () { + var err = {name: 'expectedError'} + var proxy = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { + throw err; + } + }); + assert_throws_exactly(err, function () { customElements.define('element-with-string-prototype', proxy); }); +}, 'customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor'); + +test(function () { + var returnedValue; + var proxy = new Proxy(class extends HTMLElement { }, { + get: function (target, name) { return returnedValue; } + }); + + returnedValue = null; + assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, + 'customElements.define must throw when "prototype" property of the constructor is null'); + returnedValue = undefined; + assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, + 'customElements.define must throw when "prototype" property of the constructor is undefined'); + returnedValue = 'hello'; + assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, + 'customElements.define must throw when "prototype" property of the constructor is a string'); + returnedValue = 1; + assert_throws_js(TypeError, function () { customElements.define('element-with-string-prototype', proxy); }, + 'customElements.define must throw when "prototype" property of the constructor is a number'); + +}, 'customElements.define must throw when "prototype" property of the constructor is not an object'); + +test(function () { + var constructor = function () {} + var calls = []; + constructor.prototype = new Proxy(constructor.prototype, { + get: function (target, name) { + calls.push(name); + return target[name]; + } + }); + customElements.define('element-with-proxy-prototype', constructor); + assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback']); +}, 'customElements.define must get callbacks of the constructor prototype'); + +test(function () { + var constructor = function () {} + var calls = []; + var err = {name: 'expectedError'} + constructor.prototype = new Proxy(constructor.prototype, { + get: function (target, name) { + calls.push(name); + if (name == 'disconnectedCallback') + throw err; + return target[name]; + } + }); + assert_throws_exactly(err, function () { customElements.define('element-with-throwing-callback', constructor); }); + assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback'], + 'customElements.define must not get callbacks after one of the get throws'); +}, 'customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype'); + +test(function () { + var constructor = function () {} + var calls = []; + constructor.prototype = new Proxy(constructor.prototype, { + get: function (target, name) { + calls.push(name); + if (name == 'adoptedCallback') + return 1; + return target[name]; + } + }); + assert_throws_js(TypeError, function () { customElements.define('element-with-throwing-callback', constructor); }); + assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback'], + 'customElements.define must not get callbacks after one of the conversion throws'); +}, 'customElements.define must rethrow an exception thrown while converting a callback value to Function callback type'); + +test(function () { + var constructor = function () {} + constructor.prototype.attributeChangedCallback = function () { }; + var prototypeCalls = []; + var callOrder = 0; + constructor.prototype = new Proxy(constructor.prototype, { + get: function (target, name) { + if (name == 'prototype' || name == 'observedAttributes') + throw 'Unexpected access to observedAttributes'; + prototypeCalls.push(callOrder++); + prototypeCalls.push(name); + return target[name]; + } + }); + var constructorCalls = []; + var proxy = new Proxy(constructor, { + get: function (target, name) { + constructorCalls.push(callOrder++); + constructorCalls.push(name); + return target[name]; + } + }); + customElements.define('element-with-attribute-changed-callback', proxy); + assert_array_equals(prototypeCalls, [1, 'connectedCallback', 2, 'disconnectedCallback', 3, 'adoptedCallback', 4, 'attributeChangedCallback']); + assert_array_equals(constructorCalls, [0, 'prototype', + 5, 'observedAttributes', + 6, 'disabledFeatures', + 7, 'formAssociated']); +}, 'customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present'); + +test(function () { + var constructor = function () {} + constructor.prototype.attributeChangedCallback = function () { }; + var calls = []; + var err = {name: 'expectedError'}; + var proxy = new Proxy(constructor, { + get: function (target, name) { + calls.push(name); + if (name == 'observedAttributes') + throw err; + return target[name]; + } + }); + assert_throws_exactly(err, function () { customElements.define('element-with-throwing-observed-attributes', proxy); }); + assert_array_equals(calls, ['prototype', 'observedAttributes'], + 'customElements.define must get "prototype" and "observedAttributes" on the constructor'); +}, 'customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype'); + +test(function () { + var constructor = function () {} + constructor.prototype.attributeChangedCallback = function () { }; + var calls = []; + var proxy = new Proxy(constructor, { + get: function (target, name) { + calls.push(name); + if (name == 'observedAttributes') + return 1; + return target[name]; + } + }); + assert_throws_js(TypeError, function () { customElements.define('element-with-invalid-observed-attributes', proxy); }); + assert_array_equals(calls, ['prototype', 'observedAttributes'], + 'customElements.define must get "prototype" and "observedAttributes" on the constructor'); +}, 'customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>'); + + test(function () { + var err = {name: 'SomeError'}; + var constructor = function () {} + constructor.prototype.attributeChangedCallback = function () { }; + constructor.observedAttributes = {[Symbol.iterator]: function *() { + yield 'foo'; + throw err; + }}; + assert_throws_exactly(err, function () { customElements.define('element-with-generator-observed-attributes', constructor); }); +}, 'customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>'); + +test(function () { + var constructor = function () {} + constructor.prototype.attributeChangedCallback = function () { }; + constructor.observedAttributes = {[Symbol.iterator]: 1}; + assert_throws_js(TypeError, function () { customElements.define('element-with-observed-attributes-with-uncallable-iterator', constructor); }); +}, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes'); + +test(function () { + var constructor = function () {} + constructor.observedAttributes = 1; + customElements.define('element-without-callback-with-invalid-observed-attributes', constructor); +}, 'customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined'); + +test(function () { + var constructor = function () {} + var calls = []; + var err = {name: 'expectedError'} + var proxy = new Proxy(constructor, { + get: function (target, name) { + calls.push(name); + if (name == 'disabledFeatures') + throw err; + return target[name]; + } + }); + assert_throws_exactly(err, () => customElements.define('element-with-throwing-disabled-features', proxy)); + assert_array_equals(calls, ['prototype', 'disabledFeatures'], + 'customElements.define must get "prototype" and "disabledFeatures" on the constructor'); +}, 'customElements.define must rethrow an exception thrown while getting disabledFeatures on the constructor prototype'); + +test(function () { + var constructor = function () {} + var calls = []; + var proxy = new Proxy(constructor, { + get: function (target, name) { + calls.push(name); + if (name == 'disabledFeatures') + return 1; + return target[name]; + } + }); + assert_throws_js(TypeError, () => customElements.define('element-with-invalid-disabled-features', proxy)); + assert_array_equals(calls, ['prototype', 'disabledFeatures'], + 'customElements.define must get "prototype" and "disabledFeatures" on the constructor'); +}, 'customElements.define must rethrow an exception thrown while converting the value of disabledFeatures to sequence<DOMString>'); + +test(function () { + var constructor = function () {} + var err = {name: 'SomeError'}; + constructor.disabledFeatures = {[Symbol.iterator]: function *() { + yield 'foo'; + throw err; + }}; + assert_throws_exactly(err, () => customElements.define('element-with-generator-disabled-features', constructor)); +}, 'customElements.define must rethrow an exception thrown while iterating over disabledFeatures to sequence<DOMString>'); + +test(function () { + var constructor = function () {} + constructor.disabledFeatures = {[Symbol.iterator]: 1}; + assert_throws_js(TypeError, () => customElements.define('element-with-disabled-features-with-uncallable-iterator', constructor)); +}, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on disabledFeatures'); + +test(function () { + var constructor = function () {} + var calls = []; + var err = {name: 'expectedError'}; + var proxy = new Proxy(constructor, { + get: function (target, name) { + calls.push(name); + if (name == 'formAssociated') + throw err; + return target[name]; + } + }); + assert_throws_exactly(err, + () => customElements.define('element-with-throwing-form-associated', proxy)); + assert_array_equals(calls, ['prototype', 'disabledFeatures', 'formAssociated'], + 'customElements.define must get "prototype", "disabledFeatures", and ' + + '"formAssociated" on the constructor'); +}, 'customElements.define must rethrow an exception thrown while getting ' + + 'formAssociated on the constructor prototype'); + +test(function () { + var constructor = function () {} + var prototypeCalls = []; + constructor.prototype = new Proxy(constructor.prototype, { + get: function(target, name) { + prototypeCalls.push(name) + return target[name]; + } + }); + var constructorCalls = []; + var proxy = new Proxy(constructor, { + get: function (target, name) { + constructorCalls.push(name); + if (name == 'formAssociated') + return 1; + return target[name]; + } + }); + customElements.define('element-with-form-associated-true', proxy); + assert_array_equals(constructorCalls, + ['prototype', 'disabledFeatures', 'formAssociated'], + 'customElements.define must get "prototype", "disabledFeatures", and ' + + '"formAssociated" on the constructor'); + assert_array_equals( + prototypeCalls, + ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', + 'attributeChangedCallback', 'formAssociatedCallback', + 'formResetCallback', 'formDisabledCallback', + 'formStateRestoreCallback'], + 'customElements.define must get 8 callbacks on the prototype'); +}, 'customElements.define must get four additional callbacks on the prototype' + + ' if formAssociated is converted to true'); + +test(function () { + var constructor = function() {}; + var proxy = new Proxy(constructor, { + get: function(target, name) { + if (name == 'formAssociated') + return {}; // Any object is converted to 'true'. + return target[name]; + } + }); + var calls = []; + var err = {name: 'expectedError'}; + constructor.prototype = new Proxy(constructor.prototype, { + get: function (target, name) { + calls.push(name); + if (name == 'formDisabledCallback') + throw err; + return target[name]; + } + }); + assert_throws_exactly(err, + () => customElements.define('element-with-throwing-callback-2', proxy)); + assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', + 'adoptedCallback', 'attributeChangedCallback', + 'formAssociatedCallback', 'formResetCallback', + 'formDisabledCallback'], + 'customElements.define must not get callbacks after one of the get throws'); + + var calls2 = []; + constructor.prototype = new Proxy(constructor.prototype, { + get: function (target, name) { + calls2.push(name); + if (name == 'formResetCallback') + return 43; // Can't convert to a Function. + return target[name]; + } + }); + assert_throws_js(TypeError, + () => customElements.define('element-with-throwing-callback-3', proxy)); + assert_array_equals(calls2, ['connectedCallback', 'disconnectedCallback', + 'adoptedCallback', 'attributeChangedCallback', + 'formAssociatedCallback', 'formResetCallback'], + 'customElements.define must not get callbacks after one of the get throws'); +}, 'customElements.define must rethrow an exception thrown while getting ' + + 'additional formAssociated callbacks on the constructor prototype'); + +test(function () { + class MyCustomElement extends HTMLElement {}; + customElements.define('my-custom-element', MyCustomElement); + + var instance = new MyCustomElement; + assert_true(instance instanceof MyCustomElement, + 'An instance of a custom HTML element be an instance of the associated interface'); + + assert_true(instance instanceof HTMLElement, + 'An instance of a custom HTML element must inherit from HTMLElement'); + + assert_equals(instance.localName, 'my-custom-element', + 'An instance of a custom element must use the associated tag name'); + + assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml', + 'A custom element HTML must use HTML namespace'); + +}, 'customElements.define must define an instantiatable custom element'); + +test(function () { + var disconnectedElement = document.createElement('some-custom'); + var connectedElementBeforeShadowHost = document.createElement('some-custom'); + var connectedElementAfterShadowHost = document.createElement('some-custom'); + var elementInShadowTree = document.createElement('some-custom'); + var childElementOfShadowHost = document.createElement('some-custom'); + var customShadowHost = document.createElement('some-custom'); + var elementInNestedShadowTree = document.createElement('some-custom'); + + var container = document.createElement('div'); + var shadowHost = document.createElement('div'); + var shadowRoot = shadowHost.attachShadow({mode: 'closed'}); + container.appendChild(connectedElementBeforeShadowHost); + container.appendChild(shadowHost); + container.appendChild(connectedElementAfterShadowHost); + shadowHost.appendChild(childElementOfShadowHost); + shadowRoot.appendChild(elementInShadowTree); + shadowRoot.appendChild(customShadowHost); + + var innerShadowRoot = customShadowHost.attachShadow({mode: 'closed'}); + innerShadowRoot.appendChild(elementInNestedShadowTree); + + var calls = []; + class SomeCustomElement extends HTMLElement { + constructor() { + super(); + calls.push(this); + } + }; + + document.body.appendChild(container); + customElements.define('some-custom', SomeCustomElement); + assert_array_equals(calls, [connectedElementBeforeShadowHost, elementInShadowTree, customShadowHost, elementInNestedShadowTree, childElementOfShadowHost, connectedElementAfterShadowHost]); +}, 'customElements.define must upgrade elements in the shadow-including tree order'); + +test(function () { + assert_true('get' in CustomElementRegistry.prototype, '"get" exists on CustomElementRegistry.prototype'); + assert_true('get' in customElements, '"get" exists on window.customElements'); +}, 'CustomElementRegistry interface must have get as a method'); + +test(function () { + assert_equals(customElements.get('a-b'), undefined); +}, 'customElements.get must return undefined when the registry does not contain an entry with the given name'); + +test(function () { + assert_equals(customElements.get('html'), undefined); + assert_equals(customElements.get('span'), undefined); + assert_equals(customElements.get('div'), undefined); + assert_equals(customElements.get('g'), undefined); + assert_equals(customElements.get('ab'), undefined); +}, 'customElements.get must return undefined when the registry does not contain an entry with the given name even if the name was not a valid custom element name'); + +test(function () { + assert_equals(customElements.get('existing-custom-element'), undefined); + class ExistingCustomElement extends HTMLElement {}; + customElements.define('existing-custom-element', ExistingCustomElement); + assert_equals(customElements.get('existing-custom-element'), ExistingCustomElement); +}, 'customElements.get return the constructor of the entry with the given name when there is a matching entry.'); + +test(function () { + assert_true(customElements.whenDefined('some-name') instanceof Promise); +}, 'customElements.whenDefined must return a promise for a valid custom element name'); + +test(function () { + assert_equals(customElements.whenDefined('some-name'), customElements.whenDefined('some-name')); +}, 'customElements.whenDefined must return the same promise each time invoked for a valid custom element name which has not been defined'); + +promise_test(function () { + var resolved = false; + var rejected = false; + customElements.whenDefined('a-b').then(function () { resolved = true; }, function () { rejected = true; }); + return Promise.resolve().then(function () { + assert_false(resolved, 'The promise returned by "whenDefined" must not be resolved until a custom element is defined'); + assert_false(rejected, 'The promise returned by "whenDefined" must not be rejected until a custom element is defined'); + }); +}, 'customElements.whenDefined must return an unresolved promise when the registry does not contain the entry with the given name') + +promise_test(function () { + var promise = customElements.whenDefined('badname'); + promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; }); + + assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); + assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); + + return Promise.resolve().then(function () { + assert_false('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); + assert_true('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined'); + }); +}, 'customElements.whenDefined must return a rejected promise when the given name is not a valid custom element name'); + +promise_test(function () { + class PreexistingCustomElement extends HTMLElement { }; + customElements.define('preexisting-custom-element', PreexistingCustomElement); + + var promise = customElements.whenDefined('preexisting-custom-element'); + promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; }); + + assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); + assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); + + return Promise.resolve().then(function () { + assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); + assert_equals(promise.resolved, PreexistingCustomElement, + 'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined'); + assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined'); + }); +}, 'customElements.whenDefined must return a resolved promise when the registry contains the entry with the given name'); + +promise_test(function () { + class AnotherExistingCustomElement extends HTMLElement {}; + customElements.define('another-existing-custom-element', AnotherExistingCustomElement); + + var promise1 = customElements.whenDefined('another-existing-custom-element'); + var promise2 = customElements.whenDefined('another-existing-custom-element'); + promise1.then(function (value) { promise1.resolved = value; }, function (value) { promise1.rejected = value; }); + promise2.then(function (value) { promise2.resolved = value; }, function (value) { promise2.rejected = value; }); + + assert_not_equals(promise1, promise2); + assert_false('resolved' in promise1, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); + assert_false('resolved' in promise2, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); + assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); + assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); + + return Promise.resolve().then(function () { + assert_true('resolved' in promise1, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); + assert_equals(promise1.resolved, AnotherExistingCustomElement, 'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined'); + assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined'); + + assert_true('resolved' in promise2, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); + assert_equals(promise2.resolved, AnotherExistingCustomElement, 'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined'); + assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined'); + }); +}, 'customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name'); + +promise_test(function () { + class ElementDefinedAfterWhenDefined extends HTMLElement { }; + var promise = customElements.whenDefined('element-defined-after-whendefined'); + promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; }); + + assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); + assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); + + var promiseAfterDefine; + return Promise.resolve().then(function () { + assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the element is defined'); + assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the element is defined'); + assert_equals(customElements.whenDefined('element-defined-after-whendefined'), promise, + '"whenDefined" must return the same unresolved promise before the custom element is defined'); + customElements.define('element-defined-after-whendefined', ElementDefinedAfterWhenDefined); + assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); + assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); + + promiseAfterDefine = customElements.whenDefined('element-defined-after-whendefined'); + promiseAfterDefine.then(function (value) { promiseAfterDefine.resolved = value; }, function (value) { promiseAfterDefine.rejected = value; }); + assert_not_equals(promiseAfterDefine, promise, '"whenDefined" must return a resolved promise once the custom element is defined'); + assert_false('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask'); + assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask'); + }).then(function () { + assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); + assert_equals(promise.resolved, ElementDefinedAfterWhenDefined, + 'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined'); + assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined'); + + assert_true('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must be resolved when a custom element is defined'); + assert_equals(promiseAfterDefine.resolved, ElementDefinedAfterWhenDefined, + 'The promise returned by "whenDefined" must be resolved with the constructor of the element when a custom element is defined'); + assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined'); + }); +}, 'A promise returned by customElements.whenDefined must be resolved by "define"'); + +promise_test(function () { + class ResolvedCustomElement extends HTMLElement {}; + customElements.define('resolved-custom-element', ResolvedCustomElement); + return customElements.whenDefined('resolved-custom-element').then(function (value) { + assert_equals(value, ResolvedCustomElement, 'The promise returned by "whenDefined" must resolve with the defined class'); + }); +}, 'A promise returned by customElements.whenDefined must be resolved with the defined class'); + +promise_test(function () { + var promise = customElements.whenDefined('not-resolved-yet-custom-element').then(function (value) { + assert_equals(value, NotResolvedYetCustomElement, 'The promise returned by "whenDefined" must resolve with the defined class once such class is defined'); + }); + class NotResolvedYetCustomElement extends HTMLElement {}; + customElements.define('not-resolved-yet-custom-element', NotResolvedYetCustomElement); + return promise; +}, 'A promise returned by customElements.whenDefined must be resolved with the defined class once such class is defined'); +</script> +</body> +</html> |