From 67b95c1c7f0c191ba297746205eb2ace3302577d Mon Sep 17 00:00:00 2001 From: Ben Siggery <14013357+siggerzz@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:35:42 +0000 Subject: [PATCH] refactor(pie-text-input): DSW-2384 tests to use @playwright/test (#2194) * text input tests * refactor components tests * add variation stories * add slot variations * visual tests * update import --------- Co-authored-by: Ben Siggery --- .../testing/pie-text-input.test.stories.ts | 402 ++++++ .../playwright-lit-visual.config.ts | 6 +- .../pie-text-input/playwright-lit.config.ts | 6 +- .../test/accessibility/pie-text-input.spec.ts | 9 +- .../test/component/pie-text-input.spec.ts | 1234 +++++++++-------- .../test/helpers/page-object/selectors.ts | 16 + .../test/visual/pie-text-input.spec.ts | 466 +------ packages/components/pie-text-input/turbo.json | 40 + 8 files changed, 1113 insertions(+), 1066 deletions(-) create mode 100644 apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts create mode 100644 packages/components/pie-text-input/test/helpers/page-object/selectors.ts create mode 100644 packages/components/pie-text-input/turbo.json diff --git a/apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts b/apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts new file mode 100644 index 0000000000..add3743f79 --- /dev/null +++ b/apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts @@ -0,0 +1,402 @@ +import { html, nothing } from 'lit'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { action } from '@storybook/addon-actions'; +import { useArgs as UseArgs } from '@storybook/preview-api'; +import { type Meta } from '@storybook/web-components'; + +import '@justeattakeaway/pie-text-input'; +import { + type TextInputProps as TextInputPropsBase, types, inputModes, statusTypes, sizes, defaultProps, +} from '@justeattakeaway/pie-text-input'; +import '@justeattakeaway/pie-button'; +import '@justeattakeaway/pie-link'; +import '@justeattakeaway/pie-form-label'; +import '@justeattakeaway/pie-icons-webc/dist/IconPlaceholder.js'; +import '@justeattakeaway/pie-icons-webc/dist/IconEmail.js'; +import '@justeattakeaway/pie-icons-webc/dist/IconUser.js'; + +import { + createStory, createVariantStory, type PropDisplayOptions, type TemplateFunction, +} from '../../utilities'; + +// Extending the props type definition to include storybook specific properties for controls +type TextInputProps = TextInputPropsBase & { + leadingSlot: keyof typeof leadingSlotOptions; + trailingSlot: keyof typeof trailingSlotOptions; +}; + +type TextInputStoryMeta = Meta; + +const defaultArgs: TextInputProps = { + ...defaultProps, + leadingSlot: 'None', + trailingSlot: 'None', +}; + +const leadingSlotOptions = { + None: nothing, + 'Icon (Placeholder)': html``, + 'Short text (#)': html`#`, +}; + +const trailingSlotOptions = { + None: nothing, + 'Icon (Placeholder)': html``, + 'Short text (#)': html`#`, +}; + +const textInputStoryMeta: TextInputStoryMeta = { + title: 'Text Input', + component: 'pie-text-input', + argTypes: { + type: { + control: 'select', + options: types, + defaultValue: { + summary: defaultProps.type, + }, + }, + value: { + control: 'text', + defaultValue: { + summary: defaultProps.value, + }, + }, + name: { + control: 'text', + defaultValue: { + summary: defaultArgs.name, + }, + }, + disabled: { + control: 'boolean', + defaultValue: { + summary: false, + }, + }, + pattern: { + control: 'text', + defaultValue: { + summary: '', + }, + if: { arg: 'type', neq: 'number' }, + }, + minlength: { + control: 'number', + defaultValue: { + summary: '', + }, + if: { arg: 'type', neq: 'number' }, + }, + maxlength: { + control: 'number', + defaultValue: { + summary: '', + }, + if: { arg: 'type', neq: 'number' }, + }, + min: { + control: 'number', + defaultValue: { + summary: '', + }, + if: { arg: 'type', eq: 'number' }, + }, + max: { + control: 'number', + defaultValue: { + summary: '', + }, + if: { arg: 'type', eq: 'number' }, + }, + autocomplete: { + control: 'text', + defaultValue: { + summary: 'off', + }, + }, + placeholder: { + control: 'text', + defaultValue: { + summary: '', + }, + if: { arg: 'type', neq: 'number' }, + }, + step: { + control: 'text', + defaultValue: { + summary: '', + }, + if: { arg: 'type', eq: 'number' }, + }, + autoFocus: { + control: 'boolean', + defaultValue: { + summary: false, + }, + }, + inputmode: { + control: 'select', + options: inputModes, + defaultValue: { + summary: '', + }, + }, + readonly: { + control: 'boolean', + defaultValue: { + summary: false, + }, + }, + defaultValue: { + control: 'text', + defaultValue: { + summary: '', + }, + }, + leadingSlot: { + name: 'Leading Slot', + control: 'select', + options: Object.keys(leadingSlotOptions), + mapping: leadingSlotOptions, + }, + trailingSlot: { + name: 'Trailing Slot', + control: 'select', + options: Object.keys(trailingSlotOptions), + mapping: trailingSlotOptions, + }, + assistiveText: { + control: 'text', + defaultValue: { + summary: '', + }, + }, + status: { + control: 'select', + options: statusTypes, + defaultValue: { + summary: defaultProps.status, + }, + }, + size: { + control: 'select', + options: sizes, + defaultValue: { + summary: defaultProps.size, + }, + }, + required: { + control: 'boolean', + defaultValue: { + summary: false, + }, + }, + showEmailField: { + control: 'boolean', + defaultValue: { + summary: false, + }, + }, + }, + args: defaultArgs, +}; + +const Template = ({ + type, + value, + name, + disabled, + pattern, + minlength, + maxlength, + min, + max, + autocomplete, + placeholder, + autoFocus, + inputmode, + readonly, + defaultValue, + leadingSlot, + trailingSlot, + assistiveText, + status, + step, + size, + required, +}: TextInputProps) => { + const [, updateArgs] = UseArgs(); + + function onInput (event: InputEvent) { + const inputElement = event.target as HTMLInputElement; + const outputElement = (document.getElementById('output') as HTMLDivElement); + updateArgs({ value: inputElement?.value }); + + console.info('input event recieved', JSON.stringify(event)); + + const currentValue = (event.target as HTMLInputElement).value; + outputElement.innerText = currentValue; + } + + function onChange (event: CustomEvent) { + action('change')({ + detail: event.detail, + }); + console.info('change event recieved', JSON.stringify(event)); + } + + return html` + + ${leadingSlot} + ${trailingSlot} + +
+ `; +}; + +const onSubmit = (event: Event) => { + event.preventDefault(); + const form = document.querySelector('#testForm') as HTMLFormElement; + const output = document.querySelector('#formDataOutput') as HTMLDivElement; + + const formData = new FormData(form); + const formDataObj: { [key: string]: FormDataEntryValue } = {}; + formData.forEach((value, key) => { + formDataObj[key] = value; + }); + + output.innerText = JSON.stringify(formDataObj); +}; + +const ExampleFormTemplate: TemplateFunction = ({ + defaultValue, + disabled, + showEmailField = false, +}: TextInputProps & { showEmailField?: boolean }) => html` +
+ Username: + + + + ${showEmailField ? html` + Email: + + + + ` : nothing} + +
+ Reset + Submit +
+
+
+`; + +const DisabledFieldsetTemplate: TemplateFunction = () => html` +
+
+ +
+ + Submit +
+
+ `; + +export const Default = createStory(Template, defaultArgs)(); +export const LeadingIcon = createStory(Template, { ...defaultArgs, leadingSlot: 'Icon (Placeholder)' })(); +export const LeadingText = createStory(Template, { ...defaultArgs, leadingSlot: 'Short text (#)' })(); +export const TrailingIcon = createStory(Template, { ...defaultArgs, trailingSlot: 'Icon (Placeholder)' })(); +export const TrailingText = createStory(Template, { ...defaultArgs, trailingSlot: 'Short text (#)' })(); +export const ExampleForm = createStory(ExampleFormTemplate, defaultArgs)(); + +export const LeadingAndTrailingIcon = createStory(Template, { ...defaultArgs, leadingSlot: 'Icon (Placeholder)', trailingSlot: 'Icon (Placeholder)' })(); +export const LeadingAndTrailingText = createStory(Template, { ...defaultArgs, leadingSlot: 'Short text (#)', trailingSlot: 'Short text (#)' })(); + +export const DisabledFieldset = createStory(DisabledFieldsetTemplate, defaultArgs)(); + +const sharedTextInputPropsMatrix: Partial> = { + assistiveText: ['', 'assistive text'], + disabled: [true, false], + size: [...sizes], + readonly: [true, false], + value: ['', 'value'], + placeholder: ['', 'placeholder'], +}; + +const defaultTextInputPropsMatrix : Partial> = { + ...sharedTextInputPropsMatrix, + status: [statusTypes[0]], +}; + +const successTextInputPropsMatrix : Partial> = { + ...sharedTextInputPropsMatrix, + status: [statusTypes[1]], +}; + +const errorTextInputPropsMatrix : Partial> = { + ...sharedTextInputPropsMatrix, + status: [statusTypes[2]], +}; + +const typeTextInputPropsMatrix : Partial> = { + type: ['text', 'password'], + value: ['String'], +}; + +const slotVariations : Partial> = { + leadingSlot: Object.values(leadingSlotOptions), + trailingSlot: Object.values(trailingSlotOptions), + value: ['String'], +}; + +const slotPropDisplayOptions: PropDisplayOptions = { + hiddenProps: ['leadingSlot', 'trailingSlot'], +}; + +export const DefaultVariations = createVariantStory(Template, defaultTextInputPropsMatrix, { multiColumn: true }); +export const SuccessVariations = createVariantStory(Template, successTextInputPropsMatrix, { multiColumn: true }); +export const ErrorVariations = createVariantStory(Template, errorTextInputPropsMatrix, { multiColumn: true }); +export const SlotVariations = createVariantStory(Template, slotVariations, { ...slotPropDisplayOptions, multiColumn: true }); +export const TypeVariations = createVariantStory(Template, typeTextInputPropsMatrix, { ...slotPropDisplayOptions, multiColumn: true }); +export default textInputStoryMeta; + diff --git a/packages/components/pie-text-input/playwright-lit-visual.config.ts b/packages/components/pie-text-input/playwright-lit-visual.config.ts index fb0f14c480..2fd82d7d5f 100644 --- a/packages/components/pie-text-input/playwright-lit-visual.config.ts +++ b/packages/components/pie-text-input/playwright-lit-visual.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from '@sand4rt/experimental-ct-web'; -import { getPlaywrightVisualConfig } from '@justeattakeaway/pie-components-config'; +import { defineConfig } from '@playwright/test'; +import { getPlaywrightNativeVisualConfig } from '@justeattakeaway/pie-components-config'; -export default defineConfig(getPlaywrightVisualConfig()); +export default defineConfig(getPlaywrightNativeVisualConfig()); diff --git a/packages/components/pie-text-input/playwright-lit.config.ts b/packages/components/pie-text-input/playwright-lit.config.ts index e50b9373b3..6dcc0f833d 100644 --- a/packages/components/pie-text-input/playwright-lit.config.ts +++ b/packages/components/pie-text-input/playwright-lit.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from '@sand4rt/experimental-ct-web'; -import { getPlaywrightConfig } from '@justeattakeaway/pie-components-config'; +import { defineConfig } from '@playwright/test'; +import { getPlaywrightNativeConfig } from '@justeattakeaway/pie-components-config'; -export default defineConfig(getPlaywrightConfig()); +export default defineConfig(getPlaywrightNativeConfig()); diff --git a/packages/components/pie-text-input/test/accessibility/pie-text-input.spec.ts b/packages/components/pie-text-input/test/accessibility/pie-text-input.spec.ts index 88e447f0c2..221ebda48a 100644 --- a/packages/components/pie-text-input/test/accessibility/pie-text-input.spec.ts +++ b/packages/components/pie-text-input/test/accessibility/pie-text-input.spec.ts @@ -1,10 +1,11 @@ -import { test, expect } from '@justeattakeaway/pie-webc-testing/src/playwright/webc-fixtures.ts'; -import { PieTextInput } from '../../src/index.ts'; +import { test, expect } from '@justeattakeaway/pie-webc-testing/src/playwright/playwright-fixtures.ts'; +import { BasePage } from '@justeattakeaway/pie-webc-testing/src/helpers/page-object/base-page.ts'; test.describe('PieTextInput - Accessibility tests', () => { - test('a11y - should test the PieTextInput component WCAG compliance', async ({ makeAxeBuilder, mount }) => { - await mount(PieTextInput); + test('a11y - should test the PieTextInput component WCAG compliance', async ({ makeAxeBuilder, page }) => { + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); const results = await makeAxeBuilder().analyze(); diff --git a/packages/components/pie-text-input/test/component/pie-text-input.spec.ts b/packages/components/pie-text-input/test/component/pie-text-input.spec.ts index ed8843ac5d..6683960764 100644 --- a/packages/components/pie-text-input/test/component/pie-text-input.spec.ts +++ b/packages/components/pie-text-input/test/component/pie-text-input.spec.ts @@ -1,33 +1,16 @@ -import { setupFormDataExtraction, getFormDataObject } from '@justeattakeaway/pie-webc-testing/src/helpers/form-helpers.ts'; -import { test, expect } from '@sand4rt/experimental-ct-web'; -import { IconPlaceholder } from '@justeattakeaway/pie-icons-webc/dist/IconPlaceholder'; -import { PieAssistiveText } from '@justeattakeaway/pie-assistive-text'; -import { PieTextInput, type TextInputProps } from '../../src/index.ts'; -import { statusTypes } from '../../src/defs.ts'; - -const componentSelector = '[data-test-id="pie-text-input"]'; -const assistiveTextSelector = '[data-test-id="pie-text-input-assistive-text"]'; +import { test, expect } from '@playwright/test'; +import { BasePage } from '@justeattakeaway/pie-webc-testing/src/helpers/page-object/base-page.ts'; +import { statusTypes, type TextInputProps } from '../../src/defs.ts'; +import { textInput } from '../helpers/page-object/selectors.ts'; test.describe('PieTextInput - Component tests', () => { - // IMPORTANT: Mounting and Unmounting the component before each test ensures that any tests that do not explicitly - // mount the component will still have it available in Playwright's cache (loaded and registered in the test browser) - test.beforeEach(async ({ mount }) => { - const component = await mount(PieTextInput); - await component.unmount(); - - const iconComponent = await mount(IconPlaceholder); - await iconComponent.unmount(); - - const assistiveTextComponent = await mount(PieAssistiveText); - await assistiveTextComponent.unmount(); - }); - - test('should render successfully', async ({ mount, page }) => { + test('should render successfully', async ({ page }) => { // Arrange - await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = page.locator(componentSelector); + const input = page.getByTestId(textInput.selectors.container.dataTestId); // Assert expect(input).toBeVisible(); @@ -35,128 +18,144 @@ test.describe('PieTextInput - Component tests', () => { test.describe('Props', () => { test.describe('type', () => { - test('should default to text type if no type prop provided', async ({ mount }) => { + test('should default to text type if no type prop provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert expect(input).toHaveAttribute('type', 'text'); }); - test('should apply the type prop to the HTML input rendered', async ({ mount }) => { + test('should apply the type prop to the HTML input rendered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - type: 'number', - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'number', + value: '123', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(input).toHaveAttribute('type', 'number'); + await expect(input).toHaveAttribute('type', 'number'); }); }); test.describe('value', () => { - test('should default to an empty string if no value prop provided', async ({ mount }) => { + test('should default to an empty string if no value prop provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.inputValue())).toBe(''); + await expect(input).toHaveValue(''); }); - test('should apply the value prop to the HTML input rendered', async ({ mount }) => { + test('should apply the value prop to the HTML input rendered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - value: 'test', - } as TextInputProps, - }); + const props: TextInputProps = { + value: 'test', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.inputValue())).toBe('test'); + await expect(input).toHaveValue('test'); }); }); test.describe('name', () => { - test('should not render a name attribute on the input element if no name provided', async ({ mount }) => { + test('should not render a name attribute on the input element if no name provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('name'))).toBe(null); + await expect(input).not.toHaveAttribute('name'); }); - test('should apply the name prop to the HTML input rendered', async ({ mount }) => { + test('should apply the name prop to the HTML input rendered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - name: 'test', - } as TextInputProps, - }); + const props: TextInputProps = { + name: 'test', + value: '123', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('name'))).toBe('test'); + await expect(input).toHaveAttribute('name', 'test'); }); }); test.describe('pattern', () => { - test('should not render a pattern attribute on the input element if no pattern provided', async ({ mount }) => { + test('should not render a pattern attribute on the input element if no pattern provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('pattern'))).toBe(null); + await expect(input).not.toHaveAttribute('pattern'); }); - test('should be invalid state `patternMismatch` if pattern is not met', async ({ mount, page }) => { + test('should be invalid state `patternMismatch` if pattern is not met', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - pattern: '[a-z]{4,8}', - } as TextInputProps, + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load(); + + const component = page.getByTestId(textInput.selectors.container.dataTestId); + + // Setting this manually due to Storybook limitations - https://storybook.js.org/docs/writing-stories/args#setting-args-through-the-url + await component.evaluate((element) => { + (element as HTMLInputElement).pattern = '[a-z]{4,8}'; }); - // Act - await component.type('hello world'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('hello world'); - const isInvalid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.patternMismatch); + const isInvalid = await component.evaluate((element) => (element as HTMLInputElement).validity.patternMismatch); // Assert expect(isInvalid).toBe(true); }); - test('should be valid state if pattern is met', async ({ mount, page }) => { + test('should be valid state if pattern is met', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - pattern: '[a-z]{4,8}', - } as TextInputProps, + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load(); + + const component = page.getByTestId(textInput.selectors.container.dataTestId); + + // Setting this manually due to Storybook limitations - https://storybook.js.org/docs/writing-stories/args#setting-args-through-the-url + await component.evaluate((element) => { + (element as HTMLInputElement).pattern = '[a-z]{4,8}'; }); // Act - await component.type('test'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); @@ -166,46 +165,53 @@ test.describe('PieTextInput - Component tests', () => { }); test.describe('minlength', () => { - test('should not render a minlength attribute on the input element if no minlength provided', async ({ mount }) => { + test('should not render a minlength attribute on the input element if no minlength provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('minlength'))).toBe(null); + await expect(input).not.toHaveAttribute('minlength'); }); - test('should be invalid state `tooShort` if the min length is not entered', async ({ mount, page }) => { + test('should be invalid state `tooShort` if the min length is not entered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - minlength: 3, - } as TextInputProps, - }); + const props: TextInputProps = { + minlength: 3, + value: '', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - await component.type('te'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('te'); - const isInvalid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.tooShort); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isInvalid = await component.evaluate((element) => (element as HTMLInputElement).validity.tooShort); // Assert expect(isInvalid).toBe(true); }); - test('should be valid state if the min length is met', async ({ mount, page }) => { + test('should be valid state if the min length is met', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - minlength: 3, - } as TextInputProps, - }); + const props: TextInputProps = { + minlength: 3, + value: 'test', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - await component.type('tes'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); - const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isValid = await component.evaluate((element) => (element as HTMLInputElement).validity.valid); // Assert expect(isValid).toBe(true); @@ -213,64 +219,75 @@ test.describe('PieTextInput - Component tests', () => { }); test.describe('maxlength', () => { - test('should not render a maxlength attribute on the input element if no maxlength provided', async ({ mount }) => { + test('should not render a maxlength attribute on the input element if no maxlength provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('maxlength'))).toBe(null); + await expect(input).not.toHaveAttribute('maxlength'); }); - test('should not be able to input a value greater than the maxlength provided', async ({ mount }) => { + test('should not be able to input a value greater than the maxlength provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - maxlength: 3, - } as TextInputProps, - }); + const props: TextInputProps = { + maxlength: 3, + value: '', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - await component.type('test'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); + + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const inputValue = await component.evaluate((element) => (element as HTMLInputElement).value); // Assert - expect((await component.locator('input').inputValue())).toBe('tes'); + expect(inputValue).toBe('tes'); }); - test('should be invalid state `tooLong` if the maxlength is exceeded programmatically', async ({ mount, page }) => { + test('should be invalid state `tooLong` if the maxlength is exceeded programmatically', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - maxlength: 2, - value: 'test', - } as TextInputProps, - }); + const props: TextInputProps = { + maxlength: 2, + value: 'test', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act await page.focus('pie-text-input'); await page.keyboard.press('ArrowRight'); // Move cursor to end of input so we don't delete the whole value await page.keyboard.press('Backspace'); // Delete the last character to trigger an input event - this should trigger the validity state update - const isInvalid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.tooLong); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isInvalid = await component.evaluate((element) => (element as HTMLInputElement).validity.tooLong); // Assert expect(isInvalid).toBe(true); }); - test('should be valid state if the max length is not exceeded', async ({ mount, page }) => { + test('should be valid state if the max length is not exceeded', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - maxlength: 3, - } as TextInputProps, - }); + const props: TextInputProps = { + maxlength: 3, + value: '', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - await component.type('tes'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); - const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isValid = await component.evaluate((element) => (element as HTMLInputElement).validity.valid); // Assert expect(isValid).toBe(true); @@ -278,192 +295,212 @@ test.describe('PieTextInput - Component tests', () => { }); test.describe('autocomplete', () => { - test('should not render an autocomplete attribute on the input element if no autocomplete provided', async ({ mount }) => { + test('should not render an autocomplete attribute on the input element if no autocomplete provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('autocomplete'))).toBe(null); + await expect(input).not.toHaveAttribute('autocomplete'); }); - test('should apply the autocomplete prop to the HTML input rendered', async ({ mount }) => { + test('should apply the autocomplete prop to the HTML input rendered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - autocomplete: 'on', - } as TextInputProps, - }); + const props: TextInputProps = { + autocomplete: 'on', + value: 'test', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('autocomplete'))).toBe('on'); + await expect(input).toHaveAttribute('autocomplete', 'on'); }); }); test.describe('placeholder', () => { - test('should not render a placeholder attribute on the input element if no placeholder provided', async ({ mount }) => { + test('should not render a placeholder attribute on the input element if no placeholder provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('placeholder'))).toBe(null); + await expect(input).not.toHaveAttribute('placeholder'); }); - test('should apply the placeholder prop to the HTML input rendered', async ({ mount }) => { + test('should apply the placeholder prop to the HTML input rendered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - placeholder: 'Test Placeholder', - } as TextInputProps, - }); + const props: TextInputProps = { + placeholder: 'Test Placeholder', + value: 'test', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('placeholder'))).toBe('Test Placeholder'); + await expect(input).toHaveAttribute('placeholder', 'Test Placeholder'); }); }); test.describe('autoFocus', () => { test('should focus the component when autoFocus is `true`', async ({ page }) => { // Arrange - // Setting the content this way rather than a mount call triggers the autofocus behaviour immediately - await page.setContent(''); + const props: TextInputProps = { + autoFocus: true, + value: '', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - const inputLocator = page.getByTestId('testInput'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - await expect(inputLocator).toBeFocused(); + await expect(input).toBeFocused(); }); test('should not focus the component when autoFocus is not provided', async ({ page }) => { // Arrange - // Setting the content this way rather than a mount call triggers the autofocus behaviour immediately - await page.setContent(''); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const inputLocator = page.getByTestId('testInput'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - await expect(inputLocator).not.toBeFocused(); + await expect(input).not.toBeFocused(); }); }); test.describe('inputmode', () => { - test('should not render an inputmode attribute on the input element if no inputmode provided', async ({ mount }) => { + test('should not render an inputmode attribute on the input element if no inputmode provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('inputmode'))).toBe(null); + await expect(input).not.toHaveAttribute('inputmode'); }); - test('should apply the inputmode prop to the HTML input rendered', async ({ mount }) => { + test('should apply the inputmode prop to the HTML input rendered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - inputmode: 'numeric', - } as TextInputProps, - }); + const props: TextInputProps = { + inputmode: 'numeric', + value: '', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('inputmode'))).toBe('numeric'); + await expect(input).toHaveAttribute('inputmode', 'numeric'); }); }); test.describe('readonly', () => { - test('should be able to edit the component value when readonly is `false`', async ({ mount }) => { + test('should be able to edit the component value when readonly is `false`', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - readonly: false, - value: 'test', - } as TextInputProps, - }); + const props: TextInputProps = { + readonly: false, + value: 'test', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - await component.type('another test'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); + await input.fill('another test'); // Assert - expect((await component.locator('input').inputValue())).toBe('another test'); + await expect(input).toHaveValue('another test'); }); - test('should not be able to edit the component value when readonly is `true`', async ({ mount }) => { + test('should not be able to edit the component value when readonly is `true`', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - readonly: true, - value: 'test', - } as TextInputProps, - }); + const props: TextInputProps = { + readonly: true, + value: 'test', + }; + + const textInputNumberPage = new BasePage(page, 'text-input--default'); + await textInputNumberPage.load({ ...props }); // Act - await component.type('another test'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await component.locator('input').inputValue())).toBe('test'); + await expect(input).not.toBeEditable(); }); }); test.describe('defaultValue', () => { test('should correctly reset the input value to the default value if one is provided when the form is reset', async ({ page }) => { // Arrange - await page.setContent(` -
- - -
- `); + const textInputFormPage = new BasePage(page, 'text-input--example-form'); + await textInputFormPage.load({ defaultValue: 'foo' }); // Act & Assert - await page.locator('pie-text-input').type('test'); - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe('test'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); + await expect(page.getByTestId(textInput.selectors.input.dataTestId)).toHaveValue('test'); await page.click('button[type="reset"]'); - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe('foo'); + await expect(page.getByTestId(textInput.selectors.input.dataTestId)).toHaveValue('foo'); }); }); test.describe('disabled', () => { test.describe('when true', () => { - test('should disable the component', async ({ mount }) => { + test('should disable the component', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - disabled: true, - value: 'test', - } as TextInputProps, - }); + const props: TextInputProps = { + disabled: true, + value: 'test', + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(input).toBeDisabled(); + await expect(input).toBeDisabled(); }); test('should not be able to focus the component', async ({ page }) => { // Arrange - await page.setContent(''); + const props: TextInputProps = { + disabled: true, + value: 'test', + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const input = page.locator('pie-text-input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); await input.focus(); // Assert @@ -472,93 +509,102 @@ test.describe('PieTextInput - Component tests', () => { }); test.describe('when not provided', () => { - test('should not disable the component', async ({ mount }) => { + test('should not disable the component', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(input).not.toBeDisabled(); + await expect(input).not.toBeDisabled(); }); test('should still be able to focus the component', async ({ page }) => { // Arrange - await page.setContent(''); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = page.locator('pie-text-input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); await input.focus(); // Assert - expect(input).toBeFocused(); + await expect(input).toBeFocused(); }); }); }); test.describe('assistiveText', () => { - test('should not render the assistive text component if the prop is not provided', async ({ mount, page }) => { + test('should not render the assistive text component if the prop is not provided', async ({ page }) => { // Arrange - await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const assistiveText = page.locator(assistiveTextSelector); + const assistiveText = page.getByTestId(textInput.selectors.assistiveText.dataTestId); // Assert expect(assistiveText).not.toBeVisible(); }); - test('should apply the "default" variant attribute if no status is provided', async ({ mount, page }) => { + test('should apply the "default" variant attribute if no status is provided', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - assistiveText: 'Assistive text', - } as TextInputProps, - }); + const props: TextInputProps = { + assistiveText: 'Assistive text', + value: 'test', + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const assistiveText = page.locator(assistiveTextSelector); + const assistiveText = page.getByTestId(textInput.selectors.assistiveText.dataTestId); // Assert - expect(assistiveText).toBeVisible(); - expect(await assistiveText.getAttribute('variant')).toBe('default'); - expect(assistiveText).toHaveText('Assistive text'); + await expect(assistiveText).toBeVisible(); + await expect(assistiveText).toHaveAttribute('variant', 'default'); + await expect(assistiveText).toHaveText('Assistive text'); }); test.describe('Assistive text: Status', () => { statusTypes.forEach((status) => { - test(`should render the assistive text component with the ${status} variant`, async ({ mount, page }) => { + test(`should render the assistive text component with the ${status} variant`, async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - assistiveText: 'Assistive text', - status, - } as TextInputProps, - }); + const props: TextInputProps = { + assistiveText: 'Assistive text', + status, + value: 'test', + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const assistiveText = page.locator(assistiveTextSelector); + const assistiveText = page.getByTestId(textInput.selectors.assistiveText.dataTestId); // Assert - expect(assistiveText).toBeVisible(); - expect(assistiveText).toHaveAttribute('variant', status); - expect(assistiveText).toHaveText('Assistive text'); + await expect(assistiveText).toBeVisible(); + await expect(assistiveText).toHaveAttribute('variant', status); + await expect(assistiveText).toHaveText('Assistive text'); }); }); }); test.describe('Assistive test ID attribute', () => { - test('should contain an ID associated the input element for a11y', async ({ mount, page }) => { + test('should contain an ID associated the input element for a11y', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - assistiveText: 'Assistive text', - } as TextInputProps, - }); + const props: TextInputProps = { + assistiveText: 'Assistive text', + value: 'test', + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const assistiveText = page.locator(assistiveTextSelector); + const assistiveText = page.getByTestId(textInput.selectors.assistiveText.dataTestId); // Assert await expect(assistiveText).toHaveAttribute('id', 'assistive-text'); @@ -568,15 +614,16 @@ test.describe('PieTextInput - Component tests', () => { test.describe('step', () => { test.describe('when type is number', () => { - test('should be able to increment the value by the step amount when using the up arrow', async ({ mount, page }) => { + test('should be able to increment the value by the step amount when using the up arrow', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - type: 'number', - value: '0', - step: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'number', + value: '0', + step: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act await page.focus('pie-text-input'); @@ -584,18 +631,20 @@ test.describe('PieTextInput - Component tests', () => { await page.keyboard.press('ArrowUp'); // Assert - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe('10'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); + await expect(input).toHaveValue('10'); }); - test('should be able to decrement the value by the step amount when using the down arrow', async ({ mount, page }) => { + test('should be able to decrement the value by the step amount when using the down arrow', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - type: 'number', - value: '0', - step: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'number', + value: '0', + step: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act await page.focus('pie-text-input'); @@ -603,20 +652,22 @@ test.describe('PieTextInput - Component tests', () => { await page.keyboard.press('ArrowDown'); // Assert - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe('-10'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); + await expect(input).toHaveValue('-10'); }); }); test.describe('when type is not number', () => { - test('should not be able to increment the value by the step amount when using the up arrow', async ({ mount, page }) => { + test('should not be able to increment the value by the step amount when using the up arrow', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - type: 'text', - value: '0', - step: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'text', + value: '0', + step: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act await page.focus('pie-text-input'); @@ -624,18 +675,20 @@ test.describe('PieTextInput - Component tests', () => { await page.keyboard.press('ArrowUp'); // Assert - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe('0'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); + await expect(input).toHaveValue('0'); }); - test('should not be able to decrement the value by the step amount when using the down arrow', async ({ mount, page }) => { + test('should not be able to decrement the value by the step amount when using the down arrow', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - type: 'text', - value: '0', - step: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'text', + value: '0', + step: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act await page.focus('pie-text-input'); @@ -643,45 +696,50 @@ test.describe('PieTextInput - Component tests', () => { await page.keyboard.press('ArrowDown'); // Assert - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe('0'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); + await expect(input).toHaveValue('0'); }); }); }); test.describe('min', () => { - test('should be invalid state `rangeUnderflow` if the value is lower than the min', async ({ mount, page }) => { + test('should be invalid state `rangeUnderflow` if the value is lower than the min', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - type: 'number', - value: '0', - min: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'number', + value: '0', + min: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - await component.type('4'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('4'); - const isInvalid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.rangeUnderflow); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isInvalid = await component.evaluate((element) => (element as HTMLInputElement).validity.rangeUnderflow); // Assert expect(isInvalid).toBe(true); }); - test('should be valid state if the value is greater than the min', async ({ mount, page }) => { + test('should be valid state if the value is greater than the min', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - type: 'number', - value: '0', - min: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'number', + value: '0', + min: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - await component.type('6'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('6'); - const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isValid = await component.evaluate((element) => (element as HTMLInputElement).validity.valid); // Assert expect(isValid).toBe(true); @@ -689,39 +747,43 @@ test.describe('PieTextInput - Component tests', () => { }); test.describe('max', () => { - test('should be invalid state `rangeOverflow` if the value is greater than the max', async ({ mount, page }) => { + test('should be invalid state `rangeOverflow` if the value is greater than the max', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - type: 'number', - value: '0', - max: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'number', + value: '0', + max: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - await component.type('6'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('6'); - const isInvalid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.rangeOverflow); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isInvalid = await component.evaluate((element) => (element as HTMLInputElement).validity.rangeOverflow); // Assert expect(isInvalid).toBe(true); }); - test('should be valid state if the value is lower than the max', async ({ mount, page }) => { + test('should be valid state if the value is lower than the max', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - type: 'number', - value: '0', - max: 5, - } as TextInputProps, - }); + const props: TextInputProps = { + type: 'number', + value: '0', + max: 5, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - await component.type('4'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('4'); - const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isValid = await component.evaluate((element) => (element as HTMLInputElement).validity.valid); // Assert expect(isValid).toBe(true); @@ -729,91 +791,102 @@ test.describe('PieTextInput - Component tests', () => { }); test.describe('required', () => { - test('should not render a required attribute on the input element if no required provided', async ({ mount }) => { + test('should not render a required attribute on the input element if no required provided', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('required'))).toBe(null); + await expect(input).not.toHaveAttribute('required'); }); - test('should apply the required prop to the HTML input rendered', async ({ mount }) => { + test('should apply the required prop to the HTML input rendered', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - required: true, - } as TextInputProps, - }); + const props: TextInputProps = { + required: true, + value: 'test', + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect((await input.getAttribute('required'))).toBe(''); + await expect(input).toHaveAttribute('required'); }); - test('should be invalid state `valueMissing` if the input is empty and required', async ({ mount, page }) => { + test('should be invalid state `valueMissing` if the input is empty and required', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - required: true, - } as TextInputProps, - }); + const props: Partial = { + required: true, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const isInvalid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valueMissing); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isInvalid = await component.evaluate((element) => (element as HTMLInputElement).validity.valueMissing); // Assert expect(isInvalid).toBe(true); }); - test('should be valid state if the input is not empty and required', async ({ mount, page }) => { + test('should be valid state if the input is not empty and required', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - required: true, - } as TextInputProps, - }); + const props: Partial = { + required: true, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - await component.type('test'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); - const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isValid = await component.evaluate((element) => (element as HTMLInputElement).validity.valid); // Assert expect(isValid).toBe(true); }); - test('should be valid state if the input has a value prop and required', async ({ mount, page }) => { + test('should be valid state if the input has a value prop and required', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - required: true, - value: 'test', - } as TextInputProps, - }); + const props: Partial = { + required: true, + value: 'test', + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isValid = await component.evaluate((element) => (element as HTMLInputElement).validity.valid); // Assert expect(isValid).toBe(true); }); - test('should be valid state if the input is empty and required but disabled', async ({ mount, page }) => { + test('should be valid state if the input is empty and required but disabled', async ({ page }) => { // Arrange - await mount(PieTextInput, { - props: { - required: true, - disabled: true, - } as TextInputProps, - }); + const props: Partial = { + required: true, + disabled: true, + }; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load({ ...props }); // Act - const isValid = await page.evaluate(() => document.querySelector('pie-text-input')?.validity.valid); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const isValid = await component.evaluate((element) => (element as HTMLInputElement).validity.valid); // Assert expect(isValid).toBe(true); @@ -823,232 +896,198 @@ test.describe('PieTextInput - Component tests', () => { test.describe('Events', () => { test.describe('input', () => { - test('should emit an event each time the component receives input', async ({ mount, page }) => { + test('should emit an event each time the component receives input', async ({ page }) => { // Arrange - const messages: InputEvent[] = []; - const expectedMessagesLength = 5; - - const component = await mount(PieTextInput, { - on: { - input: (data: InputEvent) => { - messages.push(data); - }, - }, + const expectedMessagesLength = 2; + + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); + + // Set up a listener for console messages + const consoleMessages: string[] = []; + page.on('console', (message) => { + if (message.type() === 'info') { + consoleMessages.push(message.text()); + } }); - const input = component.locator('input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Act - await input.type('test'); + await input.fill('test'); await page.keyboard.press('Backspace'); // Assert - expect(messages.length).toEqual(expectedMessagesLength); + expect(consoleMessages.length).toEqual(expectedMessagesLength); }); test('should provide the event target value for event listeners', async ({ page }) => { // Arrange const expectedMessage = 'tes'; - await page.setContent(` - -
- `); - - await page.evaluate(() => { - const output = (document.getElementById('output') as HTMLDivElement); - const input = document.querySelector('pie-text-input'); - - input?.addEventListener('input', (event: Event) => { - const currentValue = (event.target as HTMLInputElement).value; - output.innerText = currentValue; - }); - }); - - const input = page.locator('pie-text-input'); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - await input.type('test'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); await page.keyboard.press('Backspace'); const output = page.locator('#output'); // Assert - expect(await output.innerText()).toEqual(expectedMessage); + await expect(output).toHaveText(expectedMessage); }); test('should correctly handle input including backspaces in the event.data property', async ({ page }) => { // Arrange const expectedMessage = 'tes'; - await page.setContent(` - -
- `); - - await page.evaluate(() => { - const output = document.getElementById('output') as HTMLDivElement; - const input = document.querySelector('pie-text-input'); - - input?.addEventListener('input', (event) => { - const { data } = event as InputEvent; - const currentValue = (event.target as HTMLInputElement).value; - - // If data is null, it's a deletion, so update the output to match the input's value - if (data === null) { - output.innerText = currentValue; - } else { - // For additions, append the data character - output.innerText += data; - } - }); - }); - - const input = page.locator('pie-text-input'); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - await input.type('test'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); await page.keyboard.press('Backspace'); // Assert - expect(await page.locator('#output').innerText()).toEqual(expectedMessage); + const output = page.locator('#output'); + await expect(output).toHaveText(expectedMessage); }); }); test.describe('change', () => { - test('should dispatch a change event when the input value changes', async ({ mount, page }) => { + test('should dispatch a change event when the input value changes', async ({ page }) => { // Arrange - const messages: CustomEvent[] = []; - - await mount(PieTextInput, { - on: { - change: (data: CustomEvent) => { - messages.push(data); - }, - }, + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); + + // Set up a listener for console messages + const consoleMessages: string[] = []; + page.on('console', (message) => { + if (message.type() === 'info') { + consoleMessages.push(message.text()); + } }); - const input = page.locator('pie-text-input'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Act - await input.type('test'); + await input.fill('test'); await page.keyboard.press('Tab'); // Assert - expect(messages.length).toEqual(1); + expect(consoleMessages).toContain('change event recieved {"isTrusted":false}'); }); - test('should dispatch a custom event that contains the original native event', async ({ mount, page }) => { + test('should dispatch a custom event that contains the original native event', async ({ page }) => { // Arrange - const messages: CustomEvent[] = []; - const expectedMessages = [{ sourceEvent: { isTrusted: true } }]; - - await mount(PieTextInput, { - on: { - change: (data: CustomEvent) => { - messages.push(data); - }, - }, - }); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); + + const consoleMessages: string[] = []; + const input = page.getByTestId(textInput.selectors.input.dataTestId); - const input = page.locator('pie-text-input'); + // Set up a listener for console messages + page.on('console', (message) => { + if (message.type() === 'info') { + consoleMessages.push(message.text()); + } + }); // Act - await input.type('test'); + await input.fill('test'); await page.keyboard.press('Tab'); // Change events on inputs are triggered when they lose focus after the value was changed // Assert - expect(messages).toStrictEqual(expectedMessages); + expect(consoleMessages).toContain('change event recieved {"isTrusted":false}'); }); }); }); test.describe('Slots', () => { test.describe('leadingText', () => { - test('should render the leading slot content', async ({ mount }) => { + test('should render the leading slot content', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - slots: { - leadingText: '', - }, - }); + const textInputLeadingTextPage = new BasePage(page, 'text-input--leading-text'); + await textInputLeadingTextPage.load(); // Act - const leadingSlot = component.locator('#leadingText'); + const leadingText = page.getByTestId('leadingText'); // Assert - expect(leadingSlot).toBeVisible(); + await expect(leadingText).toBeVisible(); }); }); test.describe('trailingText', () => { - test('should render the trailingText slot content', async ({ mount }) => { + test('should render the trailingText slot content', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - slots: { - trailingText: '', - }, - }); + const textInputTrailingTextPage = new BasePage(page, 'text-input--trailing-text'); + await textInputTrailingTextPage.load(); // Act - const trailingSlot = component.locator('#trailingText'); + const trailingText = page.getByTestId('trailingText'); // Assert - expect(trailingSlot).toBeVisible(); + await expect(trailingText).toBeVisible(); }); }); test.describe('leadingIcon', () => { - test('should render the leadingIcon slot content', async ({ mount }) => { + test('should render the leadingIcon slot content', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - slots: { - leadingIcon: '', - }, - }); + const textInputLeadingIconPage = new BasePage(page, 'text-input--leading-icon'); + await textInputLeadingIconPage.load(); // Act - const leadingSlot = component.locator('#leadingIcon'); + const leadingIcon = page.getByTestId('leadingIcon'); // Assert - expect(leadingSlot).toBeVisible(); + await expect(leadingIcon).toBeVisible(); }); }); test.describe('trailingIcon', () => { - test('should render the trailingIcon slot content', async ({ mount }) => { + test('should render the trailingIcon slot content', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - slots: { - trailingIcon: '', - }, - }); + const textInputTrailingIconPage = new BasePage(page, 'text-input--trailing-icon'); + await textInputTrailingIconPage.load(); // Act - const trailingSlot = component.locator('#trailingIcon'); + const trailingIcon = page.getByTestId('trailingIcon'); // Assert - expect(trailingSlot).toBeVisible(); + await expect(trailingIcon).toBeVisible(); }); }); test.describe('leading and trailing', () => { - test('should render both the leading and trailing slot content', async ({ mount }) => { + test('should render both the leading and trailing text content', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - slots: { - leadingIcon: '', - trailingText: '#', - }, - }); + const textInputLeadingAndTrailingTextPage = new BasePage(page, 'text-input--leading-and-trailing-text'); + await textInputLeadingAndTrailingTextPage.load(); + + // Act + const leadingText = page.getByTestId('leadingText'); + const trailingText = page.getByTestId('trailingText'); + + // Assert + await expect(leadingText).toBeVisible(); + await expect(trailingText).toBeVisible(); + }); + + test('should render both the leading and trailing icon content', async ({ page }) => { + // Arrange + const textInputLeadingAndTrailingIconPage = new BasePage(page, 'text-input--leading-and-trailing-icon'); + await textInputLeadingAndTrailingIconPage.load(); // Act - const leadingSlot = component.locator('#leadingIcon'); - const trailingSlot = component.locator('#trailingText'); + const leadingIcon = page.getByTestId('leadingIcon'); + const trailingIcon = page.getByTestId('trailingIcon'); // Assert - expect(leadingSlot).toBeVisible(); - expect(trailingSlot).toBeVisible(); + await expect(leadingIcon).toBeVisible(); + await expect(trailingIcon).toBeVisible(); }); }); }); @@ -1056,191 +1095,166 @@ test.describe('PieTextInput - Component tests', () => { test.describe('Form integration', () => { test('should correctly set the value of username in the FormData object when submitted', async ({ page }) => { // Arrange - await page.setContent(` -
- - -
-
- `); - - await setupFormDataExtraction(page, '#testForm', '#formDataJson'); + const textInputFormPage = new BasePage(page, 'text-input--example-form'); + await textInputFormPage.load(); // Act - await page.locator('pie-text-input').type('test'); - await page.click('button[type="submit"]'); - const formDataObj = await getFormDataObject(page, '#formDataJson'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); + await page.locator('pie-button', { hasText: 'Submit' }).click(); // Assert - expect(formDataObj.username).toBe('test'); + const output = await page.locator('#formDataOutput'); + await expect(output).toHaveText('{"username":"test"}'); }); test('should submit the updated value if the value prop is changed programmatically', async ({ page }) => { // Arrange - await page.setContent(` -
- - -
-
- `); - - await setupFormDataExtraction(page, '#testForm', '#formDataJson'); + const textInputFormPage = new BasePage(page, 'text-input--example-form'); + await textInputFormPage.load(); // Act - await page.locator('pie-text-input').type('test'); + await page.getByTestId(textInput.selectors.input.dataTestId).fill('test'); - await page.evaluate(() => { - const input = document.querySelector('pie-text-input') as PieTextInput; + const component = page.getByTestId(textInput.selectors.container.dataTestId); + await component.evaluate((element) => { + const input = element as HTMLInputElement; input.value = 'test2'; }); - await page.click('button[type="submit"]'); + await page.locator('pie-button', { hasText: 'Submit' }).click(); - const formDataObj = await getFormDataObject(page, '#formDataJson'); + const output = page.locator('#formDataOutput'); // Assert - expect(formDataObj.username).toBe('test2'); + await expect(output).toHaveText('{"username":"test2"}'); }); test('should correctly reset the input value when the form is reset', async ({ page }) => { // Arrange - await page.setContent(` -
- - -
- `); + const textInputFormPage = new BasePage(page, 'text-input--example-form'); + await textInputFormPage.load(); // Act & Assert - await page.locator('pie-text-input').type('test'); - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe('test'); + const component = page.getByTestId(textInput.selectors.container.dataTestId); + const input = page.getByTestId(textInput.selectors.input.dataTestId); + await input.fill('test'); + + await component.evaluate((element) => { + const input = element as HTMLInputElement; + input.value = 'test'; + }); + + await expect(input).toHaveValue('test'); - await page.click('button[type="reset"]'); - expect(await page.evaluate(() => document.querySelector('pie-text-input')?.value)).toBe(''); + await page.locator('pie-button', { hasText: 'Reset' }).click(); + await expect(input).toHaveValue(''); }); test('should not submit the value for disabled inputs', async ({ page }) => { // Arrange - await page.setContent(` -
- - - -
-
- `); + const props: Partial = { + // Configured to only disable the username field + disabled: true, + showEmailField: true, + }; - await setupFormDataExtraction(page, '#testForm', '#formDataJson'); + const textInputFormPage = new BasePage(page, 'text-input--example-form'); + await textInputFormPage.load({ ...props }); // Act - await page.click('button[type="submit"]'); + await page.locator('#email input').fill('test@test.com'); + await page.locator('pie-button', { hasText: 'Submit' }).click(); - const formDataObj = await getFormDataObject(page, '#formDataJson'); + const output = page.locator('#formDataOutput'); // Assert - expect(formDataObj).toStrictEqual({ email: 'test@test.com' }); + await expect(output).toHaveText('{"email":"test@test.com"}'); }); test('should not submit the value inside a disabled fieldset', async ({ page }) => { // Arrange - await page.setContent(` -
-
- -
- - -
-
- `); - - await setupFormDataExtraction(page, '#testForm', '#formDataJson'); + const textInputDisabledFieldsetPage = new BasePage(page, 'text-input--disabled-fieldset'); + await textInputDisabledFieldsetPage.load(); // Act - await page.click('button[type="submit"]'); + await page.locator('pie-button', { hasText: 'Submit' }).click(); - const formDataObj = await getFormDataObject(page, '#formDataJson'); + const output = page.locator('#formDataOutput'); // Assert - expect(formDataObj).toStrictEqual({ - email: 'included@test.com', - }); + await expect(output).toHaveText('{"email":"included@test.com"}'); }); }); test.describe('Attributes:', () => { test.describe('aria-describedby', () => { test.describe('when `assistiveText` is not defined', () => { - test('should not render the attribute', async ({ mount }) => { + test('should not render the attribute', async ({ page }) => { // Arrange - const component = await mount(PieTextInput); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(); // Act - const input = component.locator('input'); - - const componentAttribute = await input.getAttribute('aria-describedby'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(componentAttribute).toBeNull(); + await expect(input).not.toHaveAttribute('aria-describedby'); }); }); test.describe('when `assistiveText` is defined', () => { - test('should render the attribute correctly with the correct value', async ({ mount }) => { + test('should render the attribute correctly with the correct value', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - assistiveText: 'Some useful message', - } as TextInputProps, - }); + const props: Partial = { + assistiveText: 'Some useful message', + }; + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(props); // Act - const input = component.locator('input'); - - const componentAttribute = await input.getAttribute('aria-describedby'); + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(componentAttribute).toBe('assistive-text'); + await expect(input).toHaveAttribute('aria-describedby', 'assistive-text'); }); }); }); test.describe('aria-invalid', () => { test.describe('when the component status is set to `error`', () => { - test('should render the `aria-invalid` attribute', async ({ mount }) => { + test('should render the `aria-invalid` attribute', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - status: 'error', - } as TextInputProps, - }); + const props: Partial = { + status: 'error', + }; - // Act - const input = component.locator('input'); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(props); - const componentAttribute = await input.getAttribute('aria-invalid'); + // Act + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(componentAttribute).toBeDefined(); + await expect(input).toHaveAttribute('aria-invalid', 'true'); }); }); statusTypes.filter((status) => status !== 'error').forEach((status) => { test.describe(`when the component status is set to "${status}"`, () => { - test('should render the `aria-invalid` with a value of `false`', async ({ mount }) => { + test('should render the `aria-invalid` with a value of `false`', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { status } as TextInputProps, - }); + const props: Partial = { + status, + }; - // Act - const input = component.locator('input'); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(props); - const componentAttribute = await input.getAttribute('aria-invalid'); + // Act + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(componentAttribute).toBe('false'); + await expect(input).toHaveAttribute('aria-invalid', 'false'); }); }); }); @@ -1248,39 +1262,39 @@ test.describe('PieTextInput - Component tests', () => { test.describe('aria-errormessage', () => { test.describe('when the component status is set to `error`', () => { - test('should render the `aria-errormessage` attribute', async ({ mount }) => { + test('should render the `aria-errormessage` attribute', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { - status: 'error', - } as TextInputProps, - }); + const props: Partial = { + status: 'error', + }; - // Act - const input = component.locator('input'); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(props); - const componentAttribute = await input.getAttribute('aria-errormessage'); + // Act + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(componentAttribute).toBeDefined(); + await expect(input).toHaveAttribute('aria-errormessage'); }); }); statusTypes.filter((status) => status !== 'error').forEach((status) => { test.describe(`when the component status is set to "${status}"`, () => { - test('should not render the `aria-errormessage` attribute', async ({ mount }) => { + test('should not render the `aria-errormessage` attribute', async ({ page }) => { // Arrange - const component = await mount(PieTextInput, { - props: { status } as TextInputProps, - }); + const props: Partial = { + status, + }; - // Act - const input = component.locator('input'); + const textInputDefaultPage = new BasePage(page, 'text-input--default'); + await textInputDefaultPage.load(props); - const componentAttribute = await input.getAttribute('aria-errormessage'); + // Act + const input = page.getByTestId(textInput.selectors.input.dataTestId); // Assert - expect(componentAttribute).toBeNull(); + await expect(input).not.toHaveAttribute('aria-errormessage'); }); }); }); diff --git a/packages/components/pie-text-input/test/helpers/page-object/selectors.ts b/packages/components/pie-text-input/test/helpers/page-object/selectors.ts new file mode 100644 index 0000000000..fc875834ae --- /dev/null +++ b/packages/components/pie-text-input/test/helpers/page-object/selectors.ts @@ -0,0 +1,16 @@ +const textInput = { + selectors: { + container: { + dataTestId: 'pie-text-input-container', + }, + assistiveText: { + dataTestId: 'pie-text-input-assistive-text', + }, + input: { + dataTestId: 'pie-text-input', + }, + }, +}; +export { + textInput, +}; diff --git a/packages/components/pie-text-input/test/visual/pie-text-input.spec.ts b/packages/components/pie-text-input/test/visual/pie-text-input.spec.ts index c9f5a6eadc..6843a28d12 100644 --- a/packages/components/pie-text-input/test/visual/pie-text-input.spec.ts +++ b/packages/components/pie-text-input/test/visual/pie-text-input.spec.ts @@ -1,454 +1,28 @@ -import { test } from '@sand4rt/experimental-ct-web'; +import { test } from '@playwright/test'; import percySnapshot from '@percy/playwright'; +import { BasePage } from '@justeattakeaway/pie-webc-testing/src/helpers/page-object/base-page.ts'; +import { statusTypes } from '../../src/defs.ts'; -import { type WebComponentPropValues } from '@justeattakeaway/pie-webc-testing/src/helpers/defs.ts'; -import { createTestWebComponent } from '@justeattakeaway/pie-webc-testing/src/helpers/rendering.ts'; -import { WebComponentTestWrapper } from '@justeattakeaway/pie-webc-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts'; -import { percyWidths } from '@justeattakeaway/pie-webc-testing/src/percy/breakpoints.ts'; -import { IconPlaceholder } from '@justeattakeaway/pie-icons-webc/dist/IconPlaceholder'; -import { PieAssistiveText } from '@justeattakeaway/pie-assistive-text'; -import { setRTL } from '@justeattakeaway/pie-webc-testing/src/helpers/set-rtl-direction.ts'; +const readingDirections = ['ltr', 'rtl']; -import { PieTextInput } from '../../src/index.ts'; +readingDirections.forEach(async (dir) => { + statusTypes.forEach(async (status) => { + test(`Status - ${status} - ${dir}`, async ({ page }) => { + const textInputVariations = new BasePage(page, `text-input--${status}-variations`); + await textInputVariations.load({}, { writingDirection: dir }); + await page.waitForSelector('pie-text-input'); -// Renders a HTML string with the given prop values -type TextInputSlotOptions = { - leadingIcon?: boolean; - trailingIcon?: boolean; - leadingCharacter?: boolean; - trailingCharacter?: boolean; -}; - -const renderTestPieTextInput = (propVals: WebComponentPropValues) => { - let attributes = ''; - - if (propVals.type) attributes += ` type="${propVals.type}"`; - if (propVals.size) attributes += ` size="${propVals.size}"`; - if (propVals.placeholder) attributes += ` placeholder="${propVals.placeholder}"`; - if (propVals.value) attributes += ` value="${propVals.value}"`; - if (propVals.status) attributes += ` status="${propVals.status}"`; - if (propVals.assistiveText) attributes += ` assistiveText="${propVals.assistiveText}"`; - if (propVals.disabled) attributes += ' disabled'; - if (propVals.readonly) attributes += ' readonly'; - - return ``; -}; - -// Adds any slots to the component HTML string -const addSlotsToComponent = (component: string, slotOptions: TextInputSlotOptions) => { - let slots = ''; - - if (slotOptions.leadingIcon) slots += ''; - if (slotOptions.trailingIcon) slots += ''; - if (slotOptions.leadingCharacter) slots += '#'; - if (slotOptions.trailingCharacter) slots += '#'; - - // add slots between > and - return component.replace('>', `>${slots}`); -}; - -test.beforeEach(async ({ mount }, testInfo) => { - testInfo.setTimeout(testInfo.timeout + 40000); - - // This ensures the input component is registered in the DOM for each test. - // It appears to add them to a Playwright cache which we understand is required for the tests to work correctly. - const textInputComponent = await mount(PieTextInput); - await textInputComponent.unmount(); - - const iconComponent = await mount(IconPlaceholder); - await iconComponent.unmount(); - - const assistiveTextComponent = await mount(PieAssistiveText); - await assistiveTextComponent.unmount(); -}); - -test('Size variants with value and placeholder', async ({ mount, page }) => { - const sizeVariants = ['small', 'medium', 'large']; - const value = 'String'; - const placeholder = 'Placeholder'; - - for (const size of sizeVariants) { - let testComponent = createTestWebComponent({ size, value }, renderTestPieTextInput); - let propKeyValues = `size: ${testComponent.propValues.size}, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - testComponent = createTestWebComponent({ size, placeholder }, renderTestPieTextInput); - propKeyValues = `size: ${testComponent.propValues.size}, placeholder: ${testComponent.propValues.placeholder}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - } - - await percySnapshot(page, 'PIE Text Input - Size variants with value and placeholder', percyWidths); -}); - -const readingDirections = ['LTR', 'RTL']; - -for (const dir of readingDirections) { - test(`Assistive text and statuses - ${dir}`, async ({ mount, page }) => { - if (dir === 'RTL') { - setRTL(page); - } - - // Assistive text with no status - let testComponent = createTestWebComponent({ assistiveText: 'Assistive text', value: 'String' }, renderTestPieTextInput); - let propKeyValues = `assistiveText: ${testComponent.propValues.value}, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Error + assistive text - testComponent = createTestWebComponent({ assistiveText: 'Error text', value: 'String', status: 'error' }, renderTestPieTextInput); - propKeyValues = `assistiveText: ${testComponent.propValues.assistiveText}, value: ${testComponent.propValues.value}, status: ${testComponent.propValues.status}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Error and no assistive text - testComponent = createTestWebComponent({ value: 'String', status: 'error' }, renderTestPieTextInput); - propKeyValues = `value: ${testComponent.propValues.value}, status: ${testComponent.propValues.status}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Success + assistive text - testComponent = createTestWebComponent({ assistiveText: 'Success text', value: 'String', status: 'success' }, renderTestPieTextInput); - propKeyValues = `assistiveText: ${testComponent.propValues.assistiveText}, value: ${testComponent.propValues.value}, status: ${testComponent.propValues.status}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - await percySnapshot(page, `PIE Text Input - Assistive text and statuses - ${dir}`, percyWidths); + await percySnapshot(page, `PIE Text Input - statusType - ${status} - ${dir}`, { widths: [1280] }); + }); }); -} - -for (const dir of readingDirections) { - test(`Content and slots - ${dir}`, async ({ mount, page }) => { - if (dir === 'RTL') { - setRTL(page); - } - - // Trailing + leading icon - let testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - let componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingIcon: true, trailingIcon: true }); - - let propKeyValues = `slots: Trailing + Leading Icon, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Leading icon - testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingIcon: true }); - - propKeyValues = `slots: Leading Icon, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Trailing icon - testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { trailingIcon: true }); - - propKeyValues = `slots: Trailing Icon, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Leading + Trailing text - testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingCharacter: true, trailingCharacter: true }); - propKeyValues = `slots: Leading + Trailing Text, value: ${testComponent.propValues.value}`; + test(`Content and slots - ${dir}`, async ({ page }) => { + // Arrange + const textInputVariations = new BasePage(page, 'text-input--slot-variations'); + await textInputVariations.load({}, { writingDirection: dir }); + await page.waitForSelector('pie-text-input'); - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Leading text - testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingCharacter: true }); - - propKeyValues = `slots: Leading Text, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Trailing text - testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { trailingCharacter: true }); - - propKeyValues = `slots: Trailing Text, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Leading Icon + Trailing text - testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingIcon: true, trailingCharacter: true }); - - propKeyValues = `slots: Leading Icon + Trailing Text, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Leading text + Trailing icon - testComponent = createTestWebComponent({ value: 'String' }, renderTestPieTextInput); - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingCharacter: true, trailingIcon: true }); - - propKeyValues = `slots: Leading Text + Trailing Icon, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Disabled + Leading Icon + Trailing text - testComponent = createTestWebComponent({ - value: 'String', - disabled: true, - }, renderTestPieTextInput); - - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingIcon: true, trailingCharacter: true }); - - propKeyValues = `slots: Leading Icon + Trailing Text, disabled: ${testComponent.propValues.disabled}, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Disabled placeholder - testComponent = createTestWebComponent({ - disabled: true, - placeholder: 'Placeholder', - }, renderTestPieTextInput); - - propKeyValues = `disabled: ${testComponent.propValues.disabled}, placeholder: ${testComponent.propValues.placeholder}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Readonly + Leading Icon + Trailing text - testComponent = createTestWebComponent({ - value: 'String', - readonly: true, - }, renderTestPieTextInput); - - componentStringWithSlots = addSlotsToComponent(testComponent.renderedString, { leadingIcon: true, trailingCharacter: true }); - - propKeyValues = `slots: Leading Icon + Trailing Text, readonly: ${testComponent.propValues.readonly}, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: componentStringWithSlots.trim(), - }, - }, - ); - - // Readonly placeholder - testComponent = createTestWebComponent({ - readonly: true, - placeholder: 'Placeholder', - }, renderTestPieTextInput); - - propKeyValues = `readonly: ${testComponent.propValues.readonly}, placeholder: ${testComponent.propValues.placeholder}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Disabled and readonly + value - testComponent = createTestWebComponent({ - disabled: true, - readonly: true, - value: 'String', - }, renderTestPieTextInput); - - propKeyValues = `disabled: ${testComponent.propValues.disabled}, readonly: ${testComponent.propValues.readonly}, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Disabled and readonly + placeholder - testComponent = createTestWebComponent({ - disabled: true, - readonly: true, - placeholder: 'Placeholder', - }, renderTestPieTextInput); - - propKeyValues = `disabled: ${testComponent.propValues.disabled}, readonly: ${testComponent.propValues.readonly}, placeholder: ${testComponent.propValues.placeholder}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Password - testComponent = createTestWebComponent({ - type: 'password', - value: 'password', - }, renderTestPieTextInput); - - propKeyValues = `type: ${testComponent.propValues.type}, value: ${testComponent.propValues.value}`; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - // Long content - testComponent = createTestWebComponent({ - value: 'this is a wonderfully long string that should definitely be longer than the width of the input. If not, here is some extra text to really push it over the edge. This is a test to see how the input handles long strings.', - }, renderTestPieTextInput); - - propKeyValues = 'value: SUPER LONG STRING'; - - await mount( - WebComponentTestWrapper, - { - props: { propKeyValues }, - slots: { - component: testComponent.renderedString.trim(), - }, - }, - ); - - await percySnapshot(page, `PIE Text Input - Content and Slots - ${dir}`, percyWidths); + // Assert + await percySnapshot(page, `PIE Text Input - Content and Slots - ${dir}`, { widths: [1280] }); }); -} +}); diff --git a/packages/components/pie-text-input/turbo.json b/packages/components/pie-text-input/turbo.json new file mode 100644 index 0000000000..de3bf55858 --- /dev/null +++ b/packages/components/pie-text-input/turbo.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://turborepo.org/schema.json", + "extends": [ + "//" + ], + "pipeline": { + "test:browsers": { + "cache": true, + "dependsOn": [], + "inputs": [ + "$TURBO_DEFAULT$", + "../../../apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts" + ] + }, + "test:browsers:ci": { + "cache": true, + "dependsOn": [], + "inputs": [ + "$TURBO_DEFAULT$", + "../../../apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts" + ] + }, + "test:visual": { + "cache": false, + "dependsOn": [], + "inputs": [ + "$TURBO_DEFAULT$", + "../../../apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts" + ] + }, + "test:visual:ci": { + "cache": false, + "dependsOn": [], + "inputs": [ + "$TURBO_DEFAULT$", + "../../../apps/pie-storybook/stories/testing/pie-text-input.test.stories.ts" + ] + } + } +} \ No newline at end of file