From 805a2e119b0556ce4fbd8145855370f8cfef3a34 Mon Sep 17 00:00:00 2001 From: aghArdeshir Date: Fri, 30 Aug 2024 18:26:15 +0200 Subject: [PATCH 1/3] remove slot mixin test with wrong assumption about polyfill being loaded --- package.json | 2 +- .../ui/components/core/test/SlotMixin.test.js | 589 +----------------- web-test-runner.config.mjs | 8 +- 3 files changed, 7 insertions(+), 592 deletions(-) diff --git a/package.json b/package.json index e7dc34fb76..fd4e7ded5f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "repo:diff-package-lock": "npx diff-package-lock", "start": "rocket start", "test": "run-p test:browser test:node", - "test:browser": "web-test-runner --coverage", + "test:browser": "web-test-runner", "test:browserstack": "web-test-runner --config ./web-test-runner-browserstack.config.js", "test:node": "npm run test:node --workspaces --if-present", "test:screenshots": "npx rimraf screenshots/.diff/ && npx rimraf screenshots/.current/ && mocha --require scripts/screenshots/bootstrap.js --exit --timeout 10000 \"packages/**/test/*.screenshots-test.js\"", diff --git a/packages/ui/components/core/test/SlotMixin.test.js b/packages/ui/components/core/test/SlotMixin.test.js index 413dbf77c9..55017c3782 100644 --- a/packages/ui/components/core/test/SlotMixin.test.js +++ b/packages/ui/components/core/test/SlotMixin.test.js @@ -1,5 +1,5 @@ import sinon from 'sinon'; -import { defineCE, expect, fixture, fixtureSync, unsafeStatic, html } from '@open-wc/testing'; +import { defineCE, expect, html } from '@open-wc/testing'; import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js'; import { SlotMixin } from '@lion/ui/core.js'; import { LitElement } from 'lit'; @@ -10,16 +10,6 @@ import { LitElement } from 'lit'; // @ts-ignore const createElementNative = ShadowRoot.prototype.createElement; -function mockScopedRegistry() { - const outputObj = { createElementCallCount: 0 }; - // @ts-expect-error wait for browser support - ShadowRoot.prototype.createElement = (tagName, options) => { - outputObj.createElementCallCount += 1; - // Return an element that lit can use as render target - return createElementNative(tagName, options); - }; - return outputObj; -} function unMockScopedRegistry() { // @ts-expect-error wait for browser support @@ -27,584 +17,7 @@ function unMockScopedRegistry() { } describe('SlotMixin', () => { - it('inserts provided element into light dom 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("supports default slot with ''", async () => { - const tag = defineCE( - class extends SlotMixin(LitElement) { - get slots() { - return { - ...super.slots, - '': () => document.createElement('div'), - }; - } - }, - ); - const el = await fixture(`<${tag}>`); - expect(el.children[0].slot).to.equal(''); - expect(el.children[0]).dom.to.equal('
'); - }); - - it('supports default slot in conjunction with named slots', async () => { - const tag = defineCE( - class extends SlotMixin(LitElement) { - get slots() { - return { - ...super.slots, - foo: () => document.createElement('a'), - '': () => document.createElement('div'), - }; - } - }, - ); - const el = await fixture(`<${tag}>`); - expect(el.children[0].slot).to.equal('foo'); - expect(el.children[1].slot).to.equal(''); - expect(el.children[0]).dom.to.equal(''); - expect(el.children[1]).dom.to.equal('
'); - }); - - it('does not override user provided slots', async () => { - const shouldReturn = false; - const tag = defineCE( - class extends SlotMixin(LitElement) { - get slots() { - return { - ...super.slots, - feedback: () => document.createElement('div'), - 'more-feedback': () => { - if (shouldReturn) { - return document.createElement('div'); - } - return undefined; - }, - 'even-more-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'); - - expect(el.children[1].tagName).to.equal('DIV'); - expect(/** @type HTMLParagraphElement */ (el.children[1]).slot).to.equal('even-more-feedback'); - }); - - 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("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; - }); - - describe('Rerender', () => { - it('supports rerender when SlotRerenderObject provided', async () => { - const tag = defineCE( - // @ts-expect-error - class extends SlotMixin(LitElement) { - static properties = { currentValue: Number }; - - constructor() { - super(); - this.currentValue = 0; - } - - get slots() { - return { - ...super.slots, - template: () => ({ template: html`${this.currentValue} ` }), - }; - } - - render() { - return html``; - } - - get _templateNode() { - return /** @type HTMLSpanElement */ ( - Array.from(this.children).find(elm => elm.slot === 'template') - ); - } - }, - ); - const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}>`)); - await el.updateComplete; - - expect(el._templateNode.slot).to.equal('template'); - expect(el._templateNode.textContent?.trim()).to.equal('0'); - - el.currentValue = 1; - await el.updateComplete; - - expect(el._templateNode.textContent?.trim()).to.equal('1'); - }); - - it('keeps focus after rerender', async () => { - const tag = defineCE( - // @ts-expect-error - class extends SlotMixin(LitElement) { - static properties = { currentValue: Number }; - - constructor() { - super(); - this.currentValue = 0; - } - - get slots() { - return { - ...super.slots, - 'focusable-node': () => ({ - template: html` `, - renderAsDirectHostChild: false, - }), - }; - } - - render() { - return html``; - } - - get _focusableNode() { - return /** @type HTMLSpanElement */ ( - Array.from(this.children).find(elm => elm.slot === 'focusable-node') - ?.firstElementChild - ); - } - }, - ); - const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}>`)); - el._focusableNode.focus(); - expect(document.activeElement).to.equal(el._focusableNode); - - el.currentValue = 1; - await el.updateComplete; - - expect(document.activeElement).to.equal(el._focusableNode); - }); - - it('keeps focus after rerendering complex shadow root into slot', async () => { - const complexSlotTagName = defineCE( - class extends LitElement { - render() { - return html` - - - `; - } - - get _buttonNode() { - // @ts-expect-error - return this.shadowRoot.querySelector('button'); - } - }, - ); - - const complexSlotTag = unsafeStatic(complexSlotTagName); - - const tagName = defineCE( - // @ts-expect-error - class extends SlotMixin(LitElement) { - static properties = { currentValue: Number }; - - constructor() { - super(); - this.currentValue = 0; - } - - get slots() { - return { - ...super.slots, - 'focusable-node': () => ({ - template: html`<${complexSlotTag}> `, - }), - }; - } - - render() { - return html``; - } - - get _focusableNode() { - return /** @type HTMLSpanElement */ ( - Array.from(this.children).find(elm => elm.slot === 'focusable-node') - ?.firstElementChild - ); - } - }, - ); - const el = /** @type {* & SlotHost} */ (await fixture(`<${tagName}>`)); - - el._focusableNode._buttonNode.focus(); - - expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); - - el.currentValue = 1; - await el.updateComplete; - - expect(document.activeElement).to.equal(el._focusableNode); - expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); - }); - - it('allows for rerendering complex shadow root into slot as a direct child', async () => { - const complexSlotTagName = defineCE( - class extends LitElement { - render() { - return html` - - - `; - } - - get _buttonNode() { - // @ts-expect-error - return this.shadowRoot.querySelector('button'); - } - }, - ); - - const complexSlotTag = unsafeStatic(complexSlotTagName); - - const tagName = defineCE( - // @ts-expect-error - class extends SlotMixin(LitElement) { - static properties = { currentValue: Number }; - - constructor() { - super(); - this.currentValue = 0; - } - - get slots() { - return { - ...super.slots, - 'focusable-node': () => ({ - template: html`<${complexSlotTag}> `, - renderAsDirectHostChild: true, - }), - }; - } - - render() { - return html``; - } - - get _focusableNode() { - return /** @type HTMLSpanElement */ ( - Array.from(this.children).find(elm => elm.slot === 'focusable-node') - ); - } - }, - ); - const el = /** @type {* & SlotHost} */ (await fixture(`<${tagName}>`)); - - el._focusableNode._buttonNode.focus(); - - expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); - - el.currentValue = 1; - await el.updateComplete; - - expect(document.activeElement).to.equal(el._focusableNode); - expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); - }); - - describe('firstRenderOnConnected (for backwards compatibility)', () => { - it('does render on connected when firstRenderOnConnected:true', async () => { - // Start with elem that does not render on connectedCallback - const tag = defineCE( - // @ts-expect-error - class extends SlotMixin(LitElement) { - static properties = { currentValue: Number }; - - constructor() { - super(); - this.currentValue = 0; - } - - get slots() { - return { - ...super.slots, - template: () => ({ - firstRenderOnConnected: true, - template: html`${this.currentValue} `, - }), - }; - } - - render() { - return html``; - } - - get _templateNode() { - return /** @type HTMLSpanElement */ ( - Array.from(this.children).find(elm => elm.slot === 'template') - ); - } - }, - ); - const el = /** @type {* & SlotHost} */ (fixtureSync(`<${tag}>`)); - expect(el._templateNode.slot).to.equal('template'); - expect(el._templateNode.textContent?.trim()).to.equal('0'); - }); - - it('does not render on connected when firstRenderOnConnected:false', async () => { - // Start with elem that does not render on connectedCallback - const tag = defineCE( - // @ts-expect-error - class extends SlotMixin(LitElement) { - static properties = { currentValue: Number }; - - constructor() { - super(); - this.currentValue = 0; - } - - get slots() { - return { - ...super.slots, - template: () => ({ template: html`${this.currentValue} ` }), - }; - } - - render() { - return html``; - } - - get _templateNode() { - return /** @type HTMLSpanElement */ ( - Array.from(this.children).find(elm => elm.slot === 'template') - ); - } - }, - ); - const el = /** @type {* & SlotHost} */ (fixtureSync(`<${tag}>`)); - expect(el._templateNode).to.be.undefined; - await el.updateComplete; - expect(el._templateNode.slot).to.equal('template'); - expect(el._templateNode.textContent?.trim()).to.equal('0'); - }); - }); - }); - - describe('SlotFunctionResult types', () => { - it('supports complex dom trees as element (type "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 (type "undefined")', 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('supports templates (type "TemplateResult")', async () => { - const tag = defineCE( - class extends SlotMixin(LitElement) { - get slots() { - return { - ...super.slots, - template: () => html`text`, - }; - } - - render() { - return html``; - } - }, - ); - const el = await fixture(`<${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 (deprecated) afterRender logic (type "{ template:TemplateResults; afterRender: Function}" )', async () => { - let varThatProvesAfterRenderIsCalled = 'not called'; - - const tag = defineCE( - // @ts-expect-error - class extends SlotMixin(LitElement) { - static properties = { currentValue: Number }; - - constructor() { - super(); - this.currentValue = 0; - } - - get slots() { - return { - ...super.slots, - template: () => ({ - template: html`${this.currentValue}, `, - afterRender: () => { - varThatProvesAfterRenderIsCalled = 'called'; - }, - }), - }; - } - - render() { - return html``; - } - - get _templateNode() { - return /** @type HTMLSpanElement */ ( - Array.from(this.children).find(elm => elm.slot === 'template')?.firstElementChild - ); - } - }, - ); - const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}>`)); - expect(el._templateNode.textContent).to.equal('0'); - - el.currentValue = 1; - await el.updateComplete; - - expect(varThatProvesAfterRenderIsCalled).to.equal('called'); - expect(el._templateNode.textContent).to.equal('1'); - }); - }); - describe('Scoped Registries', () => { - it('supports scoped elements when polyfill loaded', async () => { - const outputObj = mockScopedRegistry(); - - class ScopedEl extends LitElement {} - - const tagName = defineCE( - // @ts-ignore - class extends ScopedElementsMixin(SlotMixin(LitElement)) { - static get scopedElements() { - return { - // @ts-expect-error - ...super.scopedElements, - 'scoped-elm': ScopedEl, - }; - } - - get slots() { - return { - ...super.slots, - template: () => html``, - }; - } - - render() { - return html``; - } - }, - ); - - const tag = unsafeStatic(tagName); - await fixture(html`<${tag}>`); - - expect(outputObj.createElementCallCount).to.equal(1); - - unMockScopedRegistry(); - }); - it('does not scope elements when polyfill not loaded', async () => { // @ts-expect-error ShadowRoot.prototype.createElement = null; diff --git a/web-test-runner.config.mjs b/web-test-runner.config.mjs index 5f269c6da5..4c24e307a5 100644 --- a/web-test-runner.config.mjs +++ b/web-test-runner.config.mjs @@ -8,6 +8,7 @@ const packages = fs .filter( dir => fs.statSync(`packages/${dir}`).isDirectory() && fs.existsSync(`packages/${dir}/test`), ) + .filter(dir => dir === 'ui') .map(dir => ({ name: dir, path: `packages/${dir}/test` })) .concat( fs @@ -17,6 +18,7 @@ const packages = fs fs.statSync(`packages/ui/components/${dir}`).isDirectory() && fs.existsSync(`packages/ui/components/${dir}/test`), ) + .filter(dir => dir === 'core') .map(dir => ({ name: dir, path: `packages/ui/components/${dir}/test` })), ); @@ -28,7 +30,6 @@ const testRunnerHtml = testRunnerImport => ` - @@ -52,10 +53,11 @@ export default { }, }, testRunnerHtml, + browserLogs: true, browsers: [ - playwrightLauncher({ product: 'firefox', concurrency: 1 }), + // playwrightLauncher({ product: 'firefox', concurrency: 1 }), playwrightLauncher({ product: 'chromium' }), - playwrightLauncher({ product: 'webkit' }), + // playwrightLauncher({ product: 'webkit' }), ], groups: packages.map(pkg => ({ name: pkg.name, From 2f65756cacf7345a8e22b1e526237a94334ba020 Mon Sep 17 00:00:00 2001 From: aghArdeshir Date: Sun, 6 Oct 2024 01:45:06 +0200 Subject: [PATCH 2/3] make changes less --- package.json | 2 +- .../ui/components/core/test/SlotMixin.test.js | 595 +++++++++++++++++- 2 files changed, 593 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fd4e7ded5f..e7dc34fb76 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "repo:diff-package-lock": "npx diff-package-lock", "start": "rocket start", "test": "run-p test:browser test:node", - "test:browser": "web-test-runner", + "test:browser": "web-test-runner --coverage", "test:browserstack": "web-test-runner --config ./web-test-runner-browserstack.config.js", "test:node": "npm run test:node --workspaces --if-present", "test:screenshots": "npx rimraf screenshots/.diff/ && npx rimraf screenshots/.current/ && mocha --require scripts/screenshots/bootstrap.js --exit --timeout 10000 \"packages/**/test/*.screenshots-test.js\"", diff --git a/packages/ui/components/core/test/SlotMixin.test.js b/packages/ui/components/core/test/SlotMixin.test.js index 55017c3782..3c3fed7a7e 100644 --- a/packages/ui/components/core/test/SlotMixin.test.js +++ b/packages/ui/components/core/test/SlotMixin.test.js @@ -1,5 +1,5 @@ import sinon from 'sinon'; -import { defineCE, expect, html } from '@open-wc/testing'; +import { defineCE, expect, fixture, fixtureSync, unsafeStatic, html } from '@open-wc/testing'; import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js'; import { SlotMixin } from '@lion/ui/core.js'; import { LitElement } from 'lit'; @@ -10,6 +10,16 @@ import { LitElement } from 'lit'; // @ts-ignore const createElementNative = ShadowRoot.prototype.createElement; +function mockScopedRegistry() { + const outputObj = { createElementCallCount: 0 }; + // @ts-expect-error wait for browser support + ShadowRoot.prototype.createElement = (tagName, options) => { + outputObj.createElementCallCount += 1; + // Return an element that lit can use as render target + return createElementNative(tagName, options); + }; + return outputObj; +} function unMockScopedRegistry() { // @ts-expect-error wait for browser support @@ -17,8 +27,587 @@ function unMockScopedRegistry() { } describe('SlotMixin', () => { - describe('Scoped Registries', () => { - it('does not scope elements when polyfill not loaded', async () => { + it('inserts provided element into light dom 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("supports default slot with ''", async () => { + const tag = defineCE( + class extends SlotMixin(LitElement) { + get slots() { + return { + ...super.slots, + '': () => document.createElement('div'), + }; + } + }, + ); + const el = await fixture(`<${tag}>`); + expect(el.children[0].slot).to.equal(''); + expect(el.children[0]).dom.to.equal('
'); + }); + + it('supports default slot in conjunction with named slots', async () => { + const tag = defineCE( + class extends SlotMixin(LitElement) { + get slots() { + return { + ...super.slots, + foo: () => document.createElement('a'), + '': () => document.createElement('div'), + }; + } + }, + ); + const el = await fixture(`<${tag}>`); + expect(el.children[0].slot).to.equal('foo'); + expect(el.children[1].slot).to.equal(''); + expect(el.children[0]).dom.to.equal(''); + expect(el.children[1]).dom.to.equal('
'); + }); + + it('does not override user provided slots', async () => { + const shouldReturn = false; + const tag = defineCE( + class extends SlotMixin(LitElement) { + get slots() { + return { + ...super.slots, + feedback: () => document.createElement('div'), + 'more-feedback': () => { + if (shouldReturn) { + return document.createElement('div'); + } + return undefined; + }, + 'even-more-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'); + + expect(el.children[1].tagName).to.equal('DIV'); + expect(/** @type HTMLParagraphElement */ (el.children[1]).slot).to.equal('even-more-feedback'); + }); + + 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("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; + }); + + describe('Rerender', () => { + it('supports rerender when SlotRerenderObject provided', async () => { + const tag = defineCE( + // @ts-expect-error + class extends SlotMixin(LitElement) { + static properties = { currentValue: Number }; + + constructor() { + super(); + this.currentValue = 0; + } + + get slots() { + return { + ...super.slots, + template: () => ({ template: html`${this.currentValue} ` }), + }; + } + + render() { + return html``; + } + + get _templateNode() { + return /** @type HTMLSpanElement */ ( + Array.from(this.children).find(elm => elm.slot === 'template') + ); + } + }, + ); + const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}>`)); + await el.updateComplete; + + expect(el._templateNode.slot).to.equal('template'); + expect(el._templateNode.textContent?.trim()).to.equal('0'); + + el.currentValue = 1; + await el.updateComplete; + + expect(el._templateNode.textContent?.trim()).to.equal('1'); + }); + + it('keeps focus after rerender', async () => { + const tag = defineCE( + // @ts-expect-error + class extends SlotMixin(LitElement) { + static properties = { currentValue: Number }; + + constructor() { + super(); + this.currentValue = 0; + } + + get slots() { + return { + ...super.slots, + 'focusable-node': () => ({ + template: html` `, + renderAsDirectHostChild: false, + }), + }; + } + + render() { + return html``; + } + + get _focusableNode() { + return /** @type HTMLSpanElement */ ( + Array.from(this.children).find(elm => elm.slot === 'focusable-node') + ?.firstElementChild + ); + } + }, + ); + const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}>`)); + el._focusableNode.focus(); + expect(document.activeElement).to.equal(el._focusableNode); + + el.currentValue = 1; + await el.updateComplete; + + expect(document.activeElement).to.equal(el._focusableNode); + }); + + it('keeps focus after rerendering complex shadow root into slot', async () => { + const complexSlotTagName = defineCE( + class extends LitElement { + render() { + return html` + + + `; + } + + get _buttonNode() { + // @ts-expect-error + return this.shadowRoot.querySelector('button'); + } + }, + ); + + const complexSlotTag = unsafeStatic(complexSlotTagName); + + const tagName = defineCE( + // @ts-expect-error + class extends SlotMixin(LitElement) { + static properties = { currentValue: Number }; + + constructor() { + super(); + this.currentValue = 0; + } + + get slots() { + return { + ...super.slots, + 'focusable-node': () => ({ + template: html`<${complexSlotTag}> `, + }), + }; + } + + render() { + return html``; + } + + get _focusableNode() { + return /** @type HTMLSpanElement */ ( + Array.from(this.children).find(elm => elm.slot === 'focusable-node') + ?.firstElementChild + ); + } + }, + ); + const el = /** @type {* & SlotHost} */ (await fixture(`<${tagName}>`)); + + el._focusableNode._buttonNode.focus(); + + expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); + + el.currentValue = 1; + await el.updateComplete; + + expect(document.activeElement).to.equal(el._focusableNode); + expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); + }); + + it('allows for rerendering complex shadow root into slot as a direct child', async () => { + const complexSlotTagName = defineCE( + class extends LitElement { + render() { + return html` + + + `; + } + + get _buttonNode() { + // @ts-expect-error + return this.shadowRoot.querySelector('button'); + } + }, + ); + + const complexSlotTag = unsafeStatic(complexSlotTagName); + + const tagName = defineCE( + // @ts-expect-error + class extends SlotMixin(LitElement) { + static properties = { currentValue: Number }; + + constructor() { + super(); + this.currentValue = 0; + } + + get slots() { + return { + ...super.slots, + 'focusable-node': () => ({ + template: html`<${complexSlotTag}> `, + renderAsDirectHostChild: true, + }), + }; + } + + render() { + return html``; + } + + get _focusableNode() { + return /** @type HTMLSpanElement */ ( + Array.from(this.children).find(elm => elm.slot === 'focusable-node') + ); + } + }, + ); + const el = /** @type {* & SlotHost} */ (await fixture(`<${tagName}>`)); + + el._focusableNode._buttonNode.focus(); + + expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); + + el.currentValue = 1; + await el.updateComplete; + + expect(document.activeElement).to.equal(el._focusableNode); + expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); + }); + + describe('firstRenderOnConnected (for backwards compatibility)', () => { + it('does render on connected when firstRenderOnConnected:true', async () => { + // Start with elem that does not render on connectedCallback + const tag = defineCE( + // @ts-expect-error + class extends SlotMixin(LitElement) { + static properties = { currentValue: Number }; + + constructor() { + super(); + this.currentValue = 0; + } + + get slots() { + return { + ...super.slots, + template: () => ({ + firstRenderOnConnected: true, + template: html`${this.currentValue} `, + }), + }; + } + + render() { + return html``; + } + + get _templateNode() { + return /** @type HTMLSpanElement */ ( + Array.from(this.children).find(elm => elm.slot === 'template') + ); + } + }, + ); + const el = /** @type {* & SlotHost} */ (fixtureSync(`<${tag}>`)); + expect(el._templateNode.slot).to.equal('template'); + expect(el._templateNode.textContent?.trim()).to.equal('0'); + }); + + it('does not render on connected when firstRenderOnConnected:false', async () => { + // Start with elem that does not render on connectedCallback + const tag = defineCE( + // @ts-expect-error + class extends SlotMixin(LitElement) { + static properties = { currentValue: Number }; + + constructor() { + super(); + this.currentValue = 0; + } + + get slots() { + return { + ...super.slots, + template: () => ({ template: html`${this.currentValue} ` }), + }; + } + + render() { + return html``; + } + + get _templateNode() { + return /** @type HTMLSpanElement */ ( + Array.from(this.children).find(elm => elm.slot === 'template') + ); + } + }, + ); + const el = /** @type {* & SlotHost} */ (fixtureSync(`<${tag}>`)); + expect(el._templateNode).to.be.undefined; + await el.updateComplete; + expect(el._templateNode.slot).to.equal('template'); + expect(el._templateNode.textContent?.trim()).to.equal('0'); + }); + }); + }); + + describe('SlotFunctionResult types', () => { + it('supports complex dom trees as element (type "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 (type "undefined")', 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('supports templates (type "TemplateResult")', async () => { + const tag = defineCE( + class extends SlotMixin(LitElement) { + get slots() { + return { + ...super.slots, + template: () => html`text`, + }; + } + + render() { + return html``; + } + }, + ); + const el = await fixture(`<${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 (deprecated) afterRender logic (type "{ template:TemplateResults; afterRender: Function}" )', async () => { + let varThatProvesAfterRenderIsCalled = 'not called'; + + const tag = defineCE( + // @ts-expect-error + class extends SlotMixin(LitElement) { + static properties = { currentValue: Number }; + + constructor() { + super(); + this.currentValue = 0; + } + + get slots() { + return { + ...super.slots, + template: () => ({ + template: html`${this.currentValue}, `, + afterRender: () => { + varThatProvesAfterRenderIsCalled = 'called'; + }, + }), + }; + } + + render() { + return html``; + } + + get _templateNode() { + return /** @type HTMLSpanElement */ ( + Array.from(this.children).find(elm => elm.slot === 'template')?.firstElementChild + ); + } + }, + ); + const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}>`)); + expect(el._templateNode.textContent).to.equal('0'); + + el.currentValue = 1; + await el.updateComplete; + + expect(varThatProvesAfterRenderIsCalled).to.equal('called'); + expect(el._templateNode.textContent).to.equal('1'); + }); + }); + + // eslint-disable-next-line no-only-tests/no-only-tests + describe.only('Scoped Registries', () => { + it('supports scoped elements when polyfill loaded', async () => { + const outputObj = mockScopedRegistry(); + + class ScopedEl extends LitElement {} + + const tagName = defineCE( + // @ts-ignore + class extends ScopedElementsMixin(SlotMixin(LitElement)) { + static get scopedElements() { + return { + // @ts-expect-error + ...super.scopedElements, + 'scoped-elm': ScopedEl, + }; + } + + get slots() { + return { + ...super.slots, + template: () => html``, + }; + } + + render() { + return html``; + } + }, + ); + + const tag = unsafeStatic(tagName); + await fixture(html`<${tag}>`); + + expect(outputObj.createElementCallCount).to.equal(1); + + unMockScopedRegistry(); + }); + + // eslint-disable-next-line no-only-tests/no-only-tests + it.only('does not scope elements when polyfill not loaded', async () => { // @ts-expect-error ShadowRoot.prototype.createElement = null; class ScopedEl extends LitElement {} From 70055e647b374cd7a2326913a83af61f0a1133f0 Mon Sep 17 00:00:00 2001 From: aghArdeshir Date: Sun, 6 Oct 2024 01:47:02 +0200 Subject: [PATCH 3/3] make changes less --- web-test-runner.config.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-test-runner.config.mjs b/web-test-runner.config.mjs index 4c24e307a5..a9013628ab 100644 --- a/web-test-runner.config.mjs +++ b/web-test-runner.config.mjs @@ -55,9 +55,9 @@ export default { testRunnerHtml, browserLogs: true, browsers: [ - // playwrightLauncher({ product: 'firefox', concurrency: 1 }), + playwrightLauncher({ product: 'firefox', concurrency: 1 }), playwrightLauncher({ product: 'chromium' }), - // playwrightLauncher({ product: 'webkit' }), + playwrightLauncher({ product: 'webkit' }), ], groups: packages.map(pkg => ({ name: pkg.name,