From 1dfdd1cd7f3d6ae56e08c49b66d89aa357287f81 Mon Sep 17 00:00:00 2001 From: Benjamin Pagelsdorf Date: Mon, 25 Mar 2024 16:57:10 +0100 Subject: [PATCH] refactor(elements/ino-currency-input): migrate stencil e2e tests to playwright (#1329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * migrate ino-currency-input tests * refactor: use fill instead of setting the value * refactor: move type function to utility * refactor: formatting --------- Co-authored-by: Jan-Niklas Voß --- packages/elements/setupSpecTests.ts | 12 +- .../ino-currency-input.e2e.ts | 145 ------------------ .../ino-currency-input.spec.ts | 76 +++++++++ .../src/components/ino-input/ino-input.tsx | 8 +- packages/elements/src/util/test-utils.ts | 36 ++--- .../ino-currency-input.spec.ts | 50 ++++++ 6 files changed, 153 insertions(+), 174 deletions(-) delete mode 100644 packages/elements/src/components/ino-currency-input/ino-currency-input.e2e.ts create mode 100644 packages/elements/src/components/ino-currency-input/ino-currency-input.spec.ts create mode 100644 packages/storybook/src/stories/ino-currency-input/ino-currency-input.spec.ts diff --git a/packages/elements/setupSpecTests.ts b/packages/elements/setupSpecTests.ts index 4d80a3d7d0..2c3ecaedb8 100644 --- a/packages/elements/setupSpecTests.ts +++ b/packages/elements/setupSpecTests.ts @@ -4,11 +4,7 @@ observe() {} }; -jest.mock('@material/textfield', () => ({ - ...jest.requireActual('@material/textfield'), - MDCTextField: class { - public focus = jest.fn(); - public destroy = jest.fn(); - public value = ''; - }, -})); +HTMLInputElement.prototype.checkValidity = jest.fn(); +HTMLInputElement.prototype.setSelectionRange = jest.fn(); + +jest.mock('@material/textfield'); diff --git a/packages/elements/src/components/ino-currency-input/ino-currency-input.e2e.ts b/packages/elements/src/components/ino-currency-input/ino-currency-input.e2e.ts deleted file mode 100644 index 7d2861f467..0000000000 --- a/packages/elements/src/components/ino-currency-input/ino-currency-input.e2e.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { setupPageWithContent } from '../../util/e2etests-setup'; -const CONTENT = ` - - - -`; -const NATIVE_INPUT_SELECTOR = 'ino-input > .mdc-text-field > input'; -const HIDDEN_INPUT_SELECTOR = 'input[type="hidden"]'; - -describe('InoCurrencyInput', () => { - describe('Properties', () => { - it('should render without value', async () => { - const page = await setupPageWithContent(CONTENT); - const input = await page.find(NATIVE_INPUT_SELECTOR); - expect(await input.getProperty('value')).toBe(''); - }); - - it('should setup without default value', async () => { - const page = await setupPageWithContent(CONTENT); - const currencyInput = await page.find('ino-currency-input'); - currencyInput.setAttribute('value', 15000.99); - await page.waitForChanges(); - - const input = await page.find(NATIVE_INPUT_SELECTOR); - expect(await input.getProperty('value')).toBe('15.000,99'); - - const hiddenInput = await page.find(HIDDEN_INPUT_SELECTOR); - expect(await hiddenInput.getProperty('value')).toBe('15000.99'); - }); - - it('should format thousands on focus and blur', async () => { - const page = await setupPageWithContent(CONTENT); - const currencyInput = await page.find('ino-currency-input'); - currencyInput.setAttribute('value', 15000.99); - await page.waitForChanges(); - - // Focus - const inoInput = await page.find('ino-input'); - inoInput.triggerEvent('inoFocus'); - await page.waitForChanges(); - let input = await page.find(NATIVE_INPUT_SELECTOR); - expect(await input.getProperty('value')).toBe('15000,99'); - - // Blur - inoInput.triggerEvent('inoBlur'); - await page.waitForChanges(); - input = await page.find(NATIVE_INPUT_SELECTOR); - expect(await input.getProperty('value')).toBe('15.000,99'); - }); - - it('should render with different locale', async () => { - const page = await setupPageWithContent(CONTENT); - const currencyInput = await page.find('ino-currency-input'); - currencyInput.setAttribute('currency-locale', 'en-US'); - currencyInput.setAttribute('value', 15000.99); - await page.waitForChanges(); - - const input = await page.find(NATIVE_INPUT_SELECTOR); - expect(await input.getProperty('value')).toBe('15,000.99'); - }); - - it('should allow negative inputs', async () => { - const page = await setupPageWithContent(CONTENT); - const input = await page.find(NATIVE_INPUT_SELECTOR); - await input.type('-1500,00'); - await page.waitForChanges(); - const hiddenInput = await page.find(HIDDEN_INPUT_SELECTOR); - expect(await hiddenInput.getProperty('value')).toBe('-1500'); - }); - - it('should render with 0', async () => { - const page = await setupPageWithContent(CONTENT); - const input = await page.find(NATIVE_INPUT_SELECTOR); - await input.type('0'); - await page.waitForChanges(); - const hiddenInput = await page.find(HIDDEN_INPUT_SELECTOR); - expect(await hiddenInput.getProperty('value')).toBe('0'); - }); - - it('should prevent negative inputs on min=0', async () => { - const page = await setupPageWithContent(CONTENT); - const inoInput = await page.find('ino-input'); - inoInput.setProperty('min', '0'); - await page.waitForChanges(); - - const input = await page.find(NATIVE_INPUT_SELECTOR); - await input.type('-1500,00'); - await page.waitForChanges(); - const hiddenInput = await page.find(HIDDEN_INPUT_SELECTOR); - expect(await hiddenInput.getProperty('value')).toBe('1500'); - }); - }); - - describe('Events', () => { - it('should fire valueChange on user input', async () => { - const page = await setupPageWithContent(CONTENT); - const valueChange = await page.spyOnEvent('valueChange'); - expect(valueChange).not.toHaveReceivedEvent(); - - const input = await page.find(NATIVE_INPUT_SELECTOR); - await input.type('5000,99'); - await page.waitForChanges(); - expect(valueChange.firstEvent.detail).toBe(5); - expect(valueChange.lastEvent.detail).toBe(5000.99); - }); - - it('should not fire on non numeric values', async () => { - const page = await setupPageWithContent(CONTENT); - const valueChange = await page.spyOnEvent('valueChange'); - expect(valueChange).not.toHaveReceivedEvent(); - - const input = await page.find(NATIVE_INPUT_SELECTOR); - await input.type('1a'); - await page.waitForChanges(); - expect(valueChange.lastEvent.detail).toBe(1); - }); - - it('should not fire on more than two decimals', async () => { - const page = await setupPageWithContent(CONTENT); - const valueChange = await page.spyOnEvent('valueChange'); - expect(valueChange).not.toHaveReceivedEvent(); - - const input = await page.find(NATIVE_INPUT_SELECTOR); - await input.type('11,119'); - await page.waitForChanges(); - expect(valueChange.lastEvent.detail).toBe(11.11); - }); - - it('should accept zero values (controlled)', async () => { - const testValue = 0; - const expected = Intl.NumberFormat('de-DE', { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }).format(testValue); - - const page = await setupPageWithContent(` - - - - `); - const input = await page.find(NATIVE_INPUT_SELECTOR); - expect(await input.getProperty('value')).toBe(expected); - }); - }); -}); diff --git a/packages/elements/src/components/ino-currency-input/ino-currency-input.spec.ts b/packages/elements/src/components/ino-currency-input/ino-currency-input.spec.ts new file mode 100644 index 0000000000..0397efec08 --- /dev/null +++ b/packages/elements/src/components/ino-currency-input/ino-currency-input.spec.ts @@ -0,0 +1,76 @@ +import { newSpecPage, SpecPage } from '@stencil/core/testing'; +import { fillInput, listenForEvent } from '../../util/test-utils'; +import { CurrencyInput } from './ino-currency-input'; +import { Input } from '../ino-input/ino-input'; +import { Label } from '../ino-label/ino-label'; + +const testValue = 0; +const CONTENT = ` + + + +`; + +const NATIVE_INPUT_SELECTOR = 'ino-input > .mdc-text-field > input'; + +describe('InoCurrencyInput - Events', () => { + let page: SpecPage; + let inoCurrencyInput: HTMLInoCurrencyInputElement; + let nativeInput: HTMLInputElement; + + beforeEach(async () => { + page = await newSpecPage({ + components: [CurrencyInput, Input, Label], + html: CONTENT, + }); + inoCurrencyInput = page.body.querySelector('ino-currency-input'); + nativeInput = inoCurrencyInput.querySelector(NATIVE_INPUT_SELECTOR); + }); + + it.only('should fire valueChange on user input', async () => { + const { eventSpy, assertEventDetails } = listenForEvent( + page, + 'valueChange', + ); + expect(eventSpy).not.toHaveBeenCalled(); + + await fillInput(page, nativeInput, '5000,99'); + + assertEventDetails(5); + const lastEventIndex = eventSpy.mock.calls.length - 1; + assertEventDetails(5000.99, lastEventIndex); + }); + + it('should not fire on non numeric values', async () => { + const { eventSpy, assertEventDetails } = listenForEvent( + page, + 'valueChange', + ); + expect(eventSpy).not.toHaveBeenCalled(); + + await fillInput(page, nativeInput, '1a'); + assertEventDetails(1); + }); + + it('should not fire on more than two decimals', async () => { + const { eventSpy, assertEventDetails } = listenForEvent( + page, + 'valueChange', + ); + expect(eventSpy).not.toHaveBeenCalled(); + + await fillInput(page, nativeInput, '11,119'); + + const lastEventIndex = eventSpy.mock.calls.length - 1; + assertEventDetails(11.11, lastEventIndex); + }); + + it('should accept zero values (controlled)', async () => { + const expected = Intl.NumberFormat('de-DE', { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(testValue); + + expect(nativeInput.value).toBe(expected); + }); +}); diff --git a/packages/elements/src/components/ino-input/ino-input.tsx b/packages/elements/src/components/ino-input/ino-input.tsx index 82fd189507..a437e42d78 100644 --- a/packages/elements/src/components/ino-input/ino-input.tsx +++ b/packages/elements/src/components/ino-input/ino-input.tsx @@ -1,6 +1,8 @@ -import { MDCTextField } from '@material/textfield'; -import { MDCTextFieldHelperText } from '@material/textfield/helper-text'; -import { MDCTextFieldIcon } from '@material/textfield/icon'; +import { + MDCTextField, + MDCTextFieldHelperText, + MDCTextFieldIcon, +} from '@material/textfield'; import { Component, ComponentInterface, diff --git a/packages/elements/src/util/test-utils.ts b/packages/elements/src/util/test-utils.ts index ac84cacebe..0b83378970 100644 --- a/packages/elements/src/util/test-utils.ts +++ b/packages/elements/src/util/test-utils.ts @@ -1,30 +1,30 @@ -import { E2EPage, SpecPage } from '@stencil/core/testing'; - -export async function getFocusedElementOfPage(page: E2EPage) { - return page.$eval(':focus', (el) => el); -} +import { SpecPage } from '@stencil/core/testing'; export function listenForEvent(page: SpecPage, eventName: string) { const eventSpy = jest.fn(); page.body.addEventListener(eventName, eventSpy); - function assertEventDetails(detail: any) { - return expect(eventSpy.mock.calls[0][0]).toHaveProperty('detail', detail); + function assertEventDetails(detail: any, eventIndex = 0) { + return expect(eventSpy.mock.calls[eventIndex][0]).toHaveProperty( + 'detail', + detail, + ); } return { eventSpy, assertEventDetails }; } -export const pxToNumber = (s: string): number => - Number(s.substring(0, s.length - 2)); - -export const setPropertyOfEl = ( - page: E2EPage, - selector: string, - props: Partial, -) => { - return page.$eval(selector, (el: Component) => { - Object.assign(el, props); +export async function fillInput( + page: SpecPage, + input: HTMLInputElement, + stringToType: string, +) { + const arr = [...stringToType]; + input.value = ''; + arr.forEach((char) => { + input.value += char; + input.dispatchEvent(new Event('input')); }); -}; + await page.waitForChanges(); +} diff --git a/packages/storybook/src/stories/ino-currency-input/ino-currency-input.spec.ts b/packages/storybook/src/stories/ino-currency-input/ino-currency-input.spec.ts new file mode 100644 index 0000000000..8cc811affb --- /dev/null +++ b/packages/storybook/src/stories/ino-currency-input/ino-currency-input.spec.ts @@ -0,0 +1,50 @@ +import { expect, Locator, test } from '@playwright/test'; +import { goToStory, setAttribute, setProperty } from '../test-utils'; + +test.describe('ino-currency-input', () => { + let inoCurrencyInput: Locator; + let input: Locator; + let inoInput: Locator; + let hiddenInput: Locator; + + test.beforeEach(async ({ page }) => { + await goToStory(page, ['Input', 'ino-currency-input', 'default']); + inoCurrencyInput = page.locator('ino-currency-input'); + hiddenInput = inoCurrencyInput.locator('input[type=hidden]'); + inoInput = inoCurrencyInput.locator('ino-input'); + input = inoInput.getByRole('textbox'); + await setAttribute(inoCurrencyInput, 'value', ''); + }); + + test('should set value attribute', async () => { + await input.fill('500,00'); + await input.blur(); + await expect(input).toHaveValue('500,00'); + await expect(hiddenInput).toHaveValue('500'); + }); + + test('should format thousands on blur', async () => { + await input.fill('15000,99'); + await input.blur(); + await expect(input).toHaveValue('15.000,99'); + }); + + test('should render with different locale', async () => { + await setAttribute(inoCurrencyInput, 'currency-locale', 'en-US'); + await input.fill('15000.99'); + await input.blur(); + await expect(input).toHaveValue('15,000.99'); + }); + + test('should allow negative inputs', async () => { + await input.fill('-1500,00'); + await expect(input).toHaveValue('-1500,00'); + await expect(hiddenInput).toHaveValue('-1500'); + }); + + test('should prevent negative inputs on min=0', async () => { + await setProperty(inoInput, 'min', '0'); + await input.fill('-1500,00'); + await expect(hiddenInput).toHaveValue('1500'); + }); +});