import sinon from 'sinon'; import { defineCE, expect, fixture } from '@open-wc/testing'; import { SlotMixin } from '../src/SlotMixin.js'; import { LitElement, ScopedElementsMixin, html } from '../index.js'; describe('SlotMixin', () => { it('inserts provided element into lightdom and sets slot', async () => { const tag = defineCE( class extends SlotMixin(LitElement) { get slots() { return { ...super.slots, feedback: () => document.createElement('div'), }; } }, ); const el = await fixture(`<${tag}>`); expect(el.children[0].slot).to.equal('feedback'); }); it('does not override user provided slots', async () => { const tag = defineCE( class extends SlotMixin(LitElement) { get slots() { return { ...super.slots, feedback: () => document.createElement('div'), }; } }, ); const el = await fixture(`<${tag}>

user-content

`); expect(el.children[0].tagName).to.equal('P'); expect(/** @type HTMLParagraphElement */ (el.children[0]).innerText).to.equal('user-content'); }); it('does add when user provided slots are not direct children', async () => { const tag = defineCE( class extends SlotMixin(LitElement) { get slots() { return { ...super.slots, content: () => document.createElement('div'), }; } }, ); const el = await fixture(`<${tag}>

user-content

`); const slot = /** @type HTMLDivElement */ ( Array.from(el.children).find(elm => elm.slot === 'content') ); expect(slot.tagName).to.equal('DIV'); expect(slot.innerText).to.equal(''); }); it('supports complex dom trees as element', async () => { const tag = defineCE( class extends SlotMixin(LitElement) { constructor() { super(); this.foo = 'bar'; } get slots() { return { ...super.slots, feedback: () => { const el = document.createElement('div'); el.setAttribute('foo', this.foo); const subEl = document.createElement('p'); subEl.innerText = 'cat'; el.appendChild(subEl); return el; }, }; } }, ); const el = await fixture(`<${tag}>`); expect(el.children[0].slot).to.equal('feedback'); expect(el.children[0].getAttribute('foo')).to.equal('bar'); expect(/** @type HTMLParagraphElement */ (el.children[0].children[0]).innerText).to.equal( 'cat', ); }); it('supports conditional slots', async () => { let renderSlot = true; const tag = defineCE( class extends SlotMixin(LitElement) { get slots() { return { ...super.slots, conditional: () => { if (renderSlot) { const el = document.createElement('div'); el.id = 'someSlot'; return el; } return undefined; }, }; } }, ); const elSlot = await fixture(`<${tag}><${tag}>`); expect(elSlot.querySelector('#someSlot')).to.exist; renderSlot = false; const elNoSlot = await fixture(`<${tag}><${tag}>`); expect(elNoSlot.querySelector('#someSlot')).to.not.exist; }); it("allows to check which slots have been created via this._isPrivateSlot('slotname')", async () => { let renderSlot = true; class SlotPrivateText extends SlotMixin(LitElement) { get slots() { return { ...super.slots, conditional: () => (renderSlot ? document.createElement('div') : undefined), }; } didCreateConditionalSlot() { return this._isPrivateSlot('conditional'); } } const tag = defineCE(SlotPrivateText); const el = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`)); expect(el.didCreateConditionalSlot()).to.be.true; const elUserSlot = /** @type {SlotPrivateText} */ ( await fixture(`<${tag}>

foo

<${tag}>`) ); expect(elUserSlot.didCreateConditionalSlot()).to.be.false; renderSlot = false; const elNoSlot = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`)); expect(elNoSlot.didCreateConditionalSlot()).to.be.false; }); it('supports templates', async () => { const tag = defineCE( class extends SlotMixin(LitElement) { get slots() { return { ...super.slots, template: () => html`text`, }; } render() { return html``; } }, ); const el = await fixture(`<${tag}><${tag}>`); const slot = /** @type HTMLSpanElement */ ( Array.from(el.children).find(elm => elm.slot === 'template') ); expect(slot.slot).to.equal('template'); expect(slot.tagName).to.equal('SPAN'); }); it('supports scoped elements', async () => { const scopedSpy = sinon.spy(); class ScopedEl extends LitElement { connectedCallback() { super.connectedCallback(); scopedSpy(); } } const tag = defineCE( class extends ScopedElementsMixin(SlotMixin(LitElement)) { static get scopedElements() { return { // @ts-expect-error ...super.scopedElements, 'scoped-el': ScopedEl, }; } get slots() { return { ...super.slots, template: () => html``, }; } connectedCallback() { super.connectedCallback(); // Not rendered to shadowRoot, notScopedSpy should not be called const notScoped = document.createElement('not-scoped'); this.appendChild(notScoped); } render() { return html``; } }, ); await fixture(`<${tag}><${tag}>`); expect(scopedSpy).to.have.been.called; }); });