From 63d86fca31e709b861a3c93cbf2530ed90a52891 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Wed, 16 Apr 2025 00:21:29 +0300 Subject: [PATCH] fix(core): fix SlotController.isEmpty() --- .changeset/shaky-things-beg.md | 6 + .../controllers/slot-controller-server.ts | 5 +- .../controllers/test/slot-controller.spec.ts | 239 ++++++++++++++++-- 3 files changed, 232 insertions(+), 18 deletions(-) create mode 100644 .changeset/shaky-things-beg.md diff --git a/.changeset/shaky-things-beg.md b/.changeset/shaky-things-beg.md new file mode 100644 index 0000000000..f3ade689c6 --- /dev/null +++ b/.changeset/shaky-things-beg.md @@ -0,0 +1,6 @@ +--- +"@patternfly/pfe-core": patch +--- + +`SlotController`: correctly report the state of the default slot in `isEmpty()` calls with no arguments + \ No newline at end of file diff --git a/core/pfe-core/controllers/slot-controller-server.ts b/core/pfe-core/controllers/slot-controller-server.ts index a9ea9ae53f..40b7ea649b 100644 --- a/core/pfe-core/controllers/slot-controller-server.ts +++ b/core/pfe-core/controllers/slot-controller-server.ts @@ -26,7 +26,7 @@ export class SlotController implements SlotControllerPublicAPI { .map(x => x.trim()); } - getSlotted(..._: string[]): T[] { + getSlotted(..._: (string | null)[]): T[] { return []; } @@ -34,6 +34,9 @@ export class SlotController implements SlotControllerPublicAPI { const attr = this.host.getAttribute(SlotController.attribute); const anon = this.host.hasAttribute(SlotController.anonymousAttribute); const hints = new Set(this.fromAttribute(attr)); + if (!names.length) { + names.push(null); + } return names.every(x => x === null ? anon : hints.has(x)); } diff --git a/core/pfe-core/controllers/test/slot-controller.spec.ts b/core/pfe-core/controllers/test/slot-controller.spec.ts index ccd6a96550..4e7f6b6949 100644 --- a/core/pfe-core/controllers/test/slot-controller.spec.ts +++ b/core/pfe-core/controllers/test/slot-controller.spec.ts @@ -1,17 +1,28 @@ -import { expect, fixture, nextFrame } from '@open-wc/testing'; +import { expect, fixture } from '@open-wc/testing'; import { customElement } from 'lit/decorators/custom-element.js'; import { LitElement, html, type TemplateResult } from 'lit'; import { SlotController } from '../slot-controller.js'; +import { SlotController as SlotControllerServer } from '../slot-controller-server.js'; -@customElement('test-slot-controller-with-named-and-anonymous') -class TestSlotControllerWithNamedAndAnonymous extends LitElement { +@customElement('test-slot-controller') +class TestSlotController extends LitElement { controller = new SlotController(this, 'a', null); render(): TemplateResult { return html` - + + `; + } +} + +@customElement('test-slot-controller-server') +class TestSlotControllerServer extends LitElement { + controller = new SlotControllerServer(this, 'a', null); + render(): TemplateResult { + return html` + `; } @@ -20,10 +31,10 @@ class TestSlotControllerWithNamedAndAnonymous extends LitElement { describe('SlotController', function() { describe('with named and anonymous slots', function() { describe('with no content', function() { - let element: TestSlotControllerWithNamedAndAnonymous; + let element: TestSlotController; beforeEach(async function() { element = await fixture(html` - + `); }); it('reports empty named slots', function() { @@ -50,12 +61,12 @@ describe('SlotController', function() { }); describe('with element content in default slot', function() { - let element: TestSlotControllerWithNamedAndAnonymous; + let element: TestSlotController; beforeEach(async function() { element = await fixture(html` - +

element

-
+ `); }); it('reports empty named slots', function() { @@ -82,12 +93,12 @@ describe('SlotController', function() { }); describe('with element content in named slot', function() { - let element: TestSlotControllerWithNamedAndAnonymous; + let element: TestSlotController; beforeEach(async function() { element = await fixture(html` - +

element

-
+ `); }); it('reports non-empty named slots', function() { @@ -114,12 +125,12 @@ describe('SlotController', function() { }); describe('with text content in default slot', function() { - let element: TestSlotControllerWithNamedAndAnonymous; + let element: TestSlotController; beforeEach(async function() { element = await fixture(html` - + text - + `); }); it('reports empty named slots', function() { @@ -137,10 +148,204 @@ describe('SlotController', function() { it('returns empty list for getSlotted("a")', function() { expect(element.controller.getSlotted('a')).to.be.empty; }); - it('returns lengthy list for getSlotted(null)', function() { + it('returns empty list for getSlotted(null)', function() { expect(element.controller.getSlotted(null)).to.be.empty; }); - it('returns lengthy list for getSlotted()', function() { + it('returns empty list for getSlotted()', function() { + expect(element.controller.getSlotted()).to.be.empty; + }); + }); + + describe('with white space in default slot', function() { + let element: TestSlotController; + beforeEach(async function() { + element = await fixture(html` + + + + `); + }); + it('reports empty named slots', function() { + expect(element.controller.hasSlotted('a')).to.be.false; + expect(element.controller.isEmpty('a')).to.be.true; + }); + it('reports empty default slot', function() { + expect(element.controller.hasSlotted(null)).to.be.false; + expect(element.controller.isEmpty(null)).to.be.true; + }); + it('reports empty default slot with no arguments', function() { + expect(element.controller.hasSlotted()).to.be.false; + expect(element.controller.isEmpty()).to.be.true; + }); + it('returns empty list for getSlotted("a")', function() { + expect(element.controller.getSlotted('a')).to.be.empty; + }); + it('returns empty list for getSlotted(null)', function() { + expect(element.controller.getSlotted(null)).to.be.empty; + }); + it('returns empty list for getSlotted()', function() { + expect(element.controller.getSlotted()).to.be.empty; + }); + }); + }); +}); + +describe('SlotController (server)', function() { + describe('with named and anonymous slots', function() { + describe('with no ssr hint attrs', function() { + let element: TestSlotControllerServer; + beforeEach(async function() { + element = await fixture(html` + + `); + }); + it('reports empty named slots', function() { + expect(element.controller.hasSlotted('a')).to.be.false; + expect(element.controller.isEmpty('a')).to.be.true; + }); + it('reports empty default slot', function() { + expect(element.controller.hasSlotted(null)).to.be.false; + expect(element.controller.isEmpty(null)).to.be.true; + }); + it('reports empty default slot with no arguments', function() { + expect(element.controller.hasSlotted()).to.be.false; + expect(element.controller.isEmpty()).to.be.true; + }); + it('returns empty list for getSlotted("a")', function() { + expect(element.controller.getSlotted('a')).to.be.empty; + }); + it('returns empty list for getSlotted(null)', function() { + expect(element.controller.getSlotted(null)).to.be.empty; + }); + it('returns empty list for getSlotted()', function() { + expect(element.controller.getSlotted()).to.be.empty; + }); + }); + + describe('with ssr-hint-has-slotted-default attr', function() { + let element: TestSlotController; + beforeEach(async function() { + element = await fixture(html` + +

element

+
+ `); + }); + it('reports empty named slots', function() { + expect(element.controller.hasSlotted('a')).to.be.false; + expect(element.controller.isEmpty('a')).to.be.true; + }); + it('reports non-empty default slot', function() { + expect(element.controller.hasSlotted(null)).to.be.true; + expect(element.controller.isEmpty(null)).to.be.false; + }); + it('reports non-empty default slot with no arguments', function() { + expect(element.controller.hasSlotted()).to.be.true; + expect(element.controller.isEmpty()).to.be.false; + }); + it('returns empty list for getSlotted("a")', function() { + expect(element.controller.getSlotted('a')).to.be.empty; + }); + it('returns empty list for getSlotted(null)', function() { + expect(element.controller.getSlotted(null)).to.be.empty; + }); + it('returns empty list for getSlotted()', function() { + expect(element.controller.getSlotted()).to.be.empty; + }); + }); + + describe('with ssr-hint-has-slotted="a" attr', function() { + let element: TestSlotController; + beforeEach(async function() { + element = await fixture(html` + +

element

+
+ `); + }); + it('reports non-empty named slots', function() { + expect(element.controller.hasSlotted('a')).to.be.true; + expect(element.controller.isEmpty('a')).to.be.false; + }); + it('reports empty default slot', function() { + expect(element.controller.hasSlotted(null)).to.be.false; + expect(element.controller.isEmpty(null)).to.be.true; + }); + it('reports empty default slot with no arguments', function() { + expect(element.controller.hasSlotted()).to.be.false; + expect(element.controller.isEmpty()).to.be.true; + }); + it('returns empty list for getSlotted("a")', function() { + expect(element.controller.getSlotted('a')).to.be.empty; + }); + it('returns empty list for getSlotted(null)', function() { + expect(element.controller.getSlotted(null)).to.be.empty; + }); + it('returns empty list for getSlotted()', function() { + expect(element.controller.getSlotted()).to.be.empty; + }); + }); + + describe('with ssr-hint-has-slotted-default attr (text node)', function() { + let element: TestSlotController; + beforeEach(async function() { + element = await fixture(html` + + text + + `); + }); + it('reports empty named slots', function() { + expect(element.controller.hasSlotted('a')).to.be.false; + expect(element.controller.isEmpty('a')).to.be.true; + }); + it('reports non-empty default slot', function() { + expect(element.controller.hasSlotted(null)).to.be.true; + expect(element.controller.isEmpty(null)).to.be.false; + }); + it('reports non-empty default slot with no arguments', function() { + expect(element.controller.hasSlotted()).to.be.true; + expect(element.controller.isEmpty()).to.be.false; + }); + it('returns empty list for getSlotted("a")', function() { + expect(element.controller.getSlotted('a')).to.be.empty; + }); + it('returns empty list for getSlotted(null)', function() { + expect(element.controller.getSlotted(null)).to.be.empty; + }); + it('returns empty list for getSlotted()', function() { + expect(element.controller.getSlotted()).to.be.empty; + }); + }); + + describe('with no ssr hint attrs (white space text node)', function() { + let element: TestSlotController; + beforeEach(async function() { + element = await fixture(html` + + + + `); + }); + it('reports empty named slots', function() { + expect(element.controller.hasSlotted('a')).to.be.false; + expect(element.controller.isEmpty('a')).to.be.true; + }); + it('reports empty default slot', function() { + expect(element.controller.hasSlotted(null)).to.be.false; + expect(element.controller.isEmpty(null)).to.be.true; + }); + it('reports empty default slot with no arguments', function() { + expect(element.controller.hasSlotted()).to.be.false; + expect(element.controller.isEmpty()).to.be.true; + }); + it('returns empty list for getSlotted("a")', function() { + expect(element.controller.getSlotted('a')).to.be.empty; + }); + it('returns empty list for getSlotted(null)', function() { + expect(element.controller.getSlotted(null)).to.be.empty; + }); + it('returns empty list for getSlotted()', function() { expect(element.controller.getSlotted()).to.be.empty; }); });