Skip to content

Commit

Permalink
Export WebKit's tests for revamped scoped custom element registry
Browse files Browse the repository at this point in the history
  • Loading branch information
rniwa authored Feb 21, 2025
1 parent fa7d049 commit ab51b76
Show file tree
Hide file tree
Showing 11 changed files with 856 additions and 0 deletions.
57 changes: 57 additions & 0 deletions custom-elements/revamped-scoped-registry/Construct.tentative.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<meta name="author" title="Ryosuke Niwa" href="mailto:[email protected]">
<link rel="help" href="https://github.com/whatwg/html/issues/10854">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>

test(() => {
class ABElement extends HTMLElement { };
const scopedRegistry = new CustomElementRegistry;
scopedRegistry.define('a-b', ABElement);
assert_throws_js(TypeError, () => new ABElement);
}, 'A constructor with only a scoped custom element registry definition should fail upon construction');

test(() => {
class CElement extends HTMLElement { };
const scopedRegistry = new CustomElementRegistry;
scopedRegistry.define('scoped-c', CElement);
customElements.define('global-c', CElement);
const cElement = new CElement;
assert_equals(cElement.localName, 'global-c');
}, 'A constructor uses the global registry to create an element');

test(() => {
let fgElement;
let hiElement;
class DEElement extends HTMLElement {
constructor() {
fgElement = document.createElement('f-g', {customElements: scopedRegistry2});
super();
hiElement = document.createElement('h-i', {customElements: scopedRegistry2});
}
};
class FGElement extends HTMLElement { }
class HIElement extends HTMLElement { }
const scopedRegistry1 = new CustomElementRegistry;
scopedRegistry1.define('d-e', DEElement);
const scopedRegistry2 = new CustomElementRegistry;
scopedRegistry2.define('f-g', FGElement);
scopedRegistry2.define('h-i', HIElement);

const deElement = document.createElement('d-e', {customElements: scopedRegistry1});
assert_true(deElement instanceof DEElement);
assert_equals(deElement.customElements, scopedRegistry1);
assert_true(fgElement instanceof FGElement);
assert_equals(fgElement.customElements, scopedRegistry2);
assert_true(hiElement instanceof HIElement);
assert_equals(hiElement.customElements, scopedRegistry2);
}, 'A constructor creating an element from another registry before or after super call should work');

</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta name="author" title="Ryosuke Niwa" href="mailto:[email protected]">
<link rel="help" href="https://github.com/whatwg/html/issues/10854">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
test(() => {
let registry = new CustomElementRegistry();
assert_not_equals(registry, window.customElements);
}, 'Create a CustomElementRegistry not identically equal to window.customElements');

test(() => {
let registry = new CustomElementRegistry();
window.customElements.define('a-b', class extends HTMLElement {});
assert_equals(registry.get('a-b'), undefined);
}, 'Defining an element in the global registry does not add a definition to a scoped CustomElementRegistry');

test(() => {
let registry = new CustomElementRegistry();
registry.define('b-c', class extends HTMLElement {});
assert_equals(window.customElements.get('b-c'), undefined);
}, 'Defining an element in a scoped global registry does not add a definition to the global registry');

</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<meta name="author" title="Ryosuke Niwa" href="mailto:[email protected]">
<link rel="help" href="https://github.com/whatwg/html/issues/10854">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="host">
<template shadowrootmode="open" shadowrootclonable="true" shadowrootcustomelements>
<a-b>
<template shadowrootmode="open" shadowrootclonable="true" shadowrootcustomelements>
<c-d/><c-d>
</template>
</a-b>
<ef></ef>
</template>
</div>
<div id="host-with-registry">
<template shadowrootmode="open" shadowrootclonable="true">
<a-b></a-b>
<ef></ef>
</template>
</div>
<script>

test(() => {
assert_equals(typeof(window.customElements.initialize), 'function');
assert_equals(typeof((new CustomElementRegistry).initialize), 'function');
}, 'initialize is a function on both global and scoped CustomElementRegistry');

test(() => {
const clone = host.cloneNode(true);
const shadowRoot = clone.shadowRoot;
assert_equals(shadowRoot.customElements, null);
assert_equals(shadowRoot.querySelector('a-b').customElements, null);
assert_equals(shadowRoot.querySelector('ef').customElements, null);
window.customElements.initialize(shadowRoot);
assert_equals(shadowRoot.customElements, window.customElements);
assert_equals(shadowRoot.querySelector('a-b').customElements, window.customElements);
assert_equals(shadowRoot.querySelector('ef').customElements, window.customElements);
}, 'initialize sets element.customElements to the global registry');

