Skip to content

Commit

Permalink
Merge branch 'develop' into 7077-clean-console
Browse files Browse the repository at this point in the history
  • Loading branch information
sdvg authored Dec 19, 2024
2 parents 099fe49 + 5b7b37b commit bae3890
Show file tree
Hide file tree
Showing 111 changed files with 1,918 additions and 709 deletions.
12 changes: 12 additions & 0 deletions docs/BREAKING_CHANGES.v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ The following components have been removed:
- Visually, the tooltip has been replaced by a simple label shown in parentheses after the abbreviation.
- The property `_tooltipAlign` has been removed.

### kol-input-file

- The property `_value` has been removed as it never served a purpose. Use the `getValue()` method instead to access the FileList.

### kol-table-stateful

- The DOM event `kol-selection-change` has been renamed to `kolSelectionChange`.

### kol-table-stateless

- The DOM event `kol-selection-change` has been renamed to `kolSelectionChange`.

## `focus`-methods

The public `focus`-methods have been removed from all components. Use `kolFocus` instead.
Expand Down
1 change: 1 addition & 0 deletions packages/components/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/dist/
/dist-e2e/
/doc/**
/loader/
/public/
Expand Down
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"test": "pnpm test:unit",
"test:unit": "cross-env NODE_ENV=test stencil test --spec --json --outputFile dist/jest-test-results.json",
"test:watch": "cross-env NODE_ENV=test stencil test --spec --watchAll",
"test:e2e": "playwright test",
"test:e2e": "cross-env E2E=1 playwright test",
"postinstall": "pnpm exec playwright install",
"postpack": "mv package.bak.json package.json",
"prepack": "npm run build && cp package.json package.bak.json && rimraf dist/collection dist/kolibri/assets/@leanup dist/types/assets/@leanup && node scripts/anonymous.js && node scripts/minify.js",
Expand Down
8 changes: 7 additions & 1 deletion packages/components/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { createConfig, matchers } from '@stencil/playwright';

expect.extend(matchers);

const TEST_URL = 'http://localhost:3333';
const TEST_PORT = '3333';
const TEST_URL = `http://localhost:${TEST_PORT}`;

/* See https://playwright.dev/docs/test-configuration */
export default createConfig({
Expand All @@ -30,8 +31,13 @@ export default createConfig({
use: {
baseURL: TEST_URL,
timezoneId: 'Europe/Berlin',
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
},
webServer: {
url: TEST_URL,
reuseExistingServer: false,
/* The builtin Stencil server sometimes fails to serve some assets which leads to intermittent test failures. Use a more stable server (without watcher) for CI: */
...(process.env.CI ? { command: `stencil build --dev && mv dist-e2e/kolibri dist-e2e/build && npx serve dist-e2e -p ${TEST_PORT} -L` } : {}),
},
});
32 changes: 19 additions & 13 deletions packages/components/src/components/@deprecated/input/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import {
validateHideLabel,
validateLabelWithExpertSlot,
validateMsg,
validateShortKey,
validateTabIndex,
validateTooltipAlign,
watchBoolean,
watchString,
validateShortKey,
} from '../../../schema';

import { stopPropagation, tryToDispatchKoliBriEvent } from '../../../utils/events';
import { dispatchDomEvent, KolEvent } from '../../../utils/events';
import { ControlledInputController } from '../../input-adapter-leanup/controller';

import type { Props as AdapterProps } from '../../input-adapter-leanup/types';
Expand Down Expand Up @@ -161,12 +161,17 @@ export class InputController extends ControlledInputController implements Watche
validateAccessAndShortKey(this.component._accessKey, this.component._shortKey);
}

private emitEvent(type: KolEvent, value?: unknown): void {
if (this.host) {
dispatchDomEvent(this.host, type, value);
}
}

protected onBlur(event: Event): void {
this.component._touched = true;

// Event handling
stopPropagation(event);
tryToDispatchKoliBriEvent('blur', this.host);
this.emitEvent(KolEvent.blur);

// Callback
if (typeof this.component._on?.onBlur === 'function') {
Expand All @@ -179,10 +184,12 @@ export class InputController extends ControlledInputController implements Watche
* @param value - Optional value. Taken from event if not defined.
*/
protected onChange(event: Event, value?: StencilUnknown): void {
value = value ?? (event.target as HTMLInputElement).value;
if (typeof value === 'undefined') {
value = (event.target as HTMLInputElement).value;
}

// Event handling
tryToDispatchKoliBriEvent('change', this.host, value);
this.emitEvent(KolEvent.change, value);

// Callback
if (typeof this.component._on?.onChange === 'function') {
Expand All @@ -208,11 +215,12 @@ export class InputController extends ControlledInputController implements Watche
* @param value - Optional value. Taken from event if not defined.
*/
protected onInput(event: Event, shouldSetFormAssociatedValue = true, value?: StencilUnknown): void {
value = value ?? (event.target as HTMLInputElement).value;
if (typeof value === 'undefined') {
value = (event.target as HTMLInputElement).value;
}

// Event handling
stopPropagation(event);
tryToDispatchKoliBriEvent('input', this.host, value);
this.emitEvent(KolEvent.input, value);

// Static form handling
if (shouldSetFormAssociatedValue) {
Expand All @@ -227,8 +235,7 @@ export class InputController extends ControlledInputController implements Watche

protected onClick(event: Event): void {
// Event handling
stopPropagation(event);
tryToDispatchKoliBriEvent('click', this.host);
this.emitEvent(KolEvent.click);

// Callback
if (typeof this.component._on?.onClick === 'function') {
Expand All @@ -238,8 +245,7 @@ export class InputController extends ControlledInputController implements Watche

protected onFocus(event: Event): void {
// Event handling
stopPropagation(event);
tryToDispatchKoliBriEvent('focus', this.host);
this.emitEvent(KolEvent.focus);

// Callback
if (typeof this.component._on?.onFocus === 'function') {
Expand Down
27 changes: 27 additions & 0 deletions packages/components/src/components/accordion/accordion.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';
import { KolEvent } from '../../utils/events';

test.describe('kol-accordion', () => {
test.describe('when accordion is enabled', () => {
Expand All @@ -24,6 +25,32 @@ test.describe('kol-accordion', () => {
await page.getByRole('button', { name: 'Accordion label' }).click();
await expect(page.locator('.collapsible__content')).toHaveAttribute('aria-hidden', 'true');
});

test('should emit "click" event when the title is clicked', async ({ page }) => {
const eventPromise = page.locator('kol-accordion').evaluate(async (element: HTMLKolAccordionElement, KolEvent) => {
return new Promise((resolve) => {
element.addEventListener(KolEvent.click, resolve);
});
}, KolEvent);
await page.waitForChanges();
await page.getByRole('button', { name: 'Accordion label' }).click();
await expect(eventPromise).resolves.toBeTruthy();
});

test('should call "onClick" callback when the title is clicked', async ({ page }) => {
const callbackPromise = page.locator('kol-accordion').evaluate(async (element: HTMLKolAccordionElement) => {
return new Promise((resolve) => {
element._on = {
onClick: (_event: MouseEvent, value?: boolean) => {
resolve(value);
},
};
});
});
await page.waitForChanges();
await page.getByRole('button', { name: 'Accordion label' }).click();
await expect(callbackPromise).resolves.toBe(true);
});
});

test.describe('when accordion is disabled', () => {
Expand Down
8 changes: 7 additions & 1 deletion packages/components/src/components/accordion/shadow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// https://codepen.io/mbxtr/pen/OJPOYg?html-preprocessor=haml
import { Component, h, Method, Prop, State, Watch } from '@stencil/core';
import { Component, Element, h, Method, Prop, State, Watch } from '@stencil/core';
import type { JSX } from '@stencil/core';
import type {
AccordionAPI,
Expand All @@ -15,6 +15,7 @@ import { featureHint, validateAccordionCallbacks, validateDisabled, validateLabe
import { nonce } from '../../utils/dev.utils';
import { watchHeadingLevel } from '../heading/validation';
import KolCollapsibleFc, { type CollapsibleProps } from '../../functional-components/Collapsible';
import { dispatchDomEvent, KolEvent } from '../../utils/events';

featureHint(`[KolAccordion] Anfrage nach einer KolAccordionGroup bei dem immer nur ein Accordion geöffnet ist.
Expand All @@ -35,6 +36,8 @@ featureHint(`[KolAccordion] Tab-Sperre des Inhalts im geschlossenen Zustand.`);
shadow: true,
})
export class KolAccordion implements AccordionAPI, FocusableElement {
@Element() private readonly host?: HTMLKolAccordionElement;

private readonly nonce = nonce();
private buttonWcRef?: HTMLKolButtonWcElement;

Expand All @@ -58,6 +61,9 @@ export class KolAccordion implements AccordionAPI, FocusableElement {
*/
setTimeout(() => {
this.state._on?.onClick?.(event, this._open === true);
if (this.host) {
dispatchDomEvent(this.host, KolEvent.click, this._open === true);
}
});
};

Expand Down
37 changes: 37 additions & 0 deletions packages/components/src/components/alert/alert.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';
import { KolEvent } from '../../utils/events';

test.describe('kol-alert', () => {
test.describe('Callbacks', () => {
test('should call "onClose" callback when the close button is clicked', async ({ page }) => {
await page.setContent('<kol-alert _label="Alert" _has-closer />');
const callbackPromise = page.locator('kol-alert').evaluate(async (element: HTMLKolAlertElement) => {
return new Promise((resolve) => {
element._on = {
onClose: (_event: Event, value?: unknown) => {
resolve(value);
},
};
});
});
await page.waitForChanges();
await page.getByTestId('alert-close-button').click();
await expect(callbackPromise).resolves.toBeUndefined();
});
});

test.describe('DOM events', () => {
test('should emit "close" when close button is clicked', async ({ page }) => {
await page.setContent('<kol-alert _label="Alert" _has-closer />');
const eventPromise = page.locator('kol-alert').evaluate(async (element: HTMLKolAlertElement, KolEvent) => {
return new Promise((resolve) => {
element.addEventListener(KolEvent.close, resolve);
});
}, KolEvent);
await page.waitForChanges();
await page.getByTestId('alert-close-button').click();
await expect(eventPromise).resolves.toBeTruthy();
});
});
});
8 changes: 7 additions & 1 deletion packages/components/src/components/alert/component.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { JSX } from '@stencil/core';
import { alertTypeOptions, alertVariantOptions, setState, validateHasCloser, validateLabel, watchBoolean, watchValidator } from '../../schema';
import { Component, h, Prop, State, Watch } from '@stencil/core';
import { Component, Element, h, Prop, State, Watch } from '@stencil/core';
import { watchHeadingLevel } from '../heading/validation';
import type { AlertAPI, AlertStates, AlertType, AlertVariant, HasCloserPropType, HeadingLevel, KoliBriAlertEventCallbacks, LabelPropType } from '../../schema';
import KolAlertFc, { type KolAlertFcProps } from '../../functional-components/Alert';
import { dispatchDomEvent, KolEvent } from '../../utils/events';

/**
* @internal
Expand All @@ -14,8 +15,13 @@ import KolAlertFc, { type KolAlertFcProps } from '../../functional-components/Al
shadow: false,
})
export class KolAlertWc implements AlertAPI {
@Element() private readonly host?: HTMLKolAlertWcElement;

private readonly close = () => {
this._on?.onClose?.(new Event('Close'));
if (this.host) {
dispatchDomEvent(this.host, KolEvent.close);
}
};

private readonly handleAlertTimeout = () => {
Expand Down
48 changes: 48 additions & 0 deletions packages/components/src/components/badge/badge.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';
import { KolEvent } from '../../utils/events';

test.describe('kol-badge', () => {
test.describe('Callbacks', () => {
['onClick', 'onMouseDown'].forEach((callbackName) => {
test(`should call ${callbackName} callback when smart button emits`, async ({ page }) => {
await page.setContent(`<kol-badge _label="Badge with Button"></kol-badge>`);
const kolBadge = page.locator('kol-badge');

const callbackPromise = kolBadge.evaluate((element: HTMLKolBadgeElement, callbackName) => {
return new Promise<void>((resolve) => {
element._smartButton = {
_label: `Smart Button`,
_on: {
[callbackName]: () => {
resolve();
},
},
};
});
}, callbackName);
await page.waitForChanges();

await page.locator('button').click();
await expect(callbackPromise).resolves.toBeUndefined();
});
});
});

test.describe('DOM events', () => {
[KolEvent.click, KolEvent.mousedown].forEach((event) => {
test(`should emit ${event} when smart button emits ${event}`, async ({ page }) => {
const BADGE_PROPS = { _label: `Smart Button` };
await page.setContent(`<kol-badge _label="Badge with Button" _smart-button='${JSON.stringify(BADGE_PROPS)}'></kol-badge>`);
const eventPromise = page.locator('kol-badge').evaluate(async (element, event) => {
return new Promise((resolve) => {
element.addEventListener(event, resolve);
});
}, event);
await page.waitForChanges();
await page.locator('button').dispatchEvent(event);
await expect(eventPromise).resolves.toBeTruthy();
});
});
});
});
50 changes: 50 additions & 0 deletions packages/components/src/components/button-link/button-link.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';
import { KolEvent } from '../../utils/events';

test.describe('kol-button-link', () => {
test('it renders label', async ({ page }) => {
await page.setContent('<kol-button-link _label="Test ButtonLink Element" _variant="primary"></kol-button-link>');
const kolButton = page.locator('kol-button-link');
await expect(kolButton).toContainText('Test ButtonLink Element');
});

test.describe('Callbacks', () => {
['onClick', 'onMouseDown'].forEach((callbackName) => {
test(`should call ${callbackName} callback when internal button emits`, async ({ page }) => {
await page.setContent('<kol-button-link _label="Button"></kol-button-link>');
const kolButton = page.locator('kol-button-link');

const callbackPromise = kolButton.evaluate((element: HTMLKolButtonElement, callbackName) => {
return new Promise<void>((resolve) => {
element._on = {
[callbackName]: () => {
resolve();
},
};
});
}, callbackName);
await page.waitForChanges();

await page.locator('button').click();
await expect(callbackPromise).resolves.toBeUndefined();
});
});
});

test.describe('DOM events', () => {
[KolEvent.click, KolEvent.mousedown].forEach((event) => {
test(`should emit ${event} when internal button emits ${event}`, async ({ page }) => {
await page.setContent('<kol-button-link _label="Button"></kol-button-link>');
const eventPromise = page.locator('kol-button-link').evaluate(async (element, event) => {
return new Promise((resolve) => {
element.addEventListener(event, resolve);
});
}, event);
await page.waitForChanges();
await page.locator('button').dispatchEvent(event);
await expect(eventPromise).resolves.toBeTruthy();
});
});
});
});
Loading

0 comments on commit bae3890

Please sign in to comment.