diff --git a/src/components/sbb-button/index.ts b/src/components/sbb-button/index.ts
new file mode 100644
index 0000000000..009da7feab
--- /dev/null
+++ b/src/components/sbb-button/index.ts
@@ -0,0 +1 @@
+export * from './sbb-button';
diff --git a/src/components/sbb-button/sbb-button.e2e.ts b/src/components/sbb-button/sbb-button.e2e.ts
index d625379a9b..c229883ea3 100644
--- a/src/components/sbb-button/sbb-button.e2e.ts
+++ b/src/components/sbb-button/sbb-button.e2e.ts
@@ -1,97 +1,103 @@
-import { E2EElement, E2EPage, newE2EPage } from '@stencil/core/testing';
import { waitForCondition } from '../../global/testing';
+import { assert, expect, fixture } from '@open-wc/testing';
+import { html } from 'lit/static-html.js';
+import { sendKeys } from '@web/test-runner-commands';
+import { EventSpy } from '../../global/testing/event-spy';
+import { SbbButton } from './sbb-button';
describe('sbb-button', () => {
- let element: E2EElement, page: E2EPage;
+ let element: SbbButton;
beforeEach(async () => {
- page = await newE2EPage();
- await page.setContent('I am a button');
- element = await page.find('sbb-button');
+ element = await fixture(html`I am a button`);
});
it('renders', async () => {
- expect(element).toHaveClass('hydrated');
+ assert.instanceOf(element, SbbButton);
});
describe('events', () => {
it('dispatches event on click', async () => {
- await page.waitForChanges();
- const clickSpy = await page.spyOnEvent('click');
+ await element.updateComplete;
+ const clickSpy = new EventSpy('click');
await element.click();
await waitForCondition(() => clickSpy.events.length === 1);
- expect(clickSpy).toHaveReceivedEventTimes(1);
+ expect(clickSpy.count).to.be.equal(1);
});
it('should not dispatch event on click if disabled', async () => {
- element.setAttribute('disabled', true);
+ element.setAttribute('disabled', 'true');
- await page.waitForChanges();
+ await element.updateComplete;
- const clickSpy = await page.spyOnEvent('click');
+ const clickSpy = new EventSpy('click');
- await element.click();
- expect(clickSpy).not.toHaveReceivedEvent();
+ element.click();
+ expect(clickSpy.count).not.to.be.greaterThan(0);
});
it('should dispatch event on click if is-static', async () => {
- element.setAttribute('is-static', true);
+ element.setAttribute('is-static', 'true');
- await page.waitForChanges();
+ await element.updateComplete;
- const clickSpy = await page.spyOnEvent('click');
+ const clickSpy = new EventSpy('click');
await element.click();
- expect(clickSpy).toHaveReceivedEvent();
+ expect(clickSpy.count).to.be.greaterThan(0);
});
it('should dispatch click event on pressing Enter', async () => {
- const clickSpy = await page.spyOnEvent('click');
- await element.press('Enter');
- expect(clickSpy).toHaveReceivedEvent();
+ const clickSpy = new EventSpy('click');
+ element.focus();
+ await sendKeys({ press: 'Enter' });
+ expect(clickSpy.count).to.be.greaterThan(0);
});
it('should dispatch click event on pressing Space', async () => {
- const clickSpy = await page.spyOnEvent('click');
- await element.press(' ');
- expect(clickSpy).toHaveReceivedEvent();
+ const clickSpy = new EventSpy('click');
+ element.focus();
+ await sendKeys({ press: ' ' });
+ expect(clickSpy.count).to.be.greaterThan(0);
});
it('should dispatch click event on pressing Enter with href', async () => {
- element.setAttribute('href', 'test');
- await page.waitForChanges();
+ element.setAttribute('href', '#');
+ await element.updateComplete;
- const clickSpy = await page.spyOnEvent('click');
- await element.press('Enter');
- expect(clickSpy).toHaveReceivedEvent();
+ const clickSpy = new EventSpy('click');
+ element.focus();
+ await sendKeys({ press: 'Enter' });
+ expect(clickSpy.count).to.be.greaterThan(0);
});
it('should not dispatch click event on pressing Space with href', async () => {
- element.setAttribute('href', 'test');
- await page.waitForChanges();
+ element.setAttribute('href', '#');
+ await element.updateComplete;
- const clickSpy = await page.spyOnEvent('click');
- await element.press(' ');
- expect(clickSpy).not.toHaveReceivedEvent();
+ const clickSpy = new EventSpy('click');
+ element.focus();
+ await sendKeys({ press: ' ' });
+ expect(clickSpy.count).not.to.be.greaterThan(0);
});
it('should stop propagating host click if disabled', async () => {
- element.setProperty('disabled', true);
+ element.disabled = true;
- const clickSpy = await page.spyOnEvent('click');
+ const clickSpy = new EventSpy('click');
- element.triggerEvent('click');
- await page.waitForChanges();
+ element.dispatchEvent(new CustomEvent('click'));
+ await element.updateComplete;
- expect(clickSpy).not.toHaveReceivedEvent();
+ expect(clickSpy.count).not.to.be.greaterThan(0);
});
it('should receive focus', async () => {
- await element.focus();
- await page.waitForChanges();
+ element.focus();
+ await element.updateComplete;
- expect(await page.evaluate(() => document.activeElement.id)).toBe('focus-id');
+ expect(document.activeElement.id).to.be.equal('focus-id');
});
});
});
diff --git a/src/components/sbb-button/sbb-button.spec.ts b/src/components/sbb-button/sbb-button.spec.ts
index e99b8424f2..e88826c15b 100644
--- a/src/components/sbb-button/sbb-button.spec.ts
+++ b/src/components/sbb-button/sbb-button.spec.ts
@@ -1,184 +1,179 @@
-import { SbbButton } from './sbb-button';
-import { newSpecPage } from '@stencil/core/testing';
+import { expect, fixture } from '@open-wc/testing';
+import { html } from 'lit/static-html.js';
+import './sbb-button';
describe('sbb-button', () => {
it('renders a primary button without icon', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: `
-
- Label Text
- `,
- });
-
- expect(root).toEqualHtml(`
-
-
-
-
-
-
- Label Text
-
- `);
+ const root = await fixture(
+ html`
+ Label Text
+ `,
+ );
+
+ expect(root).dom.to.be.equal(`
+
+
+ Label Text
+
+ `);
+ expect(root).shadowDom.to.be.equal(`
+
+
+
+ `);
});
it('renders a primary button with slotted icon', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: `Label Text`,
- });
-
- expect(root).toEqualHtml(`
-
-
-
-
-
-
-
-
-
-
- Label Text
-
- `);
+ const root = await fixture(
+ html`
+
+ Label Text
+ `,
+ );
+
+ expect(root).dom.to.be.equal(`
+
+
+ Label Text
+
+ `);
+ expect(root).shadowDom.to.be.equal(`
+
+
+
+
+
+
+ `);
});
it('renders a button as a link', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: `
-
- Label Text
- `,
- });
-
- expect(root).toEqualHtml(`
-
+ Label Text
+ `,
+ );
+
+ expect(root).dom.to.be.equal(`
+
+
+ Label Text
+
+ `);
+ expect(root).shadowDom.to.be.equal(`
+
-
-
-
-
-
- . Link target opens in new window.
-
-
-
-
- Label Text
-
- `);
+
+
+
+ . Link target opens in new window.
+
+
+
+ `);
});
it('renders a sbb-button inside an anchor as span element', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: `this is a button`,
- });
-
- expect(root).toEqualHtml(`
-
-
-
-
-
-
- this is a button
-
- `);
+ const root = (
+ await fixture(
+ html`this is a button`,
+ )
+ ).querySelector('sbb-button');
+
+ expect(root).dom.to.be.equal(`
+
+ this is a button
+
+ `);
+ expect(root).shadowDom.to.be.equal(`
+
+
+
+ `);
});
it('renders a sbb-button as span element by setting is-static property', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: `this is a static button`,
- });
-
- expect(root).toEqualHtml(`
-
-
-
-
-
-
- this is a static button
-
- `);
+ const root = await fixture(
+ html`this is a static button`,
+ );
+
+ expect(root).dom.to.be.equal(`
+
+ this is a static button
+
+ `);
+ expect(root).shadowDom.to.be.equal(`
+
+
+
+ `);
});
it('should detect icon button', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: ``,
- });
+ const root = await fixture(
+ html``,
+ );
- expect(root).toHaveAttribute('data-icon-only');
+ expect(root).to.have.attribute('data-icon-only');
});
it('should detect icon button when there is space around icon', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: ` `,
- });
+ const root = await fixture(
+ html` `,
+ );
- expect(root).toHaveAttribute('data-icon-only');
+ expect(root).to.have.attribute('data-icon-only');
});
- it('should render form field button variant when inside of a form field', async () => {
- const { root } = await newSpecPage({
- components: [SbbButton],
- html: `
-
+ // TODO-Migr: enable this test after the FormField migration
+ it.skip('should render form field button variant when inside of a form field', async () => {
+ const root = await fixture(
+ html`
-
+
`,
- });
+ );
- expect(root).toHaveAttribute('data-icon-small');
+ expect(root).to.have.attribute('data-icon-small');
});
});
diff --git a/src/components/sbb-button/sbb-button.stories.tsx b/src/components/sbb-button/sbb-button.stories.tsx
index 905c562de8..726ab6241d 100644
--- a/src/components/sbb-button/sbb-button.stories.tsx
+++ b/src/components/sbb-button/sbb-button.stories.tsx
@@ -2,9 +2,10 @@
import { h, JSX } from 'jsx-dom';
import readme from './readme.md';
import { withActions } from '@storybook/addon-actions/decorator';
-import type { Meta, StoryObj, ArgTypes, Args, Decorator } from '@storybook/html';
+import type { Meta, StoryObj, ArgTypes, Args, Decorator } from '@storybook/web-components';
import type { InputType, StoryContext } from '@storybook/types';
import isChromatic from 'chromatic';
+import './sbb-button';
const wrapperStyle = (context: StoryContext): Record => ({
'background-color': context.args.negative ? '#484040' : 'var(--sbb-color-white-default)',
diff --git a/src/components/sbb-button/sbb-button.tsx b/src/components/sbb-button/sbb-button.tsx
index 3a496ec7b0..20be0fa45b 100644
--- a/src/components/sbb-button/sbb-button.tsx
+++ b/src/components/sbb-button/sbb-button.tsx
@@ -1,4 +1,3 @@
-import { Component, ComponentInterface, Element, h, Host, JSX, Prop, State } from '@stencil/core';
import { InterfaceButtonAttributes } from './sbb-button.custom';
import {
ButtonType,
@@ -19,95 +18,100 @@ import {
namedSlotChangeHandlerAspect,
} from '../../global/eventing';
import { ACTION_ELEMENTS, hostContext, toggleDatasetEntry } from '../../global/dom';
+import { LitElement, nothing, TemplateResult } from 'lit';
+import { html, unsafeStatic } from 'lit/static-html.js';
+import { customElement, property, state } from 'lit/decorators.js';
+import { spread } from '@open-wc/lit-helpers';
+import { setAttribute, setAttributes } from '../../global/dom';
+import Style from './sbb-button.scss?lit&inline';
+import '../sbb-icon';
/**
* @slot unnamed - Button Content
* @slot icon - Slot used to display the icon, if one is set
*/
-@Component({
- shadow: true,
- styleUrl: 'sbb-button.scss',
- tag: 'sbb-button',
-})
-export class SbbButton implements ComponentInterface, LinkButtonProperties, IsStaticProperty {
+@customElement('sbb-button')
+export class SbbButton extends LitElement implements LinkButtonProperties, IsStaticProperty {
+ public static override styles = Style;
+
/** Variant of the button, like primary, secondary etc. */
- @Prop({ reflect: true }) public variant: InterfaceButtonAttributes['variant'] = 'primary';
+ @property({ reflect: true }) public variant: InterfaceButtonAttributes['variant'] = 'primary';
/** Negative coloring variant flag. */
- @Prop({ reflect: true }) public negative = false;
+ @property({ reflect: true, type: Boolean }) public negative = false;
/** Size variant, either l or m. */
- @Prop({ reflect: true }) public size?: InterfaceButtonAttributes['size'] = 'l';
+ @property({ reflect: true }) public size?: InterfaceButtonAttributes['size'] = 'l';
/**
* Set this property to true if you want only a visual representation of a
* button, but no interaction (a span instead of a link/button will be rendered).
*/
- @Prop({ mutable: true, reflect: true }) public isStatic = false;
+ @property({ attribute: 'is-static', reflect: true, type: Boolean }) public isStatic = false;
/**
* The icon name we want to use, choose from the small icon variants
* from the ui-icons category from here
* https://icons.app.sbb.ch.
*/
- @Prop() public iconName?: string;
+ @property({ attribute: 'icon-name' }) public iconName?: string;
/** The href value you want to link to (if it is present, button becomes a link). */
- @Prop() public href: string | undefined;
+ @property() public href: string | undefined;
/** Where to display the linked URL. */
- @Prop() public target?: LinkTargetType | string | undefined;
+ @property() public target?: LinkTargetType | string | undefined;
/** The relationship of the linked URL as space-separated link types. */
- @Prop() public rel?: string | undefined;
+ @property() public rel?: string | undefined;
/** Whether the browser will show the download dialog on click. */
- @Prop() public download?: boolean;
+ @property({ type: Boolean }) public download?: boolean;
/** The type attribute to use for the button. */
- @Prop() public type: ButtonType | undefined;
+ @property() public type: ButtonType | undefined;
/** Whether the button is disabled. */
- @Prop({ reflect: true }) public disabled = false;
+ @property({ reflect: true, type: Boolean }) public disabled = false;
/** The name attribute to use for the button. */
- @Prop({ reflect: true }) public name: string | undefined;
+ @property({ reflect: true }) public name: string | undefined;
/** The value attribute to use for the button. */
- @Prop() public value?: string;
+ @property() public value?: string;
/** The