test(() => {
const clone = host.cloneNode(true);
const shadowRoot = clone.shadowRoot;
assert_equals(shadowRoot.customElements, null);
assert_equals(shadowRoot.querySelector('a-b').customElements, null);
assert_equals(shadowRoot.querySelector('ef').customElements, null);
window.customElements.initialize(shadowRoot);
assert_equals(shadowRoot.customElements, window.customElements);
assert_equals(shadowRoot.querySelector('a-b').customElements, window.customElements);
assert_equals(shadowRoot.querySelector('ef').customElements, window.customElements);
assert_equals(shadowRoot.querySelector('a-b').shadowRoot.customElements, null);
assert_equals(shadowRoot.querySelector('a-b').shadowRoot.querySelector('c-d').customElements, null);
}, 'initialize does not set the registry of nested shadow tree to the global registry');


test(() => {
const clone = host.cloneNode(true);
const shadowRoot = clone.shadowRoot;
assert_equals(shadowRoot.customElements, null);
assert_equals(shadowRoot.querySelector('a-b').customElements, null);
assert_equals(shadowRoot.querySelector('ef').customElements, null);
const registry = new CustomElementRegistry;
registry.initialize(shadowRoot);
assert_equals(shadowRoot.customElements, registry);
assert_equals(shadowRoot.querySelector('a-b').customElements, registry);
assert_equals(shadowRoot.querySelector('ef').customElements, registry);
}, 'initialize sets element.customElements to a scoped registry');

test(() => {
const clone = host.cloneNode(true);
const shadowRoot = clone.shadowRoot;
assert_equals(shadowRoot.customElements, null);
assert_equals(shadowRoot.querySelector('a-b').customElements, null);
assert_equals(shadowRoot.querySelector('ef').customElements, null);
const registry = new CustomElementRegistry;
registry.initialize(shadowRoot);
assert_equals(shadowRoot.customElements, registry);
assert_equals(shadowRoot.querySelector('a-b').customElements, registry);
assert_equals(shadowRoot.querySelector('ef').customElements, registry);
assert_equals(shadowRoot.querySelector('a-b').shadowRoot.customElements, null);
assert_equals(shadowRoot.querySelector('a-b').shadowRoot.querySelector('c-d').customElements, null);
}, 'initialize does not set the registry of nested shadow tree to a scoped registry');

test(() => {
const clone = host.cloneNode(true);
const shadowRoot = clone.shadowRoot;
assert_equals(shadowRoot.customElements, null);
assert_equals(shadowRoot.querySelector('a-b').customElements, null);
assert_equals(shadowRoot.querySelector('ef').customElements, null);
const registry = new CustomElementRegistry;
registry.initialize(shadowRoot);
assert_equals(shadowRoot.customElements, registry);
assert_equals(shadowRoot.querySelector('a-b').customElements, registry);
assert_equals(shadowRoot.querySelector('ef').customElements, registry);
document.body.appendChild(clone);
assert_equals(shadowRoot.customElements, registry);
assert_equals(shadowRoot.querySelector('a-b').customElements, registry);
assert_equals(shadowRoot.querySelector('ef').customElements, registry);
}, 'initialize sets element.customElements permantently');

test(() => {
const clone = document.getElementById('host-with-registry').cloneNode(true);
const shadowRoot = clone.shadowRoot;
assert_equals(shadowRoot.customElements, window.customElements);
assert_equals(shadowRoot.querySelector('a-b').customElements, window.customElements);
assert_equals(shadowRoot.querySelector('ef').customElements, window.customElements);
const registry = new CustomElementRegistry;
registry.initialize(shadowRoot);
assert_equals(shadowRoot.customElements, window.customElements);
assert_equals(shadowRoot.querySelector('a-b').customElements, window.customElements);
assert_equals(shadowRoot.querySelector('ef').customElements, window.customElements);
}, 'initialize is no-op on a subtree with a non-null registry');

</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<meta name="author" title="Ryosuke Niwa" href="mailto:[email protected]">
<link rel="help" href="https://github.com/whatwg/html/issues/10854">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<some-host id="host">
<template shadowrootmode="closed" shadowrootclonable="true" shadowrootcustomelements>
<a-b>
<template shadowrootmode="closed" shadowrootclonable="true" shadowrootcustomelements>
<c-d/>
<template shadowrootmode="closed" shadowrootclonable="true">
<a-b></a-b>
</template>
<c-d>
</template>
</a-b>
</template>
</some-host>
<script>

customElements.define('some-host', class SomeHost extends HTMLElement {
elementInternals;

constructor() {
super();
this.elementInternals = this.attachInternals();
}
});
customElements.define('a-b', class GlobalABElement extends HTMLElement { });
customElements.define('c-d', class GlobalCDElement extends HTMLElement { });

test(() => {
assert_equals(typeof(window.customElements.upgrade), 'function');
assert_equals(typeof((new CustomElementRegistry).upgrade), 'function');
}, 'upgrade is a function on both global and scoped CustomElementRegistry');

test(() => {
const registry = new CustomElementRegistry;
registry.define('a-b', class ABElement extends HTMLElement { });

const clone = host.cloneNode(true);
registry.upgrade(clone.elementInternals.shadowRoot);
assert_equals(clone.elementInternals.shadowRoot.querySelector('a-b').__proto__.constructor.name, 'HTMLElement');
}, 'upgrade is a no-op when called on a shadow root with no association');

