diff --git a/test/index.js b/test/index.js index 8806be4..e4729ba 100644 --- a/test/index.js +++ b/test/index.js @@ -22,3 +22,4 @@ test('./test-computed-properties.html'); test('./test-observed-properties.html'); test('./test-listeners.html'); test('./test-scratch.html'); +test('./test-custom-elements-everywhere.html'); diff --git a/test/test-custom-elements-everywhere.html b/test/test-custom-elements-everywhere.html new file mode 100644 index 0000000..075f1c3 --- /dev/null +++ b/test/test-custom-elements-everywhere.html @@ -0,0 +1,10 @@ + + + + + +

Test Custom Elements Everywhere

+

These tests attempt to hold “x-element” to the standards set by the “custom-elements-everywhere” project.

+

See Custom Elements Everywhere for details.

+ + diff --git a/test/test-custom-elements-everywhere.js b/test/test-custom-elements-everywhere.js new file mode 100644 index 0000000..aa3a8dc --- /dev/null +++ b/test/test-custom-elements-everywhere.js @@ -0,0 +1,421 @@ +import XElement from '../x-element.js'; +import { assert, it } from '../../../@netflix/x-test/x-test.js'; + +// https://github.com/webcomponents/custom-elements-everywhere/blob/main/libraries/__shared__/webcomponents/src/ce-with-children.js +class CEWithChildren extends HTMLElement { + constructor() { + super(); + this.attachShadow({mode: 'open'}); + this.shadowRoot.innerHTML = ` +

Test h1

+
+

Test p

+
+ + `; + } +} +customElements.define('ce-with-children', CEWithChildren); + +// https://github.com/webcomponents/custom-elements-everywhere/blob/main/libraries/__shared__/webcomponents/src/ce-with-event.js +class CEWithEvent extends HTMLElement { + constructor() { + super(); + this.addEventListener('click', this.onClick); + } + onClick() { + this.dispatchEvent(new CustomEvent('lowercaseevent')); + this.dispatchEvent(new CustomEvent('kebab-event')); + this.dispatchEvent(new CustomEvent('camelEvent')); + this.dispatchEvent(new CustomEvent('CAPSevent')); + this.dispatchEvent(new CustomEvent('PascalEvent')); + } +} +customElements.define('ce-with-event', CEWithEvent); + +// https://github.com/webcomponents/custom-elements-everywhere/blob/main/libraries/__shared__/webcomponents/src/ce-with-properties.js +class CEWithProperties extends HTMLElement { + set bool(value) { + this._bool = value; + } + get bool() { + return this._bool; + } + set num(value) { + this._num = value; + } + get num() { + return this._num; + } + set str(value) { + this._str = value; + } + get str() { + return this._str; + } + set arr(value) { + this._arr = value; + } + get arr() { + return this._arr; + } + set obj(value) { + this._obj = value; + } + get obj() { + return this._obj; + } +} +customElements.define('ce-with-properties', CEWithProperties); + +// https://github.com/webcomponents/custom-elements-everywhere/blob/main/libraries/__shared__/webcomponents/src/ce-without-children.js +class CEWithoutChildren extends HTMLElement { + constructor() { + super(); + } +} +customElements.define('ce-without-children', CEWithoutChildren); + +it('no children: can display a Custom Element with no children', () => { + class TestElement1 extends XElement { + static template(html) { + return () => html``; + } + } + customElements.define('test-element-1', TestElement1); + const element = document.createElement('test-element-1'); + element.id = 'test-element-1'; + document.body.append(element); + assert(!!document.getElementById('test-element-1').shadowRoot.getElementById('ce-without-children')); + document.body.removeChild(element); +}); + +it('basic support: with children: can display a Custom Element with children in a Shadow Root', () => { + class TestElement2 extends XElement { + static template(html) { + return () => html``; + } + } + customElements.define('test-element-2', TestElement2); + const element = document.createElement('test-element-2'); + element.id = 'test-element-2'; + document.body.append(element); + const children = document.getElementById('test-element-2').shadowRoot.getElementById('ce-with-children').shadowRoot.children; + assert(!!children.length); + document.body.removeChild(element); +}); + +it('basic support: with children: can display a Custom Element with children in a Shadow Root and pass in Light DOM children', () => { + class TestElement3 extends XElement { + static template(html) { + return () => html`

