From c120b05b8fc7f05a344ef0b7422044e0e0107456 Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Wed, 20 Mar 2024 17:24:38 +0000 Subject: [PATCH 01/15] refactor(sbb-calendar): implement initial support for other date libraries BREAKING CHANGE: The `SbbDatepicker` property `selectedDate` has been renamed to `selected`. This also applies to the attribute `selected-date`, which has been renamed to `selected`. Additionally the `DateAdapter` (and `NativeDateAdapter`) have been superficially refactored. An important change is that the month is now `1`-based, instead of `0`-based. --- src/components/calendar/calendar.e2e.ts | 2 +- src/components/calendar/calendar.ts | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/calendar/calendar.e2e.ts b/src/components/calendar/calendar.e2e.ts index 54c390f404..0e30fe1afc 100644 --- a/src/components/calendar/calendar.e2e.ts +++ b/src/components/calendar/calendar.e2e.ts @@ -8,7 +8,7 @@ import { SbbCalendarElement } from './calendar'; import '../button'; -describe(`sbb-calendar`, () => { +describe(`sbb-calendar with ${fixture.name}`, () => { let element: SbbCalendarElement; const waitForTransition = async (): Promise => { await waitForLitRender(element); diff --git a/src/components/calendar/calendar.ts b/src/components/calendar/calendar.ts index 4f5b80b468..d6906c290b 100644 --- a/src/components/calendar/calendar.ts +++ b/src/components/calendar/calendar.ts @@ -26,12 +26,10 @@ import { i18nYearMonthSelection, } from '../core/i18n'; import type { SbbDateLike } from '../core/interfaces'; - -import style from './calendar.scss?lit&inline'; - import '../button/secondary-button'; import '../icon'; -import '../screenreader-only'; + +import style from './calendar.scss?lit&inline'; /** * In keyboard navigation, the cell's index and the element's index in its month / year batch must be distinguished; @@ -819,9 +817,9 @@ export class SbbCalendarElement extends LitElement {
${this._createLabelForDayView(this._activeDate)} ${this._wide ? this._createLabelForDayView(nextMonthActiveDate!) : nothing} - + ${this._createAriaLabelForDayView(this._activeDate, nextMonthActiveDate!)} - +
${this._getArrow( 'right', @@ -899,7 +897,7 @@ export class SbbCalendarElement extends LitElement { return this._weekdays.map( (day: Weekday) => html` - ${day.long} + ${day.long} `, @@ -1009,7 +1007,7 @@ export class SbbCalendarElement extends LitElement { ${this._chosenYear} ${this._wide ? ` - ${this._chosenYear! + 1}` : nothing} - ${this._chosenYear} `; + ${this._chosenYear} `; } /** Creates the table for the month selection view. */ @@ -1156,7 +1154,7 @@ export class SbbCalendarElement extends LitElement { ${yearLabel} - ${yearLabel} + ${yearLabel} `; } From 3ed6b533f405a8369997172a4fe5c5100ffa5534 Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Thu, 21 Mar 2024 09:11:43 +0000 Subject: [PATCH 02/15] fix: review --- src/components/calendar/calendar.e2e.ts | 2 +- src/components/calendar/calendar.ts | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/calendar/calendar.e2e.ts b/src/components/calendar/calendar.e2e.ts index 0e30fe1afc..54c390f404 100644 --- a/src/components/calendar/calendar.e2e.ts +++ b/src/components/calendar/calendar.e2e.ts @@ -8,7 +8,7 @@ import { SbbCalendarElement } from './calendar'; import '../button'; -describe(`sbb-calendar with ${fixture.name}`, () => { +describe(`sbb-calendar`, () => { let element: SbbCalendarElement; const waitForTransition = async (): Promise => { await waitForLitRender(element); diff --git a/src/components/calendar/calendar.ts b/src/components/calendar/calendar.ts index d6906c290b..4f5b80b468 100644 --- a/src/components/calendar/calendar.ts +++ b/src/components/calendar/calendar.ts @@ -26,11 +26,13 @@ import { i18nYearMonthSelection, } from '../core/i18n'; import type { SbbDateLike } from '../core/interfaces'; -import '../button/secondary-button'; -import '../icon'; import style from './calendar.scss?lit&inline'; +import '../button/secondary-button'; +import '../icon'; +import '../screenreader-only'; + /** * In keyboard navigation, the cell's index and the element's index in its month / year batch must be distinguished; * this is necessary because the navigation in the vertical direction using some keys is restricted to a single month for days, @@ -817,9 +819,9 @@ export class SbbCalendarElement extends LitElement {
${this._createLabelForDayView(this._activeDate)} ${this._wide ? this._createLabelForDayView(nextMonthActiveDate!) : nothing} - + ${this._createAriaLabelForDayView(this._activeDate, nextMonthActiveDate!)} - +
${this._getArrow( 'right', @@ -897,7 +899,7 @@ export class SbbCalendarElement extends LitElement { return this._weekdays.map( (day: Weekday) => html` - ${day.long} + ${day.long} `, @@ -1007,7 +1009,7 @@ export class SbbCalendarElement extends LitElement { ${this._chosenYear} ${this._wide ? ` - ${this._chosenYear! + 1}` : nothing} - ${this._chosenYear} `; + ${this._chosenYear} `; } /** Creates the table for the month selection view. */ @@ -1154,7 +1156,7 @@ export class SbbCalendarElement extends LitElement { ${yearLabel} - ${yearLabel} + ${yearLabel} `; } From 7bdbb5abd84e6d1e844448baa11e62754a8bcd99 Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Fri, 8 Mar 2024 16:22:39 +0100 Subject: [PATCH 03/15] feat: implement support for server side rendering (SSR) --- config/lit-ssr-worker.js | 31 ++- package.json | 1 + .../action-group/action-group.e2e.ts | 36 +-- .../alert/alert-group/alert-group.e2e.ts | 30 ++- src/components/alert/alert/alert.e2e.ts | 13 +- .../autocomplete/autocomplete.e2e.ts | 31 ++- src/components/autocomplete/autocomplete.ts | 14 +- .../breadcrumb-group/breadcrumb-group.e2e.ts | 48 ++-- .../breadcrumb-group/breadcrumb-group.ts | 4 +- .../breadcrumb/breadcrumb-group/readme.md | 5 + .../breadcrumb/breadcrumb/breadcrumb.e2e.ts | 10 +- .../breadcrumb/breadcrumb/breadcrumb.ts | 8 +- .../button/button-link/button-link.e2e.ts | 7 +- .../button/button-static/button-static.e2e.ts | 7 +- src/components/button/button/button.e2e.ts | 10 +- src/components/calendar/calendar.e2e.ts | 12 +- .../card/card-badge/card-badge.e2e.ts | 10 +- .../card/card-button/card-button.e2e.ts | 29 ++- .../card/card-link/card-link.e2e.ts | 64 +++-- src/components/card/card/card.e2e.ts | 38 ++- .../checkbox-group/checkbox-group.e2e.ts | 23 +- .../checkbox/checkbox/checkbox.e2e.ts | 13 +- src/components/chip/chip.e2e.ts | 10 +- .../container/container/container.e2e.ts | 8 +- .../container/sticky-bar/sticky-bar.e2e.ts | 77 +++--- .../action-base-element.e2e.ts | 8 +- .../button-base-element.e2e.ts | 10 +- .../core/common-behaviors/hydration-mixin.ts | 10 +- src/components/core/common-behaviors/index.ts | 3 +- .../common-behaviors/link-base-element.e2e.ts | 10 +- .../common-behaviors/link-base-element.ts | 3 +- ...st-element.ts => named-slot-list-mixin.ts} | 41 ++- .../common-behaviors/slot-child-observer.ts | 87 ------- src/components/core/testing/fixture.ts | 1 + src/components/core/testing/test-setup-ssr.ts | 29 +++ .../datepicker-next-day.e2e.ts | 103 +++++--- .../datepicker-next-day.ts | 2 +- .../datepicker-previous-day.e2e.ts | 99 ++++--- .../datepicker-previous-day.ts | 2 +- .../datepicker-toggle.e2e.ts | 85 +++--- .../datepicker/datepicker/datepicker.e2e.ts | 49 ++-- src/components/dialog/dialog.e2e.ts | 74 +++--- src/components/divider/divider.e2e.ts | 10 +- .../expansion-panel-content.e2e.ts | 7 +- .../expansion-panel-header.e2e.ts | 11 +- .../expansion-panel/expansion-panel.e2e.ts | 48 ++-- .../file-selector/file-selector.e2e.ts | 10 +- src/components/footer/footer.e2e.ts | 8 +- src/components/form-error/form-error.e2e.ts | 8 +- .../form-field-clear.spec.snap.js | 2 +- .../form-field-clear/form-field-clear.e2e.ts | 7 +- .../form-field/form-field/form-field.e2e.ts | 142 ++++++---- .../form-field/form-field/form-field.spec.ts | 8 +- .../form-field/form-field/form-field.ts | 9 +- .../header/header-button/header-button.e2e.ts | 10 +- .../header/header-link/header-link.e2e.ts | 11 +- src/components/header/header/header.e2e.ts | 83 +++--- src/components/header/header/header.ts | 12 +- src/components/icon/icon.e2e.ts | 8 +- src/components/image/image.e2e.ts | 12 +- .../journey-header/journey-header.e2e.ts | 10 +- .../journey-summary/journey-summary.e2e.ts | 10 +- src/components/link-list/link-list.e2e.ts | 59 +++-- src/components/link-list/link-list.ts | 4 +- .../link/link-button/link-button.e2e.ts | 10 +- .../link/link-static/link-static.e2e.ts | 10 +- src/components/link/link/link.e2e.ts | 10 +- .../loading-indicator.e2e.ts | 10 +- .../map-container/map-container.e2e.ts | 23 +- .../menu/menu-button/menu-button.e2e.ts | 10 +- .../menu/menu-link/menu-link.e2e.ts | 7 +- src/components/menu/menu/menu.e2e.ts | 48 ++-- src/components/menu/menu/menu.ts | 17 +- src/components/message/message.e2e.ts | 8 +- .../navigation-button.e2e.ts | 7 +- .../navigation-link/navigation-link.e2e.ts | 7 +- .../navigation-list/navigation-list.e2e.ts | 22 +- .../navigation-list/navigation-list.ts | 4 +- .../navigation-marker.e2e.ts | 7 +- .../navigation-marker/navigation-marker.ts | 4 +- .../navigation-section.e2e.ts | 40 +-- .../navigation/navigation/navigation.e2e.ts | 54 ++-- .../notification/notification.e2e.ts | 21 +- .../option/optgroup/optgroup.e2e.ts | 23 +- src/components/option/optgroup/optgroup.ts | 20 +- src/components/option/option/option.e2e.ts | 59 +++-- src/components/option/option/option.ts | 32 ++- src/components/option/option/readme.md | 10 +- .../pearl-chain-vertical.e2e.ts | 10 +- src/components/pearl-chain/pearl-chain.e2e.ts | 10 +- .../popover-trigger/popover-trigger.e2e.ts | 25 +- src/components/popover/popover/popover.e2e.ts | 90 ++++--- .../radio-button-group.e2e.ts | 29 ++- .../radio-button/radio-button.e2e.ts | 10 +- .../screenreader-only.e2e.ts | 10 +- src/components/select/select.e2e.ts | 69 ++--- src/components/select/select.ts | 18 +- .../selection-panel/selection-panel.e2e.ts | 184 +++++++------ .../skiplink-list/skiplink-list.e2e.ts | 29 ++- src/components/skiplink-list/skiplink-list.ts | 4 +- src/components/slider/slider.e2e.ts | 27 +- src/components/status/status.e2e.ts | 10 +- .../tabs/tab-group/tab-group.e2e.ts | 11 +- .../tabs/tab-title/tab-title.e2e.ts | 8 +- src/components/tag/tag-group/tag-group.e2e.ts | 225 +++++++++------- src/components/tag/tag-group/tag-group.ts | 9 +- src/components/tag/tag/tag.e2e.ts | 12 +- src/components/teaser-hero/teaser-hero.e2e.ts | 8 +- src/components/teaser-paid/teaser-paid.e2e.ts | 20 +- src/components/teaser/teaser.e2e.ts | 10 +- src/components/time-input/time-input.e2e.ts | 64 +++-- .../timetable-duration.e2e.ts | 7 +- .../timetable-occupancy-icon.e2e.snap.js | 55 +++- .../timetable-occupancy-icon.e2e.ts | 8 +- .../timetable-occupancy.e2e.ts | 13 +- .../timetable-row/timetable-row.e2e.ts | 10 +- src/components/title/title.e2e.ts | 8 +- src/components/toast/toast.e2e.ts | 58 +++-- .../toggle-check/toggle-check.e2e.ts | 12 +- .../toggle/toggle-option/toggle-option.e2e.ts | 11 +- src/components/toggle/toggle/toggle.e2e.ts | 25 +- .../train-blocked-passage.e2e.ts | 10 +- .../train-formation/train-formation.e2e.ts | 243 +++++++++++------- .../train/train-formation/train-formation.ts | 4 +- .../train/train-wagon/train-wagon.e2e.ts | 15 +- .../train/train-wagon/train-wagon.ts | 11 +- src/components/train/train/train.e2e.ts | 27 +- src/components/train/train/train.ts | 4 +- .../visual-checkbox/visual-checkbox.e2e.ts | 7 +- .../boilerplate/component.e2e.ts | 8 +- web-test-runner.config.js | 14 +- yarn.lock | 9 +- 132 files changed, 2120 insertions(+), 1438 deletions(-) rename src/components/core/common-behaviors/{named-slot-list-element.ts => named-slot-list-mixin.ts} (83%) delete mode 100644 src/components/core/common-behaviors/slot-child-observer.ts create mode 100644 src/components/core/testing/test-setup-ssr.ts diff --git a/config/lit-ssr-worker.js b/config/lit-ssr-worker.js index 02fcd3aca7..e85af27aec 100644 --- a/config/lit-ssr-worker.js +++ b/config/lit-ssr-worker.js @@ -1,9 +1,11 @@ +import { createHash } from 'crypto'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import { dirname, join, relative, resolve } from 'path'; import { parentPort, workerData } from 'worker_threads'; + import { render } from '@lit-labs/ssr'; -import { build } from 'vite'; -import { createHash } from 'crypto'; -import { dirname, join, relative, resolve } from 'path'; +import * as esbuild from 'esbuild'; +import { sassPlugin } from 'esbuild-sass-plugin'; if (parentPort === null) { throw new Error('worker.js must only be run in a worker thread'); @@ -50,6 +52,28 @@ async function buildModules(buildCacheOutDir) { .join('\n'); writeFileSync(entry, importStatement, 'utf8'); + await esbuild.build({ + entryPoints: [entry], + outdir: buildCacheOutDir.pathname, + bundle: true, + format: 'esm', + platform: 'node', + target: 'node20', + external: Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }), + logLevel: 'warning', + plugins: [ + sassPlugin({ + type: 'lit-css', + loadPaths: [ + new URL('../', import.meta.url).pathname, + new URL('../node_modules/', import.meta.url).pathname, + ], + }), + ], + }); + + /* + // Vite build config. It is slower by about a factor of 3. await build({ root: new URL('..', import.meta.url).pathname, mode: 'development', @@ -66,6 +90,7 @@ async function buildModules(buildCacheOutDir) { sourcemap: 'inline', }, }); + */ } /** Generate a hash from the contents of the given modules and their import chain. */ diff --git a/package.json b/package.json index c7cbf75331..d498f034cd 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "chromatic": "11.2.0", "custom-elements-manifest": "^2.0.0", "date-fns": "3.6.0", + "esbuild-sass-plugin": "^3.1.0", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "npm:eslint-plugin-i@latest", diff --git a/src/components/action-group/action-group.e2e.ts b/src/components/action-group/action-group.e2e.ts index 72ca654ecf..765fd4a2d9 100644 --- a/src/components/action-group/action-group.e2e.ts +++ b/src/components/action-group/action-group.e2e.ts @@ -1,30 +1,36 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; import type { SbbSecondaryButtonElement } from '../button'; -import { waitForLitRender } from '../core/testing'; +import { fixture, waitForLitRender } from '../core/testing'; import type { SbbBlockLinkElement } from '../link'; import '../button/secondary-button'; import '../link/block-link'; import { SbbActionGroupElement } from './action-group'; -describe('sbb-action-group', () => { +import '../button'; +import '../link'; + +describe(`sbb-action-group with ${fixture.name}`, () => { let element: SbbActionGroupElement; beforeEach(async () => { - element = await fixture(html` - - Button - - Link - - - `); + element = await fixture( + html` + + Button + + Link + + + `, + { modules: ['./action-group.ts', '../button/index.ts', '../link/index.ts'] }, + ); await waitForLitRender(element); }); diff --git a/src/components/alert/alert-group/alert-group.e2e.ts b/src/components/alert/alert-group/alert-group.e2e.ts index bcdb0edd22..a9213139b6 100644 --- a/src/components/alert/alert-group/alert-group.e2e.ts +++ b/src/components/alert/alert-group/alert-group.e2e.ts @@ -1,15 +1,15 @@ -import { expect, fixture } from '@open-wc/testing'; +import { expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; import type { SbbTransparentButtonElement } from '../../button'; -import { waitForCondition, EventSpy, waitForLitRender } from '../../core/testing'; +import { waitForCondition, EventSpy, waitForLitRender, fixture } from '../../core/testing'; import type { SbbAlertElement } from '../alert'; import { SbbAlertGroupElement } from './alert-group'; import '../alert'; -describe('sbb-alert-group', () => { +describe(`sbb-alert-group with ${fixture.name}`, () => { let element: SbbAlertGroupElement; it('should handle events ond states on interacting with alerts', async () => { @@ -18,16 +18,19 @@ describe('sbb-alert-group', () => { const accessibilityTitleLevel = '3'; // Given sbb-alert-group with two alerts - element = await fixture(html` - - First - Second - - `); + element = await fixture( + html` + + First + Second + + `, + { modules: ['./alert-group.ts', '../alert/index.ts'] }, + ); const didDismissAlertSpy = new EventSpy(SbbAlertGroupElement.events.didDismissAlert); const emptySpy = new EventSpy(SbbAlertGroupElement.events.empty); @@ -97,6 +100,7 @@ describe('sbb-alert-group', () => { // Given empty sbb-alert-group element = await fixture( html``, + { modules: ['./alert-group.ts'] }, ); const emptySpy = new EventSpy(SbbAlertGroupElement.events.empty); diff --git a/src/components/alert/alert/alert.e2e.ts b/src/components/alert/alert/alert.e2e.ts index 17e53940bc..b6cda38540 100644 --- a/src/components/alert/alert/alert.e2e.ts +++ b/src/components/alert/alert/alert.e2e.ts @@ -1,15 +1,15 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; -import { waitForCondition, EventSpy } from '../../core/testing'; +import { waitForCondition, EventSpy, fixture } from '../../core/testing'; import { SbbAlertElement } from './alert'; -describe('sbb-alert', () => { +describe(`sbb-alert with ${fixture.name}`, () => { let alert: SbbAlertElement; it('renders', async () => { - alert = await fixture(html``); + alert = await fixture(html``, { modules: ['./alert.ts'] }); assert.instanceOf(alert, SbbAlertElement); }); @@ -17,7 +17,9 @@ describe('sbb-alert', () => { const willOpenSpy = new EventSpy(SbbAlertElement.events.willOpen); const didOpenSpy = new EventSpy(SbbAlertElement.events.didOpen); - await fixture(html`Interruption`); + await fixture(html`Interruption`, { + modules: ['./alert.ts'], + }); await waitForCondition(() => willOpenSpy.events.length === 1); expect(willOpenSpy.count).to.be.equal(1); @@ -28,6 +30,7 @@ describe('sbb-alert', () => { it('should hide close button in readonly mode', async () => { alert = await fixture( html`Alert content`, + { modules: ['./alert.ts'] }, ); expect(alert.shadowRoot!.querySelector('.sbb-alert__close-button-wrapper')).to.be.null; diff --git a/src/components/autocomplete/autocomplete.e2e.ts b/src/components/autocomplete/autocomplete.e2e.ts index 66dde11e09..c540a14636 100644 --- a/src/components/autocomplete/autocomplete.e2e.ts +++ b/src/components/autocomplete/autocomplete.e2e.ts @@ -1,28 +1,31 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys, sendMouse } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { waitForCondition, waitForLitRender, EventSpy } from '../core/testing'; +import { waitForCondition, waitForLitRender, EventSpy, fixture } from '../core/testing'; import { SbbFormFieldElement } from '../form-field'; import { SbbOptionElement } from '../option'; import { SbbAutocompleteElement } from './autocomplete'; -describe('sbb-autocomplete', () => { +describe(`sbb-autocomplete with ${fixture.name}`, () => { let element: SbbAutocompleteElement, formField: SbbFormFieldElement, input: HTMLInputElement; beforeEach(async () => { - formField = await fixture(html` - - - - 1 - 2 - 3 - - - - `); + formField = await fixture( + html` + + + + 1 + 2 + 3 + + + + `, + { modules: ['../form-field/index.ts', './autocomplete.ts', '../option/index.ts'] }, + ); input = formField.querySelector('input')!; element = formField.querySelector('sbb-autocomplete')!; }); diff --git a/src/components/autocomplete/autocomplete.ts b/src/components/autocomplete/autocomplete.ts index 3483bd5272..f27589a696 100644 --- a/src/components/autocomplete/autocomplete.ts +++ b/src/components/autocomplete/autocomplete.ts @@ -4,7 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js'; import { ref } from 'lit/directives/ref.js'; import { assignId, getNextElementIndex } from '../core/a11y'; -import { hostAttributes, SbbNegativeMixin, SlotChildObserver } from '../core/common-behaviors'; +import { SbbHydrationMixin, SbbNegativeMixin, hostAttributes } from '../core/common-behaviors'; import { setAttribute, getDocumentWritingMode, @@ -44,7 +44,7 @@ let nextId = 0; @hostAttributes({ dir: getDocumentWritingMode(), }) -export class SbbAutocompleteElement extends SlotChildObserver(SbbNegativeMixin(LitElement)) { +export class SbbAutocompleteElement extends SbbNegativeMixin(SbbHydrationMixin(LitElement)) { public static override styles: CSSResultGroup = style; public static readonly events = { willOpen: 'willOpen', @@ -259,10 +259,6 @@ export class SbbAutocompleteElement extends SlotChildObserver(SbbNegativeMixin(L this._didLoad = true; } - public override checkChildren(): void { - this._highlightOptions(this.triggerElement?.value); - } - private _syncNegative(): void { this.querySelectorAll?.('sbb-divider').forEach((divider) => (divider.negative = this.negative)); @@ -550,6 +546,10 @@ export class SbbAutocompleteElement extends SlotChildObserver(SbbNegativeMixin(L removeAriaComboBoxAttributes(element); } + private _handleSlotchange(): void { + this._highlightOptions(this.triggerElement?.value); + } + protected override render(): TemplateResult { setAttribute(this, 'data-state', this._state); setAttribute(this, 'role', this._ariaRoleOnHost ? 'listbox' : null); @@ -572,7 +572,7 @@ export class SbbAutocompleteElement extends SlotChildObserver(SbbNegativeMixin(L id=${!this._ariaRoleOnHost ? this._overlayId : nothing} ${ref((containerRef) => (this._optionContainer = containerRef as HTMLElement))} > - + diff --git a/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.e2e.ts b/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.e2e.ts index 2dc512f10c..cd825c8dd9 100644 --- a/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.e2e.ts +++ b/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.e2e.ts @@ -1,25 +1,28 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys, setViewport } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { waitForCondition, EventSpy, waitForLitRender } from '../../core/testing'; +import { waitForCondition, EventSpy, waitForLitRender, fixture } from '../../core/testing'; import type { SbbBreadcrumbElement } from '../breadcrumb'; import '../breadcrumb'; import { SbbBreadcrumbGroupElement } from './breadcrumb-group'; -describe('sbb-breadcrumb-group', () => { +describe(`sbb-breadcrumb-group with ${fixture.name}`, () => { describe('without ellipsis', () => { let element: SbbBreadcrumbGroupElement; beforeEach(async () => { - element = await fixture(html` - - - One - Two - - `); + element = await fixture( + html` + + + One + Two + + `, + { modules: ['./breadcrumb-group.ts', '../breadcrumb/index.ts'] }, + ); await waitForLitRender(element); }); @@ -50,17 +53,20 @@ describe('sbb-breadcrumb-group', () => { beforeEach(async () => { await setViewport({ width: 160, height: 320 }); - breadcrumbGroup = await fixture(html` - - - First - Second - Third - Fourth - Fifth - Sixth - - `); + breadcrumbGroup = await fixture( + html` + + + First + Second + Third + Fourth + Fifth + Sixth + + `, + { modules: ['./breadcrumb-group.ts', '../breadcrumb/index.ts'] }, + ); await waitForLitRender(breadcrumbGroup); ellipsisListItemElement = breadcrumbGroup.shadowRoot!.querySelector( diff --git a/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.ts b/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.ts index 450c40622b..5adbeabd06 100644 --- a/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.ts +++ b/src/components/breadcrumb/breadcrumb-group/breadcrumb-group.ts @@ -10,8 +10,8 @@ import { customElement, state } from 'lit/decorators.js'; import { getNextElementIndex, isArrowKeyPressed, sbbInputModalityDetector } from '../../core/a11y'; import { + SbbNamedSlotListMixin, hostAttributes, - SbbNamedSlotListElementMixin, type WithListChildren, } from '../../core/common-behaviors'; import { LanguageController } from '../../core/common-behaviors'; @@ -34,7 +34,7 @@ import '../../icon'; @hostAttributes({ role: 'navigation', }) -export class SbbBreadcrumbGroupElement extends SbbNamedSlotListElementMixin< +export class SbbBreadcrumbGroupElement extends SbbNamedSlotListMixin< SbbBreadcrumbElement, typeof LitElement >(LitElement) { diff --git a/src/components/breadcrumb/breadcrumb-group/readme.md b/src/components/breadcrumb/breadcrumb-group/readme.md index 978bdeff29..94488aba78 100644 --- a/src/components/breadcrumb/breadcrumb-group/readme.md +++ b/src/components/breadcrumb/breadcrumb-group/readme.md @@ -25,6 +25,11 @@ the last element of the list receives the attribute `aria-current="page"`. +## Properties + +| Name | Attribute | Privacy | Type | Default | Description | +| ---- | --------- | ------- | ---- | ------- | ----------- | + ## Slots | Name | Description | diff --git a/src/components/breadcrumb/breadcrumb/breadcrumb.e2e.ts b/src/components/breadcrumb/breadcrumb/breadcrumb.e2e.ts index 78d6106841..b220ec114d 100644 --- a/src/components/breadcrumb/breadcrumb/breadcrumb.e2e.ts +++ b/src/components/breadcrumb/breadcrumb/breadcrumb.e2e.ts @@ -1,15 +1,17 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; -import { waitForCondition, EventSpy, waitForLitRender } from '../../core/testing'; +import { waitForCondition, EventSpy, waitForLitRender, fixture } from '../../core/testing'; import { SbbBreadcrumbElement } from './breadcrumb'; -describe('sbb-breadcrumb', () => { +describe(`sbb-breadcrumb with ${fixture.name}`, () => { let element: SbbBreadcrumbElement; beforeEach(async () => { - element = await fixture(html`Test`); + element = await fixture(html`Test`, { + modules: ['./breadcrumb.ts'], + }); }); it('renders', async () => { diff --git a/src/components/breadcrumb/breadcrumb/breadcrumb.ts b/src/components/breadcrumb/breadcrumb/breadcrumb.ts index b5ebb07325..8779904752 100644 --- a/src/components/breadcrumb/breadcrumb/breadcrumb.ts +++ b/src/components/breadcrumb/breadcrumb/breadcrumb.ts @@ -3,9 +3,9 @@ import { customElement, state } from 'lit/decorators.js'; import { html } from 'lit/static-html.js'; import { + SbbHydrationMixin, SbbIconNameMixin, SbbLinkBaseElement, - SlotChildObserver, } from '../../core/common-behaviors'; import '../../icon'; @@ -18,12 +18,12 @@ import style from './breadcrumb.scss?lit&inline'; * @slot icon - Use this to display an icon as breadcrumb. */ @customElement('sbb-breadcrumb') -export class SbbBreadcrumbElement extends SlotChildObserver(SbbIconNameMixin(SbbLinkBaseElement)) { +export class SbbBreadcrumbElement extends SbbIconNameMixin(SbbHydrationMixin(SbbLinkBaseElement)) { public static override styles: CSSResultGroup = style; @state() private _hasText = false; - protected override checkChildren(): void { + private _handleSlotchange(): void { this._hasText = Array.from(this.childNodes ?? []).some( (n) => !(n as Element).slot && n.textContent?.trim(), ); @@ -33,7 +33,7 @@ export class SbbBreadcrumbElement extends SlotChildObserver(SbbIconNameMixin(Sbb return html` ${this.renderIconSlot('sbb-breadcrumb__icon')} - + `; } diff --git a/src/components/button/button-link/button-link.e2e.ts b/src/components/button/button-link/button-link.e2e.ts index 5a4a03ac3a..fdfd4ca0be 100644 --- a/src/components/button/button-link/button-link.e2e.ts +++ b/src/components/button/button-link/button-link.e2e.ts @@ -1,17 +1,18 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { EventSpy, waitForCondition, waitForLitRender } from '../../core/testing'; +import { EventSpy, waitForCondition, waitForLitRender, fixture } from '../../core/testing'; import { SbbButtonLinkElement } from './button-link'; -describe('sbb-button-link', () => { +describe(`sbb-button-link with ${fixture.name}`, () => { let element: SbbButtonLinkElement; beforeEach(async () => { element = await fixture( html`I am a link`, + { modules: ['./button-link.ts'] }, ); }); diff --git a/src/components/button/button-static/button-static.e2e.ts b/src/components/button/button-static/button-static.e2e.ts index 7742273aa4..0432d47be2 100644 --- a/src/components/button/button-static/button-static.e2e.ts +++ b/src/components/button/button-static/button-static.e2e.ts @@ -1,17 +1,18 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { EventSpy, waitForCondition, waitForLitRender } from '../../core/testing'; +import { EventSpy, waitForCondition, waitForLitRender, fixture } from '../../core/testing'; import { SbbButtonStaticElement } from './button-static'; -describe('sbb-button-static', () => { +describe(`sbb-button-static with ${fixture.name}`, () => { let element: SbbButtonStaticElement; beforeEach(async () => { element = await fixture( html`I am a static button`, + { modules: ['./button-static.ts'] }, ); }); diff --git a/src/components/button/button/button.e2e.ts b/src/components/button/button/button.e2e.ts index 02e9adc3e1..fe9f5f21a5 100644 --- a/src/components/button/button/button.e2e.ts +++ b/src/components/button/button/button.e2e.ts @@ -1,16 +1,18 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { EventSpy, waitForCondition, waitForLitRender } from '../../core/testing'; +import { EventSpy, waitForCondition, waitForLitRender, fixture } from '../../core/testing'; import { SbbButtonElement } from './button'; -describe('sbb-button', () => { +describe(`sbb-button with ${fixture.name}`, () => { let element: SbbButtonElement; beforeEach(async () => { - element = await fixture(html`I am a button`); + element = await fixture(html`I am a button`, { + modules: ['./button.ts'], + }); }); it('renders', async () => { diff --git a/src/components/calendar/calendar.e2e.ts b/src/components/calendar/calendar.e2e.ts index 54c390f404..c7037839a5 100644 --- a/src/components/calendar/calendar.e2e.ts +++ b/src/components/calendar/calendar.e2e.ts @@ -1,14 +1,14 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { waitForCondition, waitForLitRender, EventSpy } from '../core/testing'; +import { waitForCondition, waitForLitRender, EventSpy, fixture } from '../core/testing'; import { SbbCalendarElement } from './calendar'; import '../button'; -describe(`sbb-calendar`, () => { +describe(`sbb-calendar with ${fixture.name}`, () => { let element: SbbCalendarElement; const waitForTransition = async (): Promise => { await waitForLitRender(element); @@ -18,10 +18,11 @@ describe(`sbb-calendar`, () => { beforeEach(async () => { element = await fixture( html``, + { modules: ['./calendar.ts'] }, ); }); - it('renders', async () => { + it.only('renders', async () => { assert.instanceOf(element, SbbCalendarElement); }); @@ -324,7 +325,8 @@ describe(`sbb-calendar`, () => { describe('navigation for year view', () => { beforeEach(async () => { element = await fixture( - html``, + html``, + { modules: ['./calendar.ts'] }, ); const yearSelectionButton: HTMLElement = element.shadowRoot!.querySelector( diff --git a/src/components/card/card-badge/card-badge.e2e.ts b/src/components/card/card-badge/card-badge.e2e.ts index f2e2686141..bdb0204bfd 100644 --- a/src/components/card/card-badge/card-badge.e2e.ts +++ b/src/components/card/card-badge/card-badge.e2e.ts @@ -1,13 +1,17 @@ -import { assert, fixture } from '@open-wc/testing'; +import { assert } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; +import { fixture } from '../../core/testing'; + import { SbbCardBadgeElement } from './card-badge'; -describe('sbb-card-badge', () => { +describe(`sbb-card-badge with ${fixture.name}`, () => { let element: SbbCardBadgeElement; it('renders', async () => { - element = await fixture(html``); + element = await fixture(html``, { + modules: ['./card-badge.ts'], + }); assert.instanceOf(element, SbbCardBadgeElement); }); }); diff --git a/src/components/card/card-button/card-button.e2e.ts b/src/components/card/card-button/card-button.e2e.ts index 2c075029fb..e0dcb88ed0 100644 --- a/src/components/card/card-button/card-button.e2e.ts +++ b/src/components/card/card-button/card-button.e2e.ts @@ -1,8 +1,14 @@ -import { expect, fixture } from '@open-wc/testing'; +import { expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { EventSpy, waitForCondition, waitForLitRender } from '../../core/testing'; +import { + EventSpy, + waitForCondition, + waitForLitRender, + fixture, + isNonHydratedSsr, +} from '../../core/testing'; import type { SbbCardElement } from '../card'; import type { SbbCardButtonElement } from './card-button'; @@ -10,12 +16,13 @@ import type { SbbCardButtonElement } from './card-button'; import '../card'; import './card-button'; -describe('sbb-card-button', () => { +describe(`sbb-card-button with ${fixture.name}`, () => { let element: SbbCardElement; it('should render an active sbb-card-button', async () => { element = await fixture( html`Click meContent`, + { modules: ['../card/index.ts', './card-button.ts'] }, ); expect(element).to.have.attribute('data-has-action'); @@ -25,7 +32,15 @@ describe('sbb-card-button', () => { const cardAction = element.querySelector('sbb-card-button'); expect(cardAction).dom.to.be.equal(` - + Click me `); @@ -35,6 +50,7 @@ describe('sbb-card-button', () => { it('should correctly toggle active state', async () => { element = await fixture( html`Click meContent`, + { modules: ['../card/index.ts', './card-button.ts'] }, ); expect(element).not.to.have.attribute('data-has-active-action'); @@ -52,6 +68,7 @@ describe('sbb-card-button', () => { `, + { modules: ['../card/index.ts', './card-button.ts'] }, ); expect(element).to.have.attribute('data-has-action'); @@ -75,6 +92,7 @@ describe('sbb-card-button', () => { `, + { modules: ['../card/index.ts', './card-button.ts'] }, ); expect(document.querySelector('button')).to.have.attribute('data-card-focusable'); @@ -105,6 +123,7 @@ describe('sbb-card-button', () => { Click me `, + { modules: ['../card/index.ts', './card-button.ts'] }, ); // Add a button to slot @@ -122,6 +141,7 @@ describe('sbb-card-button', () => { html` `, + { modules: ['../card/index.ts'] }, ); // Add a sbb-card-button @@ -140,6 +160,7 @@ describe('sbb-card-button', () => { beforeEach(async () => { element = await fixture( html`CardContent`, + { modules: ['../card/index.ts', './card-button.ts'] }, ); action = document.querySelector('sbb-card-button')!; }); diff --git a/src/components/card/card-link/card-link.e2e.ts b/src/components/card/card-link/card-link.e2e.ts index 533cb1b66a..f51c21335c 100644 --- a/src/components/card/card-link/card-link.e2e.ts +++ b/src/components/card/card-link/card-link.e2e.ts @@ -1,8 +1,14 @@ -import { expect, fixture } from '@open-wc/testing'; +import { expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { EventSpy, waitForCondition, waitForLitRender } from '../../core/testing'; +import { + EventSpy, + waitForCondition, + waitForLitRender, + fixture, + isNonHydratedSsr, +} from '../../core/testing'; import type { SbbCardElement } from '../card'; import type { SbbCardLinkElement } from './card-link'; @@ -10,18 +16,23 @@ import type { SbbCardLinkElement } from './card-link'; import '../card'; import './card-link'; -describe('sbb-card-link', () => { +describe(`sbb-card-link with ${fixture.name}`, () => { let element: SbbCardElement; it('should render an sbb-card-link as a link opening in a new window', async () => { - element = await fixture(html` - - Follow me - Content text - - `); + element = await fixture( + html` + + Follow me + Content text + + `, + { modules: ['../card/index.ts', './card-link.ts'] }, + ); expect(element).to.have.attribute('data-has-action'); expect(element).not.to.have.attribute('data-has-active-action'); @@ -30,7 +41,16 @@ describe('sbb-card-link', () => { const cardAction = element.querySelector('sbb-card-link'); expect(cardAction).dom.to.be.equal(` - + Follow me `); @@ -40,6 +60,7 @@ describe('sbb-card-link', () => { it('should correctly toggle active state', async () => { element = await fixture( html`Click meContent`, + { modules: ['../card/index.ts', './card-link.ts'] }, ); expect(element).not.to.have.attribute('data-has-active-action'); @@ -55,6 +76,7 @@ describe('sbb-card-link', () => { >Click me`, + { modules: ['../card/index.ts', './card-link.ts'] }, ); expect(element).to.have.attribute('data-has-action'); @@ -78,6 +100,7 @@ describe('sbb-card-link', () => { `, + { modules: ['../card/index.ts', './card-link.ts'] }, ); expect(document.querySelector('button')).to.have.attribute('data-card-focusable'); @@ -108,6 +131,7 @@ describe('sbb-card-link', () => { Click me `, + { modules: ['../card/index.ts', './card-link.ts'] }, ); // Add a button to slot @@ -127,6 +151,7 @@ describe('sbb-card-link', () => { `, + { modules: ['../card/index.ts'] }, ); // Add a sbb-card-link @@ -143,12 +168,15 @@ describe('sbb-card-link', () => { let action: SbbCardLinkElement; beforeEach(async () => { - element = await fixture(html` - - Card - Content - - `); + element = await fixture( + html` + + Card + Content + + `, + { modules: ['../card/index.ts', './card-link.ts'] }, + ); action = document.querySelector('sbb-card-link')!; }); diff --git a/src/components/card/card/card.e2e.ts b/src/components/card/card/card.e2e.ts index c663b82c63..454df38105 100644 --- a/src/components/card/card/card.e2e.ts +++ b/src/components/card/card/card.e2e.ts @@ -1,29 +1,37 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; +import { fixture } from '../../core/testing'; + import { SbbCardElement } from './card'; + import '../card-badge'; -describe('sbb-card', () => { +describe(`sbb-card with ${fixture.name}`, () => { let element: SbbCardElement; it('renders', async () => { - element = await fixture(html``); + element = await fixture(html``, { + modules: ['./card.ts'], + }); assert.instanceOf(element, SbbCardElement); }); it('should render with sbb-card-badge', async () => { - element = await fixture(html` - -

Title

- Content text - - % - from CHF - 19.99 - -
- `); + element = await fixture( + html` + +

Title

+ Content text + + % + from CHF + 19.99 + +
+ `, + { modules: ['./card.ts', '../card-badge/index.ts'] }, + ); expect( getComputedStyle( @@ -39,6 +47,7 @@ describe('sbb-card', () => {

Title

Content text `, + { modules: ['./card.ts'] }, ); expect( @@ -60,6 +69,7 @@ describe('sbb-card', () => { 19.99 `, + { modules: ['./card.ts', '../card-badge/index.ts'] }, ); expect(root.shadowRoot!.querySelector('.sbb-card__badge-wrapper')).not.to.be.ok; diff --git a/src/components/checkbox/checkbox-group/checkbox-group.e2e.ts b/src/components/checkbox/checkbox-group/checkbox-group.e2e.ts index deab9b1066..db8ea068ee 100644 --- a/src/components/checkbox/checkbox-group/checkbox-group.e2e.ts +++ b/src/components/checkbox/checkbox-group/checkbox-group.e2e.ts @@ -1,26 +1,29 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; -import { waitForLitRender } from '../../core/testing'; +import { waitForLitRender, fixture } from '../../core/testing'; import { SbbCheckboxElement } from '../checkbox'; import { SbbCheckboxGroupElement } from './checkbox-group'; -describe('sbb-checkbox-group', () => { +describe(`sbb-checkbox-group with ${fixture.name}`, () => { let element: SbbCheckboxGroupElement; let checkboxOne: SbbCheckboxElement, checkboxTwo: SbbCheckboxElement, checkboxThree: SbbCheckboxElement; beforeEach(async () => { - element = await fixture(html` - - Label 1 - Label 2 - Label 3 - - `); + element = await fixture( + html` + + Label 1 + Label 2 + Label 3 + + `, + { modules: ['./checkbox-group.ts', '../checkbox/index.ts'] }, + ); checkboxOne = document.querySelector('#checkbox-1')!; checkboxTwo = document.querySelector('#checkbox-2')!; checkboxThree = document.querySelector('#checkbox-3')!; diff --git a/src/components/checkbox/checkbox/checkbox.e2e.ts b/src/components/checkbox/checkbox/checkbox.e2e.ts index 2c44dc0369..8872255611 100644 --- a/src/components/checkbox/checkbox/checkbox.e2e.ts +++ b/src/components/checkbox/checkbox/checkbox.e2e.ts @@ -1,9 +1,9 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { a11ySnapshot, sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; import { isChromium, isFirefox } from '../../core/dom'; -import { EventSpy, waitForCondition, waitForLitRender } from '../../core/testing'; +import { EventSpy, fixture, waitForCondition, waitForLitRender } from '../../core/testing'; import type { SbbVisualCheckboxElement } from '../../visual-checkbox'; import { SbbCheckboxElement } from './checkbox'; @@ -15,12 +15,14 @@ interface CheckboxAccessibilitySnapshot { required: boolean; } -describe('sbb-checkbox', () => { +describe(`sbb-checkbox with ${fixture.name}`, () => { describe('general', () => { let element: SbbCheckboxElement; beforeEach(async () => { - element = await fixture(html`Label`); + element = await fixture(html`Label`, { + modules: ['./checkbox.ts'], + }); }); it('should render', async () => { @@ -66,6 +68,7 @@ describe('sbb-checkbox', () => { `, + { modules: ['./checkbox.ts'] }, ); element = root.querySelector('sbb-checkbox')!; @@ -233,6 +236,7 @@ describe('sbb-checkbox', () => { `, + { modules: ['./checkbox.ts'] }, ); await waitForLitRender(form); @@ -713,6 +717,7 @@ describe('sbb-checkbox', () => { `, + { modules: ['./checkbox.ts'] }, ); await waitForLitRender(form); diff --git a/src/components/chip/chip.e2e.ts b/src/components/chip/chip.e2e.ts index 1ae4fc84db..0c25209ccb 100644 --- a/src/components/chip/chip.e2e.ts +++ b/src/components/chip/chip.e2e.ts @@ -1,11 +1,15 @@ -import { assert, fixture } from '@open-wc/testing'; +import { assert } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; +import { fixture } from '../core/testing'; + import { SbbChipElement } from './chip'; -describe('sbb-chip', () => { +describe(`sbb-chip with ${fixture.name}`, () => { it('renders', async () => { - const element: SbbChipElement = await fixture(html`Label`); + const element: SbbChipElement = await fixture(html`Label`, { + modules: ['./chip.ts'], + }); assert.instanceOf(element, SbbChipElement); }); }); diff --git a/src/components/container/container/container.e2e.ts b/src/components/container/container/container.e2e.ts index b7f3e92ed2..03c28be43d 100644 --- a/src/components/container/container/container.e2e.ts +++ b/src/components/container/container/container.e2e.ts @@ -1,13 +1,15 @@ -import { assert, fixture } from '@open-wc/testing'; +import { assert } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; +import { fixture } from '../../core/testing'; + import { SbbContainerElement } from './container'; -describe('sbb-container', () => { +describe(`sbb-container with ${fixture.name}`, () => { let element: SbbContainerElement; beforeEach(async () => { - element = await fixture(html``); + element = await fixture(html``, { modules: ['./container.ts'] }); }); it('renders', async () => { diff --git a/src/components/container/sticky-bar/sticky-bar.e2e.ts b/src/components/container/sticky-bar/sticky-bar.e2e.ts index 8e2f2cdcd3..01dcf532ed 100644 --- a/src/components/container/sticky-bar/sticky-bar.e2e.ts +++ b/src/components/container/sticky-bar/sticky-bar.e2e.ts @@ -1,15 +1,15 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { setViewport } from '@web/test-runner-commands'; -import { html } from 'lit/static-html.js'; +import { html } from 'lit'; -import { waitForCondition, waitForLitRender } from '../../core/testing'; +import { waitForCondition, waitForLitRender, fixture } from '../../core/testing'; import type { SbbContainerElement } from '../container'; import { SbbStickyBarElement } from './sticky-bar'; import '../container'; -describe('sbb-sticky-bar', () => { +describe(`sbb-sticky-bar with ${fixture.name}`, () => { let container: SbbContainerElement; let stickyBar: SbbStickyBarElement; const getIsSticking = (): boolean => { @@ -18,17 +18,26 @@ describe('sbb-sticky-bar', () => { beforeEach(async () => { await setViewport({ width: 320, height: 500 }); - container = await fixture(html` - - ${[...Array(15).keys()].map( - (value) => - html`
-

Situation ${value}

-
`, - )} - -
- `); + container = await fixture( + html` + +

Situation 1

+

Situation 2

+

Situation 3

+

Situation 4

+

Situation 5

+

Situation 6

+

Situation 7

+

Situation 8

+

Situation 9

+

Situation 10

+

Situation 11

+

Situation 12

+ +
+ `, + { modules: ['../container/index.ts', './sticky-bar.ts'] }, + ); stickyBar = container.querySelector('sbb-sticky-bar')!; }); @@ -49,14 +58,17 @@ describe('sbb-sticky-bar', () => { it('is settled when content is not long enough', async () => { await setViewport({ width: 320, height: 600 }); - container = await fixture(html` - - - - - - - `); + container = await fixture( + html` + + + + + + + `, + { modules: ['../container/index.ts', './sticky-bar.ts'] }, + ); stickyBar = container.querySelector('sbb-sticky-bar')!; await waitForCondition(async () => !getIsSticking()); @@ -65,14 +77,17 @@ describe('sbb-sticky-bar', () => { }); it('renders with expanded layout', async () => { - container = await fixture(html` - - - - - - - `); + container = await fixture( + html` + + + + + + + `, + { modules: ['../container/index.ts', './sticky-bar.ts'] }, + ); stickyBar = container.querySelector('sbb-sticky-bar')!; expect(stickyBar).to.have.attribute('data-expanded'); diff --git a/src/components/core/common-behaviors/action-base-element.e2e.ts b/src/components/core/common-behaviors/action-base-element.e2e.ts index 76f2c82cf0..bf9817b557 100644 --- a/src/components/core/common-behaviors/action-base-element.e2e.ts +++ b/src/components/core/common-behaviors/action-base-element.e2e.ts @@ -1,6 +1,8 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { html, type TemplateResult } from 'lit'; +import { fixture } from '../testing'; + import { SbbActionBaseElement } from './action-base-element'; class GenericAction extends SbbActionBaseElement { @@ -10,12 +12,12 @@ class GenericAction extends SbbActionBaseElement { } customElements.define('generic-action', GenericAction); -describe('SbbActionBaseElement', () => { +describe(`SbbActionBaseElement with ${fixture.name}`, () => { describe('template', () => { let element: GenericAction; beforeEach(async () => { - element = await fixture(html``); + element = await fixture(html``, { modules: [] }); }); it('renders', async () => { diff --git a/src/components/core/common-behaviors/button-base-element.e2e.ts b/src/components/core/common-behaviors/button-base-element.e2e.ts index a9dd6c7ef0..0bd56744f1 100644 --- a/src/components/core/common-behaviors/button-base-element.e2e.ts +++ b/src/components/core/common-behaviors/button-base-element.e2e.ts @@ -1,8 +1,8 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html, type TemplateResult } from 'lit'; -import { EventSpy, waitForLitRender } from '../testing'; +import { EventSpy, waitForLitRender, fixture } from '../testing'; import { SbbButtonBaseElement } from './button-base-element'; @@ -15,12 +15,12 @@ class GenericButton extends SbbButtonBaseElement { } customElements.define('generic-button', GenericButton); -describe('SbbButtonBaseElement', () => { +describe(`SbbButtonBaseElement with ${fixture.name}`, () => { describe('template', () => { let element: GenericButton; beforeEach(async () => { - element = await fixture(html``); + element = await fixture(html``, { modules: [] }); }); it('renders', async () => { @@ -41,7 +41,7 @@ describe('SbbButtonBaseElement', () => { let element: GenericButton; beforeEach(async () => { - element = await fixture(html` `); + element = await fixture(html` `, { modules: [] }); }); it('no click dispatch if disabled', async () => { diff --git a/src/components/core/common-behaviors/hydration-mixin.ts b/src/components/core/common-behaviors/hydration-mixin.ts index 9c2711a625..a19877c72e 100644 --- a/src/components/core/common-behaviors/hydration-mixin.ts +++ b/src/components/core/common-behaviors/hydration-mixin.ts @@ -2,7 +2,7 @@ import { isServer, type LitElement, type PropertyValues } from 'lit'; import { forwardEventToHost } from '../eventing'; -import type { Constructor } from './constructor'; +import type { AbstractConstructor } from './constructor'; // Define the interface for the mixin export declare abstract class SbbHydrationMixinType { @@ -52,10 +52,10 @@ const hydrationSuppressed = * @returns A class extended with the hydration check functionality. */ // eslint-disable-next-line @typescript-eslint/naming-convention -export const SbbHydrationMixin = >( +export const SbbHydrationMixin = >( base: T, -): Constructor & T => { - class SbbHydrationMixinClass extends base implements Partial { +): AbstractConstructor & T => { + abstract class SbbHydrationMixinClass extends base implements Partial { private _hydrationRequired = false; private _hydrationComplete = new Promise( (resolve) => (this._resolveHydration = resolve), @@ -141,5 +141,5 @@ export const SbbHydrationMixin = >( return value; } } - return SbbHydrationMixinClass as unknown as Constructor & T; + return SbbHydrationMixinClass as unknown as AbstractConstructor & T; }; diff --git a/src/components/core/common-behaviors/index.ts b/src/components/core/common-behaviors/index.ts index 991f26d7a2..0dfcb87e85 100644 --- a/src/components/core/common-behaviors/index.ts +++ b/src/components/core/common-behaviors/index.ts @@ -10,8 +10,7 @@ export * from './icon-name-mixin'; export * from './language-controller'; export * from './link-base-element'; export * from './named-slot-state-controller'; -export * from './named-slot-list-element'; +export * from './named-slot-list-mixin'; export * from './negative-mixin'; export * from './required-mixin'; -export * from './slot-child-observer'; export * from './update-scheduler'; diff --git a/src/components/core/common-behaviors/link-base-element.e2e.ts b/src/components/core/common-behaviors/link-base-element.e2e.ts index d9a8fda948..d4d5cf8799 100644 --- a/src/components/core/common-behaviors/link-base-element.e2e.ts +++ b/src/components/core/common-behaviors/link-base-element.e2e.ts @@ -1,8 +1,8 @@ -import { assert, expect, fixture } from '@open-wc/testing'; +import { assert, expect } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import { html, type TemplateResult } from 'lit'; -import { EventSpy, waitForLitRender } from '../testing'; +import { EventSpy, waitForLitRender, fixture } from '../testing'; import { SbbLinkBaseElement } from './link-base-element'; @@ -15,12 +15,12 @@ class GenericLink extends SbbLinkBaseElement { } customElements.define('generic-link', GenericLink); -describe('SbbLinkBaseElement', () => { +describe(`SbbLinkBaseElement with ${fixture.name}`, () => { describe('template', () => { let element: GenericLink; beforeEach(async () => { - element = await fixture(html``); + element = await fixture(html``, { modules: [] }); }); it('renders', async () => { @@ -40,7 +40,7 @@ describe('SbbLinkBaseElement', () => { let element: GenericLink; beforeEach(async () => { - element = await fixture(html` `); + element = await fixture(html` `, { modules: [] }); }); it('no click dispatch if disabled', async () => { diff --git a/src/components/core/common-behaviors/link-base-element.ts b/src/components/core/common-behaviors/link-base-element.ts index caf23beb3a..87543b868e 100644 --- a/src/components/core/common-behaviors/link-base-element.ts +++ b/src/components/core/common-behaviors/link-base-element.ts @@ -1,6 +1,7 @@ import { html, isServer, nothing, type TemplateResult } from 'lit'; import { property } from 'lit/decorators.js'; +import { getLocalName } from '../dom'; import { isEventPrevented } from '../eventing'; import { i18nTargetOpensInNewWindow } from '../i18n'; @@ -84,7 +85,7 @@ export abstract class SbbLinkBaseElement extends SbbActionBaseElement { protected override render(): TemplateResult { return html` , + T extends SbbNamedSlotListMixinType, C extends HTMLElement = HTMLElement, > = T & { listChildren: C[] }; -export declare abstract class NamedSlotListElementMixinType { +export declare abstract class SbbNamedSlotListMixinType< + C extends HTMLElement, +> extends SbbHydrationMixinType { protected abstract readonly listChildTagNames: string[]; @state() protected listChildren: C[]; - protected checkChildren(): void; protected renderList(attributes?: { class?: string; ariaLabel?: string; @@ -38,20 +42,20 @@ export declare abstract class NamedSlotListElementMixinType, >( superClass: T, -): AbstractConstructor> & T => { +): AbstractConstructor> & T => { /** * This base class provides named slot list observer functionality. * This allows using the pattern of rendering a named slot for each child, which allows * wrapping children in a ul/li list. */ abstract class NamedSlotListElement - extends SlotChildObserver(superClass) - implements Partial> + extends SbbHydrationMixin(superClass) + implements Partial> { /** A list of upper-cased tag names to match against. (e.g. SBB-LINK) */ protected abstract readonly listChildTagNames: string[]; @@ -63,7 +67,17 @@ export const SbbNamedSlotListElementMixin = < */ @state() protected listChildren: C[] = []; - protected override checkChildren(): void { + public override connectedCallback(): void { + super.connectedCallback(); + this.shadowRoot?.addEventListener('slotchange', this._handleSlotchange, { passive: true }); + } + + public override disconnectedCallback(): void { + super.disconnectedCallback(); + this.shadowRoot?.removeEventListener('slotchange', this._handleSlotchange); + } + + private _handleSlotchange = (): void => { const listChildren = Array.from(this.children ?? []).filter((e): e is C => this.listChildTagNames.includes(e.tagName), ); @@ -86,7 +100,7 @@ export const SbbNamedSlotListElementMixin = < // Remove the ssr attribute, once we have actually initialized the children elements. this.removeAttribute(SSR_CHILD_COUNT_ATTRIBUTE); - } + }; /** * Renders list and list slots for slotted children or an amount of list slots @@ -105,7 +119,7 @@ export const SbbNamedSlotListElementMixin = < if (listSlotNames.length >= 2) { return html`