UNPKG

@lion/ui

Version:

A package of extendable web components

165 lines (136 loc) 6.43 kB
import { expect, fixture } from '@open-wc/testing'; import { ssrNonHydratedFixture, ssrHydratedFixture, csrFixture, } from '@lit-labs/testing/fixtures.js'; import { LitElement, html } from 'lit'; import sinon from 'sinon'; import { ScopedElementsMixin, supportsScopedRegistry } from '../src/ScopedElementsMixin.js'; import { browserDetection } from '../src/browserDetection.js'; const hasRealScopedRegistrySupport = supportsScopedRegistry(); const originalShadowRootProps = { // @ts-expect-error createElement: globalThis.ShadowRoot?.prototype.createElement, // @ts-expect-error importNode: globalThis.ShadowRoot?.prototype.importNode, }; // Even though the polyfill might be loaded in this test or we run it in a browser supporting these features, // we mock "no support", so that `supportsScopedRegistry()` returns false inside ScopedElementsMixin.. function mockNoRegistrySupport() { // Are we on a server or do we have no polyfill? Nothing to be done here... if (!hasRealScopedRegistrySupport) return; // This will be enough to make the `supportsScopedRegistry()` check fail inside ScopedElementsMixin and bypass scoped registries globalThis.ShadowRoot = globalThis.ShadowRoot || { prototype: {} }; // @ts-expect-error globalThis.ShadowRoot.prototype.createElement = null; } mockNoRegistrySupport.restore = () => { // Are we on a server or do we have no polyfill? Nothing to be done here... if (!hasRealScopedRegistrySupport) return; // @ts-expect-error globalThis.ShadowRoot.prototype.createElement = originalShadowRootProps.createElement; // @ts-expect-error globalThis.ShadowRoot.prototype.importNode = originalShadowRootProps.importNode; }; class ScopedElementsChild extends LitElement { render() { return html`<span>I'm a child</span>`; } } class ScopedElementsHost extends ScopedElementsMixin(LitElement) { static scopedElements = { 'scoped-elements-child': ScopedElementsChild }; render() { return html`<scoped-elements-child></scoped-elements-child>`; } } customElements.define('scoped-elements-host', ScopedElementsHost); describe('ScopedElementsMixin', () => { it('renders child elements correctly (that were not registered yet on global registry)', async () => { // customElements.define('scoped-elements-child', ScopedElementsChild); for (const _fixture of [csrFixture, ssrNonHydratedFixture, ssrHydratedFixture]) { const el = await _fixture(html`<scoped-elements-host></scoped-elements-host>`, { // we must provide modules atm modules: ['./ssr-definitions/ScopedElementsHost.define.js'], }); // Wait for FF support if (!browserDetection.isFirefox) { expect( el.shadowRoot?.querySelector('scoped-elements-child')?.shadowRoot?.innerHTML, ).to.contain("<span>I'm a child</span>"); } // @ts-expect-error expect(el.registry.get('scoped-elements-child')).to.not.be.undefined; } }); describe('When scoped registries are supported', () => { it('registers elements on local registry', async () => { if (!hasRealScopedRegistrySupport) return; const ceDefineSpy = sinon.spy(customElements, 'define'); const el = /** @type {ScopedElementsHost} */ ( await fixture(html`<scoped-elements-host></scoped-elements-host>`) ); // @ts-expect-error expect(el.registry.get('scoped-elements-child')).to.equal(ScopedElementsChild); expect(el.registry).to.not.equal(customElements); expect(ceDefineSpy.calledWith('scoped-elements-child')).to.be.false; ceDefineSpy.restore(); }); }); describe('When scoped registries are not supported', () => { class ScopedElementsChildNoReg extends LitElement { render() { return html`<span>I'm a child</span>`; } } class ScopedElementsHostNoReg extends ScopedElementsMixin(LitElement) { static scopedElements = { 'scoped-elements-child-no-reg': ScopedElementsChildNoReg }; render() { return html`<scoped-elements-child-no-reg></scoped-elements-child-no-reg>`; } } before(() => { mockNoRegistrySupport(); customElements.define('scoped-elements-host-no-reg', ScopedElementsHostNoReg); }); after(() => { mockNoRegistrySupport.restore(); }); it('registers elements', async () => { const ceDefineSpy = sinon.spy(customElements, 'define'); const el = /** @type {ScopedElementsHostNoReg} */ ( await fixture(html`<scoped-elements-host-no-reg></scoped-elements-host-no-reg>`) ); expect(el.registry).to.equal(customElements); expect(ceDefineSpy.calledWith('scoped-elements-child-no-reg')).to.be.true; ceDefineSpy.restore(); }); it('fails when different classes are registered under different name', async () => { class ScopedElementsHostNoReg2 extends ScopedElementsMixin(LitElement) { static scopedElements = { 'scoped-elements-child-no-reg': class extends HTMLElement {} }; render() { return html`<scoped-elements-child-no-reg></scoped-elements-child-no-reg>`; } } customElements.define('scoped-elements-host-no-reg-2', ScopedElementsHostNoReg2); const errorStub = sinon.stub(console, 'error'); /** @type {ScopedElementsHostNoReg2} */ ( await fixture(html`<scoped-elements-host-no-reg></scoped-elements-host-no-reg>`) ); /** @type {ScopedElementsHostNoReg2} */ ( await fixture(html`<scoped-elements-host-no-reg-2></scoped-elements-host-no-reg-2>`) ); expect(errorStub.args[0][0]).to.equal( [ 'You are trying to re-register the "scoped-elements-child-no-reg" custom element with a different class via ScopedElementsMixin.', 'This is only possible with a CustomElementRegistry.', 'Your browser does not support this feature so you will need to load a polyfill for it.', 'Load "@webcomponents/scoped-custom-element-registry" before you register ANY web component to the global customElements registry.', 'e.g. add "<script src="/node_modules/@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min.js"></script>" as your first script tag.', 'For more details you can visit https://open-wc.org/docs/development/scoped-elements/', ].join('\n'), ); errorStub.restore(); }); }); });