test(() => {
const registry = new CustomElementRegistry;
registry.define('a-b', class ABElement extends HTMLElement {
elementInternals;

constructor() {
super();
this.elementInternals = this.attachInternals();
}
});

const clone = host.cloneNode(true);
registry.initialize(clone.elementInternals.shadowRoot);
registry.upgrade(clone.elementInternals.shadowRoot);
const abElement = clone.elementInternals.shadowRoot.querySelector('a-b');
assert_equals(abElement.__proto__.constructor.name, 'ABElement');
assert_equals(abElement.elementInternals.shadowRoot.customElements, null);
const cdElement = abElement.elementInternals.shadowRoot.querySelector('c-d');
assert_equals(cdElement.__proto__.constructor.name, 'HTMLElement');
assert_equals(cdElement.customElements, null);
}, 'upgrade should upgrade a candidate element when called on a shadow root with an association');

test(() => {
const registry = new CustomElementRegistry;
registry.define('a-b', class ScopedABElement extends HTMLElement {
elementInternals;

constructor() {
super();
this.elementInternals = this.attachInternals();
}
});

const clone = host.cloneNode(true);
registry.initialize(clone.elementInternals.shadowRoot);
registry.upgrade(clone.elementInternals.shadowRoot);
const abElement = clone.elementInternals.shadowRoot.querySelector('a-b');
assert_equals(abElement.__proto__.constructor.name, 'ScopedABElement');
registry.initialize(abElement.elementInternals.shadowRoot);
assert_equals(abElement.elementInternals.shadowRoot.customElements, registry);
const cdElement = abElement.elementInternals.shadowRoot.querySelector('c-d');
assert_equals(cdElement.customElements, registry);

registry.define('c-d', class ScopedCDElement extends HTMLElement {
elementInternals;

constructor() {
super();
this.elementInternals = this.attachInternals();
}
});
assert_equals(cdElement.__proto__.constructor.name, 'HTMLElement');
registry.upgrade(abElement.elementInternals.shadowRoot);
assert_equals(cdElement.__proto__.constructor.name, 'ScopedCDElement');
assert_equals(cdElement.customElements, registry);

assert_equals(cdElement.elementInternals.shadowRoot.customElements, window.customElements);
const innerAB = cdElement.elementInternals.shadowRoot.querySelector('a-b');
assert_equals(innerAB.customElements, window.customElements);
assert_equals(innerAB.__proto__.constructor.name, 'HTMLElement');
}, 'upgrade should not upgrade a candidate element not associated with the registry');

</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta name="author" title="Ryosuke Niwa" href="mailto:[email protected]">
<link rel="help" href="https://github.com/whatwg/html/issues/10854">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>

const scopedRegistry = new CustomElementRegistry();
const otherScopedRegistry = new CustomElementRegistry();
class GlobalABElement extends HTMLElement {};
class ScopedABElement extends HTMLElement {};
customElements.define('a-b', GlobalABElement);
scopedRegistry.define('a-b', ScopedABElement);

test(() => {
assert_true(document.createElement('a-b') instanceof GlobalABElement);
}, 'createElement should use the global registry by default');

test(() => {
assert_true(document.createElement('a-b', {customElements: scopedRegistry}) instanceof ScopedABElement);
}, 'createElement should use the specified scoped registry');

test(() => {
const elements = {
div: HTMLDivElement,
form: HTMLFormElement,
span: HTMLSpanElement,
table: HTMLTableElement,
};
for (const localName in elements)
assert_true(document.createElement(localName, {customElements: scopedRegistry}) instanceof elements[localName], localName);
for (const localName in elements)
assert_true(document.createElement(localName, {customElements: window.customElements}) instanceof elements[localName], localName);
}, 'createElement should create a builtin element regardles of a custom element registry specified');

test(() => {
assert_true(document.createElement('a-b', {customElements: window.customElements}) instanceof GlobalABElement);
}, 'createElement should use the specified global registry');

test(() => {
const element = document.createElement('a-b', {customElements: otherScopedRegistry});
assert_equals(element.__proto__.constructor.name, 'HTMLElement');
}, 'createElement should create an upgrade candidate when there is no matching definition in the specified registry');

test(() => {
class CDElement extends HTMLElement { }
const registry = new CustomElementRegistry;
const cdElement = document.createElement('c-d', {customElements: registry});
assert_false(cdElement instanceof CDElement);
assert_equals(cdElement.__proto__.constructor.name, 'HTMLElement');
registry.define('c-d', CDElement);
assert_false(cdElement instanceof CDElement); // Not yet upgraded since it's disconnected.
assert_equals(cdElement.__proto__.constructor.name, 'HTMLElement');
document.body.appendChild(cdElement);
assert_true(cdElement instanceof CDElement);
}, 'createElement should create an upgrade candidate and the candidate should be upgraded when the element is defined');

</script>
</body>
</html>
Loading

0 comments on commit ab51b76

Please sign in to comment.