`; + } + } + customElements.define('test-element-3', TestElement3); + const element = document.createElement('test-element-3'); + element.id = 'test-element-3'; + document.body.append(element); + const assignedElements = document.getElementById('test-element-3').shadowRoot.getElementById('ce-with-children').shadowRoot.querySelector('slot').assignedElements(); + assert(!!assignedElements.length); + assert(assignedElements[0].id === 'test-element-3-dom'); + document.body.removeChild(element); +}); + +it('basic support: with children: can display a Custom Element with children in the Shadow DOM and handle hiding and showing the element', () => { + class TestElement4 extends XElement { + static get properties() { + return { show: { type: Boolean } }; + } + static template(html) { + return ({ show }) => show ? html`` : null; + } + } + customElements.define('test-element-4', TestElement4); + const element = document.createElement('test-element-4'); + element.id = 'test-element-4'; + document.body.append(element); + const initialChildLookup = document.getElementById('test-element-4').shadowRoot.getElementById('ce-with-children'); + assert(!initialChildLookup); + element.show = true; + element.render(); // force and immediate re-render. + const finalChildLookup = document.getElementById('test-element-4').shadowRoot.getElementById('ce-with-children'); + assert(!!finalChildLookup); + document.body.removeChild(element); +}); + +it('basic support: attributes and properties: will pass boolean data as either an attribute or a property', () => { + class TestElement5 extends XElement { + static get properties() { + return { bool: { type: Boolean } }; + } + static template(html) { + return ({ bool }) => html``; + } + } + customElements.define('test-element-5', TestElement5); + const element = document.createElement('test-element-5'); + element.bool = true; + element.id = 'test-element-5'; + document.body.append(element); + const bool = document.getElementById('test-element-5').shadowRoot.getElementById('ce-with-properties').bool; + assert(bool); + document.body.removeChild(element); +}); + +it('basic support: attributes and properties: will pass numeric data as either an attribute or a property', () => { + class TestElement6 extends XElement { + static get properties() { + return { num: { type: Number } }; + } + static template(html) { + return ({ num }) => html``; + } + } + customElements.define('test-element-6', TestElement6); + const element = document.createElement('test-element-6'); + element.num = 999; + element.id = 'test-element-6'; + document.body.append(element); + const num = document.getElementById('test-element-6').shadowRoot.getElementById('ce-with-properties').num; + assert(num === 999); + document.body.removeChild(element); +}); + +it('basic support: attributes and properties: will pass string data as either an attribute or a property', () => { + class TestElement7 extends XElement { + static get properties() { + return { str: { type: String } }; + } + static template(html) { + return ({ str }) => html``; + } + } + customElements.define('test-element-7', TestElement7); + const element = document.createElement('test-element-7'); + element.str = 'foo'; + element.id = 'test-element-7'; + document.body.append(element); + const str = document.getElementById('test-element-7').shadowRoot.getElementById('ce-with-properties').str; + assert(str === 'foo'); + document.body.removeChild(element); +}); + +it('basic support: events: can imperatively listen to a DOM event dispatched by a Custom Element', () => { + class TestElement8 extends XElement { + static get properties() { + return { handled: { type: Boolean } }; + } + static template(html) { + return () => html``; + } + static onLowercaseevent(host) { + host.handled = true; + } + connectedCallback() { + super.connectedCallback(); + this.listen(this.shadowRoot.getElementById('ce-with-event'), 'lowercaseevent', TestElement8.onLowercaseevent); + } + disconnectedCallback() { + super.disconnectedCallback(); + this.unlisten(this.shadowRoot.getElementById('ce-with-event'), 'lowercaseevent', TestElement8.onLowercaseevent); + } + } + customElements.define('test-element-8', TestElement8); + const element = document.createElement('test-element-8'); + element.id = 'test-element-8'; + document.body.append(element); + const child = document.getElementById('test-element-8').shadowRoot.getElementById('ce-with-event'); + child.click(); + assert(element.handled); + document.body.removeChild(element); +}); + +it('advanced support: attributes and properties: will pass array data as a property', () => { + class TestElement9 extends XElement { + static get properties() { + return { arr: { type: Array } }; + } + static template(html) { + return ({ arr }) => html``; + } + } + customElements.define('test-element-9', TestElement9); + const element = document.createElement('test-element-9'); + const input = [1, 2, 3]; + element.arr = input; + element.id = 'test-element-9'; + document.body.append(element); + const arr = document.getElementById('test-element-9').shadowRoot.getElementById('ce-with-properties').arr; + assert(arr === input); + document.body.removeChild(element); +}); + +it('advanced support: attributes and properties: will pass object data as a property', () => { + class TestElement10 extends XElement { + static get properties() { + return { obj: { type: Object } }; + } + static template(html) { + return ({ obj }) => html``; + } + } + customElements.define('test-element-10', TestElement10); + const element = document.createElement('test-element-10'); + const input = { foo: 'bar' }; + element.obj = input; + element.id = 'test-element-10'; + document.body.append(element); + const obj = document.getElementById('test-element-10').shadowRoot.getElementById('ce-with-properties').obj; + assert(obj === input); + document.body.removeChild(element); +}); + +it('advanced support: events: can declaratively listen to a lowercase DOM event dispatched by a Custom Element', () => { + class TestElement11 extends XElement { + static get properties() { + return { handled: { type: Boolean } }; + } + static template(html) { + return () => html``; + } + static onLowercaseevent(host) { + host.handled = true; + } + connectedCallback() { + super.connectedCallback(); + this.listen(this.shadowRoot.getElementById('ce-with-event'), 'lowercaseevent', TestElement11.onLowercaseevent); + } + disconnectedCallback() { + super.disconnectedCallback(); + this.unlisten(this.shadowRoot.getElementById('ce-with-event'), 'lowercaseevent', TestElement11.onLowercaseevent); + } + } + customElements.define('test-element-11', TestElement11); + const element = document.createElement('test-element-11'); + element.id = 'test-element-11'; + document.body.append(element); + const child = document.getElementById('test-element-11').shadowRoot.getElementById('ce-with-event'); + child.click(); + assert(element.handled); + document.body.removeChild(element); +}); + +it('advanced support: events: can declaratively listen to a kebab-case DOM event dispatched by a Custom Element', () => { + class TestElement12 extends XElement { + static get properties() { + return { handled: { type: Boolean } }; + } + static template(html) { + return () => html``; + } + static onKebabEvent(host) { + host.handled = true; + } + connectedCallback() { + super.connectedCallback(); + this.listen(this.shadowRoot.getElementById('ce-with-event'), 'kebab-event', TestElement12.onKebabEvent); + } + disconnectedCallback() { + super.disconnectedCallback(); + this.unlisten(this.shadowRoot.getElementById('ce-with-event'), 'kebab-event', TestElement12.onKebabEvent); + } + } + customElements.define('test-element-12', TestElement12); + const element = document.createElement('test-element-12'); + element.id = 'test-element-12'; + document.body.append(element); + const child = document.getElementById('test-element-12').shadowRoot.getElementById('ce-with-event'); + child.click(); + assert(element.handled); + document.body.removeChild(element); +}); + +it('advanced support: events: can declaratively listen to a camelCase DOM event dispatched by a Custom Element', () => { + class TestElement13 extends XElement { + static get properties() { + return { handled: { type: Boolean } }; + } + static template(html) { + return () => html``; + } + static onCamelEvent(host) { + host.handled = true; + } + connectedCallback() { + super.connectedCallback(); + this.listen(this.shadowRoot.getElementById('ce-with-event'), 'camelEvent', TestElement13.onCamelEvent); + } + disconnectedCallback() { + super.disconnectedCallback(); + this.unlisten(this.shadowRoot.getElementById('ce-with-event'), 'camelEvent', TestElement13.onCamelEvent); + } + } + customElements.define('test-element-13', TestElement13); + const element = document.createElement('test-element-13'); + element.id = 'test-element-13'; + document.body.append(element); + const child = document.getElementById('test-element-13').shadowRoot.getElementById('ce-with-event'); + child.click(); + assert(element.handled); + document.body.removeChild(element); +}); + +it('advanced support: events: can declaratively listen to a CAPScase DOM event dispatched by a Custom Element', () => { + class TestElement14 extends XElement { + static get properties() { + return { handled: { type: Boolean } }; + } + static template(html) { + return () => html``; + } + static onCAPSevent(host) { + host.handled = true; + } + connectedCallback() { + super.connectedCallback(); + this.listen(this.shadowRoot.getElementById('ce-with-event'), 'CAPSevent', TestElement14.onCAPSevent); + } + disconnectedCallback() { + super.disconnectedCallback(); + this.unlisten(this.shadowRoot.getElementById('ce-with-event'), 'CAPSevent', TestElement14.onCAPSevent); + } + } + customElements.define('test-element-14', TestElement14); + const element = document.createElement('test-element-14'); + element.id = 'test-element-14'; + document.body.append(element); + const child = document.getElementById('test-element-14').shadowRoot.getElementById('ce-with-event'); + child.click(); + assert(element.handled); + document.body.removeChild(element); +}); + +it('advanced support: events: can declaratively listen to a PascalCase DOM event dispatched by a Custom Element', () => { + class TestElement15 extends XElement { + static get properties() { + return { handled: { type: Boolean } }; + } + static template(html) { + return () => html``; + } + static onPascalEvent(host) { + host.handled = true; + } + connectedCallback() { + super.connectedCallback(); + this.listen(this.shadowRoot.getElementById('ce-with-event'), 'PascalEvent', TestElement15.onPascalEvent); + } + disconnectedCallback() { + super.disconnectedCallback(); + this.unlisten(this.shadowRoot.getElementById('ce-with-event'), 'PascalEvent', TestElement15.onPascalEvent); + } + } + customElements.define('test-element-15', TestElement15); + const element = document.createElement('test-element-15'); + element.id = 'test-element-15'; + document.body.append(element); + const child = document.getElementById('test-element-15').shadowRoot.getElementById('ce-with-event'); + child.click(); + assert(element.handled); + document.body.removeChild(element); +}); \ No newline at end of file