import type { Locator } from '@playwright/test';
import { expect, type Page } from '@playwright/test';
import { type E2EPage, test } from '@stencil/playwright';
import type { InputTypeOnDefault } from '../schema';
import { Callback } from '../schema/enums';
import { KolEvent } from '../utils/events';
import type { FillAction } from './utils/FillAction';
import { INPUTS_SELECTOR } from './utils/inputsSelector';
type TestInputCallbacksAndEventsOptions = {
additionalProperties?: string;
componentName: string;
equalityCheck?: 'toBe' | 'toEqual';
expectedValue?: unknown;
fillAction?: FillAction;
omittedEvents?: string[];
selectInput?: (page: Page & E2EPage) => Locator;
testValue?: unknown;
};
const testInputCallbacksAndEvents = <ElementType extends { _on?: InputTypeOnDefault } & (HTMLElement | SVGElement)>({
additionalProperties = '',
componentName,
equalityCheck = 'toBe',
expectedValue,
fillAction,
omittedEvents = [],
selectInput,
testValue = 'Test Input',
}: TestInputCallbacksAndEventsOptions) => {
test.describe('Callbacks and DOM events', () => {
const EVENTS: [string, Callback, KolEvent, unknown?, unknown?][] = [
['click', Callback.onClick, KolEvent.click],
['focus', Callback.onFocus, KolEvent.focus],
['blur', Callback.onBlur, KolEvent.blur],
['input', Callback.onInput, KolEvent.input, testValue, expectedValue ?? testValue],
['change', Callback.onChange, KolEvent.change, testValue, expectedValue ?? testValue],
];
EVENTS.filter(([eventName]) => !omittedEvents.includes(eventName)).forEach(([nativeEventName, callbackName, kolEventName, testValue, expectedValue]) => {
test(`should call ${callbackName} callback when internal input emits ${nativeEventName}`, async ({ page, browserName }) => {
/* See https://github.com/microsoft/playwright/issues/33864 */
test.skip(
componentName === 'kol-input-color' && nativeEventName === 'click' && browserName === 'firefox',
'Clicking on an input[type=color] in Firefox currently makes the page close itself.',
);
await page.setContent(`<${componentName} _label="Input" ${additionalProperties}></${componentName}>`);
const component = page.locator(componentName);
const input = selectInput ? selectInput(page) : page.locator(INPUTS_SELECTOR);
const callbackPromise = component.evaluate((element: ElementType, callbackName) => {
return new Promise<unknown>((resolve) => {
element._on = {
[callbackName]: (_event: InputEvent, value?: unknown) => {
resolve(value);
},
};
});
}, callbackName);
const eventPromise = component.evaluate((element: ElementType, kolEventName) => {
return new Promise<unknown>((resolve) => {
element.addEventListener(kolEventName, (event: Event) => {
resolve((event as CustomEvent).detail);
});
});
}, kolEventName);
await page.waitForChanges();
if (fillAction) {
await fillAction(page);
} else if (typeof testValue === 'string') {
await page.locator(INPUTS_SELECTOR).fill(testValue);
}
await page.waitForChanges();
await input.dispatchEvent(nativeEventName);
await expect(callbackPromise).resolves[equalityCheck](expectedValue);
await expect(eventPromise).resolves[equalityCheck](expectedValue ?? null); // For no value, callbacks use `undefined`, the event detail is `null`.
});
});
});
};
export { testInputCallbacksAndEvents };