From 1fe9ac353917a8059ce24b14e8fee637bfa6aa7b Mon Sep 17 00:00:00 2001 From: "tom.davies" Date: Tue, 19 Sep 2023 11:25:08 +0100 Subject: [PATCH] test(textbox): migrate cypress tests to playwright --- cypress/components/textbox/textbox.cy.tsx | 984 --------------- playwright/components/index.ts | 13 + playwright/components/locators.ts | 2 +- playwright/components/textbox/index.ts | 2 +- src/components/textbox/components.test-pw.tsx | 224 ++++ .../textbox/textbox-test.stories.tsx | 43 +- src/components/textbox/textbox.pw.tsx | 1110 +++++++++++++++++ 7 files changed, 1350 insertions(+), 1028 deletions(-) delete mode 100644 cypress/components/textbox/textbox.cy.tsx create mode 100644 src/components/textbox/components.test-pw.tsx create mode 100644 src/components/textbox/textbox.pw.tsx diff --git a/cypress/components/textbox/textbox.cy.tsx b/cypress/components/textbox/textbox.cy.tsx deleted file mode 100644 index 61e98c0f72..0000000000 --- a/cypress/components/textbox/textbox.cy.tsx +++ /dev/null @@ -1,984 +0,0 @@ -/* eslint-disable no-unused-expressions, jest/valid-expect */ -import React from "react"; -import { TextboxProps } from "components/textbox"; -import * as stories from "../../../src/components/textbox/textbox-test.stories"; -import * as defaultStories from "../../../src/components/textbox/textbox.stories"; -import Box from "../../../src/components/box"; -import CypressMountWithProviders from "../../support/component-helper/cypress-mount"; -import { - verifyRequiredAsteriskForLabel, - checkGoldenOutline, - assertCssValueIsApproximately, -} from "../../support/component-helper/common-steps"; - -import { - getDataElementByValue, - tooltipPreview, - characterCount, - body, - getComponent, - cyRoot, - fieldHelpPreview, - getElement, -} from "../../locators"; -import { - CHARACTERS, - SIZE, - VALIDATION, -} from "../../support/component-helper/constants"; - -import { - textbox, - textboxInput, - textboxPrefix, - visuallyHiddenCharacterCount, - visuallyHiddenHint, -} from "../../locators/textbox"; - -import { keyCode } from "../../support/helper"; -import Button from "../../../src/components/button"; -import { ICON } from "../../locators/locators"; - -const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS]; -const keysToTrigger = ["Enter", "Space"] as const; - -const verifyOptional = (element: Cypress.Chainable>) => - element.then(($els) => { - // get Window reference from element - const win = $els[0].ownerDocument.defaultView; - // use getComputedStyle to read the pseudo selector - const after = win?.getComputedStyle($els[0], "after"); - // read the value of the `content` CSS property - const contentValue = after?.getPropertyValue("content"); - // the returned value will have double quotes around it, but this is correct - expect(contentValue).to.eq('"(optional)"'); - }); - -context("Tests for Textbox component", () => { - describe("check props for Textbox component", () => { - it.each([ - [SIZE.SMALL, "32px", "--sizing400"], - [SIZE.MEDIUM, "40px", "--sizing500"], - [SIZE.LARGE, "48px", "--sizing600"], - ] as [TextboxProps["size"], string, string][])( - "should use %s as size and render Textbox with %s as height", - (size, height, token) => { - CypressMountWithProviders(); - - textbox().should("have.css", "min-height", height); - textbox() - .getDesignTokensByCssProperty("min-height") - .should(($el) => { - expect($el[0]).to.equal(token); - }); - } - ); - - it.each([ - ["background", "--colorsUtilityYang100"], - ["border", "--colorsUtilityMajor300"], - ])("should check %s token for Textbox", (cssProp, token) => { - CypressMountWithProviders(); - - textbox() - .getDesignTokensByCssProperty(cssProp) - .should(($el) => { - expect($el[0]).to.equal(token); - }); - }); - - it("should render Textbox with isOptional prop", () => { - CypressMountWithProviders(); - - verifyOptional(getDataElementByValue("label").parent()); - }); - - it("should render Textbox with data-component prop", () => { - CypressMountWithProviders( - - ); - - getComponent(CHARACTERS.STANDARD).should("be.visible"); - }); - - it("should render Textbox with data-element prop", () => { - CypressMountWithProviders( - - ); - cyRoot() - .children() - .children() - .should("have.attr", "data-element", CHARACTERS.STANDARD); - }); - - it("should render Textbox with data-role prop", () => { - CypressMountWithProviders( - - ); - - cyRoot() - .children() - .children() - .should("have.attr", "data-role", CHARACTERS.STANDARD); - }); - - it("should render Textbox with id prop", () => { - CypressMountWithProviders( - - ); - - textboxInput().should("have.attr", "id", CHARACTERS.STANDARD); - }); - - it.each(testData)( - "should render Textbox with label prop set to %s", - (label) => { - CypressMountWithProviders(); - - getDataElementByValue("label").should("have.text", label); - } - ); - - it("should render Textbox with labelInline prop", () => { - CypressMountWithProviders(); - - getDataElementByValue("label") - .parent() - .should("have.css", "justify-content", "flex-end"); - }); - - it.each([ - ["left", "start"], - ["right", "end"], - ] as [TextboxProps["labelAlign"], string][])( - "should render Textbox with labelAlign prop set to %s", - (labelAlign, cssValue) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("label") - .parent() - .should(($element) => - expect($element).to.have.css("justify-content", `flex-${cssValue}`) - ); - } - ); - - it.each(testData)( - "should render Textbox with labelHelp prop", - (labelHelp) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("question").trigger("mouseover"); - tooltipPreview().should("have.text", labelHelp); - } - ); - - it.each([ - [1, "8px"], - [2, "16px"], - ] as [TextboxProps["labelSpacing"], string][])( - "should render Textbox with labelSpacing prop set to %s", - (spacing, padding) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("label") - .parent() - .should("have.css", "padding-right", padding); - } - ); - - it.each([ - [10, 90, 135, 1229], - [30, 70, 409, 956], - [80, 20, 1092, 273], - ] as [TextboxProps["labelWidth"], TextboxProps["inputWidth"], number, number][])( - "should use %s as labelWidth, %s as inputWidth and render it with correct label and input width ratios", - (label, input, labelRatio, inputRatio) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("label") - .parent() - .then(($el) => { - assertCssValueIsApproximately($el, "width", labelRatio); - }); - - getDataElementByValue("input") - .parent() - .then(($el) => { - assertCssValueIsApproximately($el, "width", inputRatio); - }); - } - ); - - it.each([ - ["foo", "exist"], - ["", "not.exist"], - ])( - "input hint should be conditionally rendered", - (inputHint, renderStatus) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("input-hint").should(renderStatus); - } - ); - - it.each([ - [5, "exist"], - [null, "not.exist"], - ] as [TextboxProps["characterLimit"], string][])( - "character counter should be conditionally rendered", - (characterLimit, renderStatus) => { - CypressMountWithProviders( - - ); - - characterCount().should(renderStatus); - } - ); - - it.each([ - [5, "exist"], - [null, "not.exist"], - ] as [TextboxProps["characterLimit"], string][])( - "visually hidden character count should be conditionally rendered", - (characterLimit, renderStatus) => { - CypressMountWithProviders( - - ); - - visuallyHiddenCharacterCount().should(renderStatus); - } - ); - - it.each([ - [5, "exist"], - [null, "not.exist"], - ] as [TextboxProps["characterLimit"], string][])( - "visually hidden hint should be conditionally rendered", - (characterLimit, renderStatus) => { - CypressMountWithProviders( - - ); - - visuallyHiddenHint().should(renderStatus); - } - ); - - it.each(["10%", "30%", "50%", "80%", "100%"] as TextboxProps["maxWidth"][])( - "should check maxWidth as %s for TextBox component", - (maxWidth) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("input") - .parent() - .parent() - .should("have.css", "max-width", maxWidth); - } - ); - - it("when maxWidth has no value it should render as 100%", () => { - CypressMountWithProviders(); - - getDataElementByValue("input") - .parent() - .parent() - .should("have.css", "max-width", "100%"); - }); - - it("should render Textbox with required prop", () => { - CypressMountWithProviders(); - - verifyRequiredAsteriskForLabel(); - }); - - it.each([ - [11, 11, "rgba(0, 0, 0, 0.55)"], - [11, 10, "rgb(203, 55, 74)"], - ])( - "should input %s characters and warn if over character limit of %s in Textbox", - (charactersUsed, limit, color) => { - const inputValue = "12345678901"; - const underCharacters = - limit - charactersUsed === 1 ? "character" : "characters"; - const overCharacters = - charactersUsed - limit === 1 ? "character" : "characters"; - - CypressMountWithProviders( - - ); - - textboxInput() - .type(inputValue) - .then(() => { - characterCount() - .should( - "have.text", - `${ - charactersUsed - limit - ? `${charactersUsed - limit} ${overCharacters} too many` - : `${charactersUsed - limit} ${underCharacters} left` - }` - ) - .and("have.css", "color", color); - - visuallyHiddenCharacterCount().should( - "have.text", - `${ - charactersUsed - limit - ? `${charactersUsed - limit} ${overCharacters} too many` - : `${charactersUsed - limit} ${underCharacters} left` - }` - ); - }); - } - ); - - it("should render Textbox with name prop", () => { - CypressMountWithProviders( - - ); - - textboxInput().should("have.attr", "name", CHARACTERS.STANDARD); - }); - - it("should render Textbox with disabled prop", () => { - CypressMountWithProviders(); - - textboxInput().should("be.disabled").and("have.attr", "disabled"); - }); - - it("should render Textbox icon with disabled style", () => { - CypressMountWithProviders( - - ); - - textbox() - .find(ICON) - .should("be.visible") - .and("have.css", "color", "rgba(0, 0, 0, 0.3)"); - }); - - it.each(testData)( - "should render Textbox with placeholder prop set to %s", - (placeholder) => { - CypressMountWithProviders( - - ); - - textboxInput().should("have.attr", "placeholder", placeholder); - } - ); - - it("should render Textbox with autoFocus prop and correct styling when focusRedesignOptOut is false", () => { - CypressMountWithProviders( - , - undefined, - undefined, - { - focusRedesignOptOut: false, - } - ); - - body().tab(); - textboxInput() - .should("be.focused") - .parent() - .should( - "have.css", - "box-shadow", - "rgb(255, 188, 25) 0px 0px 0px 3px, rgba(0, 0, 0, 0.9) 0px 0px 0px 6px" - ) - .and("have.css", "outline", "rgba(0, 0, 0, 0) solid 3px"); - }); - - it("should render Textbox with autoFocus prop and correct styling when focusRedesignOptOut is true", () => { - CypressMountWithProviders( - , - undefined, - undefined, - { - focusRedesignOptOut: true, - } - ); - - body().tab(); - textboxInput() - .should("be.focused") - .then(($el) => { - checkGoldenOutline($el.parent()); - }); - }); - - it("should render Textbox with readOnly prop", () => { - CypressMountWithProviders(); - - textboxInput().and("have.attr", "readOnly"); - }); - - it("should render Textbox icon with readOnly style", () => { - CypressMountWithProviders( - - ); - - textbox() - .find(ICON) - .should("be.visible") - .and("have.css", "color", "rgba(0, 0, 0, 0.3)"); - }); - - it.each(["error", "warning", "info"])( - "should verify Textbox is displayed with %s validation icon on input", - (type) => { - CypressMountWithProviders( - - ); - - textbox().find(ICON).should("have.attr", "data-element", type); - } - ); - - it.each(["error", "warning", "info"])( - "should verify Textbox is displayed with %s validation icon on label", - (type) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("label") - .parent() - .find(ICON) - .should("have.attr", "data-element", type); - } - ); - - it.each([ - [VALIDATION.ERROR, "error", true], - [VALIDATION.WARNING, "warning", true], - [VALIDATION.INFO, "info", true], - ["rgb(102, 132, 148)", "error", false], - ["rgb(102, 132, 148)", "warning", false], - ["rgb(102, 132, 148)", "info", false], - ])( - "should verify Textbox input border colour is %s when validation is %s and boolean prop is %s", - (borderColor, type, bool) => { - CypressMountWithProviders( - - ); - - textbox().should("have.css", "border-bottom-color", borderColor); - } - ); - - it("should render Textbox with leftChildren prop", () => { - CypressMountWithProviders( - Test} /> - ); - - textbox() - .children() - .should("have.attr", "data-component", "button") - .and("be.visible"); - }); - - it.each(testData)( - "should render Textbox with prefix prop set to %s", - (prefix) => { - CypressMountWithProviders(); - - textboxPrefix() - .should("have.text", prefix) - .and("have.css", "font-size", "14px") - .and("have.css", "font-weight", "900") - .and("have.css", "margin-left", "12px"); - } - ); - - it("when prefix prop is set, and align is set to 'right', 'flex-direction' should be 'row'", () => { - CypressMountWithProviders( - - ); - - getDataElementByValue("input") - .parent() - .should("have.css", "flex-direction", "row"); - }); - - it.each([ - [true, "input", "label"], - [false, "label", "input"], - ])( - "should render Textbox with reverse prop set to %s", - (boolean, firstElement, secondElement) => { - CypressMountWithProviders( - - ); - - getDataElementByValue("label").parent().parent().as("startPoint"); - - cy.get("@startPoint") - .children() - .eq(0) - .find(firstElement) - .should("be.visible"); - cy.get("@startPoint") - .children() - .eq(1) - .find(secondElement) - .should("be.visible"); - } - ); - - it("should render Textbox with positionedChildren prop", () => { - CypressMountWithProviders( - Test} /> - ); - - textbox() - .parent() - .children() - .should("have.attr", "data-component", "button") - .and("be.visible"); - }); - - it.each([ - ["flex", 399], - ["flex", 400], - ["block", 401], - ] as [string, TextboxProps["adaptiveLabelBreakpoint"]][])( - "should check Textbox label alignment is %s with adaptiveLabelBreakpoint %s and viewport 400", - (displayValue, breakpoint) => { - cy.viewport(400, 300); - - CypressMountWithProviders( - - ); - - getDataElementByValue("label") - .parent() - .parent() - .should("have.css", "display", displayValue); - } - ); - - it("should render Textbox with labelId prop", () => { - CypressMountWithProviders( - - ); - - getDataElementByValue("label") - .should("have.attr", "id", `${CHARACTERS.STANDARD}_cy-label`) - .and("have.attr", "for", `${CHARACTERS.STANDARD}_cy`); - }); - - it.each(testData)( - "should render Textbox with fieldHelp prop set to %s", - (fieldHelp) => { - CypressMountWithProviders( - - ); - - fieldHelpPreview().should("have.text", fieldHelp); - } - ); - - it("should render Textbox with formattedValue prop", () => { - CypressMountWithProviders( - - ); - - textboxInput().should("have.attr", "value", CHARACTERS.STANDARD); - }); - - it.each(["add", "filter", "play"] as TextboxProps["inputIcon"][])( - "should render Textbox with inputIcon prop set to %s", - (icon) => { - CypressMountWithProviders( - - ); - - getElement(icon).and("be.visible"); - } - ); - - it("should render Textbox with iconTabIndex prop", () => { - CypressMountWithProviders( - - ); - - getDataElementByValue("add").parent().should("have.attr", "tabindex", 25); - }); - - it.each([ - "top", - "bottom", - "left", - "right", - ] as TextboxProps["tooltipPosition"][])( - "should render Textbox component with tooltip positioned to the %s", - (position) => { - CypressMountWithProviders( - - - - ); - getDataElementByValue("error").trigger("mouseover"); - tooltipPreview() - .should("have.text", CHARACTERS.STANDARD) - .should("have.attr", "data-placement", position); - } - ); - - it("should render Textbox with helpAriaLabel prop", () => { - CypressMountWithProviders( - - ); - - getComponent("help").should( - "have.attr", - "aria-label", - CHARACTERS.STANDARD - ); - }); - - it.each(["left", "right"] as TextboxProps["align"][])( - "should render Textbox with align prop set to %s", - (align) => { - CypressMountWithProviders(); - - textboxInput().should("have.css", "text-align", align); - } - ); - - it("should render Textbox with inputRef prop and focus the input via click on ref component", () => { - CypressMountWithProviders(); - - getComponent("button").click(); - textboxInput().should("be.focused"); - }); - - it.each(testData)( - "should input into Textbox and verify the value", - (input) => { - CypressMountWithProviders(); - - textboxInput().type(input).should("have.attr", "value", input); - } - ); - }); - - describe("check events for Textbox component", () => { - const inputValue = "1"; - - it("should call onChange callback when a type event is triggered", () => { - const callback: TextboxProps["onChange"] = cy.stub(); - - CypressMountWithProviders( - - ); - - textboxInput() - .type(inputValue) - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - }); - - it("should call onBlur callback when a blur event is triggered", () => { - const callback: TextboxProps["onBlur"] = cy.stub(); - - CypressMountWithProviders(); - - textboxInput() - .click() - .blur() - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - }); - - it("should call onClick callback when a click event is triggered", () => { - const callback: TextboxProps["onClick"] = cy.stub(); - - CypressMountWithProviders( - - ); - - textboxInput() - .click() - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - }); - - it("should call onFocus callback when a focus event is triggered", () => { - const callback: TextboxProps["onFocus"] = cy.stub(); - - CypressMountWithProviders( - - ); - - textboxInput() - .focus() - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - }); - - it("should call onMouseDown callback when a mousedown event is triggered", () => { - const callback: TextboxProps["onMouseDown"] = cy.stub(); - - CypressMountWithProviders( - - ); - - textboxInput() - .trigger("mousedown") - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - }); - - it("should call iconOnMouseDown callback when a click event is triggered", () => { - const callback: TextboxProps["iconOnMouseDown"] = cy.stub(); - - CypressMountWithProviders( - - ); - - getComponent("icon") - .click() - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - }); - - it("should call iconOnClick callback when a click event is triggered", () => { - const callback: TextboxProps["iconOnClick"] = cy.stub(); - - CypressMountWithProviders( - - ); - - getComponent("icon") - .click() - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - }); - - it.each([keysToTrigger[0], keysToTrigger[1]])( - "should call iconOnClick callback when %s key is triggered", - (key) => { - const callback: TextboxProps["iconOnClick"] = cy.stub(); - - CypressMountWithProviders( - - ); - - getComponent("icon") - .trigger("keydown", { ...keyCode(key), force: true }) - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - } - ); - - it.each([keysToTrigger[0], keysToTrigger[1]])( - "should call onKeyDown callback when %s key is triggered", - (key) => { - const callback: TextboxProps["onKeyDown"] = cy.stub(); - - CypressMountWithProviders( - - ); - - textboxInput() - .focus() - .trigger("keydown", { ...keyCode(key), force: true }) - .then(() => { - expect(callback).to.have.been.calledOnce; - }); - } - ); - }); - - describe("Accessibility tests for Textbox component", () => { - it("should pass accessibility tests for Textbox default story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox autoFocus story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox characterCounter story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox disabled story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox LabelAlign story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox Margins story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox NewDesignsValidation story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox Prefix story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox ReadOnly story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox Required story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox Sizes story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox ValidationsAsABoolean story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox ValidationsAsAString story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox ValidationsAsAStringDisplayedOnLabel story", () => { - CypressMountWithProviders( - - ); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox ValidationsAsAStringWithTooltipCustom story", () => { - CypressMountWithProviders( - - ); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox ValidationsAsAStringWithTooltipDefault story", () => { - CypressMountWithProviders( - - ); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox WithCustomLabelWidthAndInputWidth story", () => { - CypressMountWithProviders( - - ); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox WithCustomMaxWidth story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox WithFieldHelp story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox WithLabelHelp story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Textbox WithLabelInline story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - }); - - it("should have the expected border radius styling", () => { - CypressMountWithProviders(); - getElement("input").should("have.css", "border-radius", "4px"); - }); -}); diff --git a/playwright/components/index.ts b/playwright/components/index.ts index 2949a58c58..82fb9eb01b 100644 --- a/playwright/components/index.ts +++ b/playwright/components/index.ts @@ -8,6 +8,7 @@ import { DLS_ROOT, FIELD_HELP_PREVIEW, LABEL, + COMMON_INPUT_CHARACTER_LIMIT, STICKY_FOOTER, COMMMON_DATA_ELEMENT_INPUT, PORTAL, @@ -30,6 +31,14 @@ export const button = (page: Page) => { return page.locator(BUTTON); }; +export const getDataComponentByValue = (page: Page, element: string) => { + return page.locator(`[data-component="${element}"]`); +}; + +export const getDataRoleByValue = (page: Page, element: string) => { + return page.locator(`[data-role="${element}"]`); +}; + export const closeIconButton = (page: Page) => { return page.locator(CLOSE_ICON_BUTTON); }; @@ -70,6 +79,10 @@ export const label = (page: Page) => { return page.locator(LABEL); }; +export const characterLimit = (page: Page) => { + return page.locator(COMMON_INPUT_CHARACTER_LIMIT); +}; + export const legendSpan = (page: Page) => { return page.locator("legend > span"); }; diff --git a/playwright/components/locators.ts b/playwright/components/locators.ts index 40d67c7e82..8c0cf4c454 100644 --- a/playwright/components/locators.ts +++ b/playwright/components/locators.ts @@ -18,7 +18,7 @@ export const COMMON_INPUT = ".common-input__"; export const BUTTON = 'button[type="button"]'; export const COMMON_INPUT_PREFIX = '[data-element="textbox-prefix"]'; export const COMMON_INPUT_CHARACTER_LIMIT = - 'div[data-element="character-limit"]'; + 'div[data-element="character-count"]'; export const PORTAL = ".carbon-portal"; export const LEGEND = "legend"; export const STICKY_FOOTER = '[data-component="sticky-footer"]'; diff --git a/playwright/components/textbox/index.ts b/playwright/components/textbox/index.ts index 1e22797c51..822515f65c 100644 --- a/playwright/components/textbox/index.ts +++ b/playwright/components/textbox/index.ts @@ -16,5 +16,5 @@ export const textboxPrefix = (page: Page) => { }; export const textboxInput = (page: Page) => { - return page.locator(TEXTBOX).locator("div").first(); + return page.locator(TEXTBOX).locator("input"); }; diff --git a/src/components/textbox/components.test-pw.tsx b/src/components/textbox/components.test-pw.tsx new file mode 100644 index 0000000000..c73851ec66 --- /dev/null +++ b/src/components/textbox/components.test-pw.tsx @@ -0,0 +1,224 @@ +import React, { useState, useRef } from "react"; +import Textbox, { TextboxProps } from "."; +import Box from "../box"; +import Button from "../button"; +import CarbonProvider from "../carbon-provider/carbon-provider.component"; + +export const SIZES = ["small", "medium", "large"] as const; +export const VALIDATIONS = ["error", "warning", "info"] as const; + +export const TextboxComponent = (props: Partial) => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + + ); +}; + +export const TextboxComponentInputRef = () => { + const ref = useRef(null); + + return ( + + + ) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ref.current = el.current; + }} + /> + + ); +}; + +export const TextboxComponentWithLeftChildren = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + Test} + onChange={setValue} + /> + ); +}; + +export const TextboxComponentWithPositionedChildren = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + Test} + onChange={setValue} + /> + ); +}; + +export const TextboxValidationsAsAStringWithTooltipDefault = () => { + return ( + + {VALIDATIONS.map((validationType) => ( +
+ +
+ ))} +
+ ); +}; + +export const TextboxValidationsAsABoolean = () => { + return ( + + {VALIDATIONS.map((validationType) => ( +
+ + +
+ ))} +
+ ); +}; + +export const TextboxValidationsAsAString = () => { + return ( + + {VALIDATIONS.map((validationType) => ( +
+ + +
+ ))} +
+ ); +}; + +export const TextboxValidationsAsAStringWithTooltipCustom = () => { + return ( + + {VALIDATIONS.map((validationType) => ( +
+ +
+ ))} +
+ ); +}; + +export const TextboxValidationsAsAStringDisplayedOnLabel = () => { + return ( + + {VALIDATIONS.map((validationType) => ( +
+ + +
+ ))} +
+ ); +}; + +export const TextboxNewDesignsValidation = () => { + return ( + + + {(["error", "warning"] as const).map((validationType) => + SIZES.map((size) => ( +
+ + +
+ )) + )} +
+
+ ); +}; diff --git a/src/components/textbox/textbox-test.stories.tsx b/src/components/textbox/textbox-test.stories.tsx index 17ba005373..1d37d57808 100644 --- a/src/components/textbox/textbox-test.stories.tsx +++ b/src/components/textbox/textbox-test.stories.tsx @@ -1,8 +1,6 @@ -import React, { useState, useRef } from "react"; +import React, { useState } from "react"; import { action } from "@storybook/addon-actions"; import Textbox, { TextboxProps } from "."; -import Box from "../box"; -import Button from "../button"; import CarbonProvider from "../carbon-provider/carbon-provider.component"; import { ICONS } from "../icon/icon-config"; @@ -193,42 +191,3 @@ export const PrefixWithSizes = () => { }; PrefixWithSizes.storyName = "prefix with sizes"; - -export const TextboxComponent = (props: Partial) => { - const [state, setState] = useState("Textbox"); - - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -}; - -export const TextboxComponentInputRef = () => { - const ref = useRef(null); - - return ( - - - ) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - ref.current = el.current; - }} - /> - - ); -}; diff --git a/src/components/textbox/textbox.pw.tsx b/src/components/textbox/textbox.pw.tsx new file mode 100644 index 0000000000..f61d7c7a39 --- /dev/null +++ b/src/components/textbox/textbox.pw.tsx @@ -0,0 +1,1110 @@ +import { expect, test } from "@playwright/experimental-ct-react17"; +import { TextboxProps } from "components/textbox"; +import React from "react"; +import { HooksConfig } from "../../../playwright"; +import { + getDesignTokensByCssProperty, + checkAccessibility, + checkGoldenOutline, + verifyRequiredAsteriskForLabel, + getStyle, + assertCssValueIsApproximately, +} from "../../../playwright/support/helper"; +import { + fieldHelpPreview, + getDataElementByValue, + getDataComponentByValue, + getDataRoleByValue, + tooltipPreview, + characterLimit, +} from "../../../playwright/components"; +import { + TextboxComponent, + TextboxComponentInputRef, + TextboxComponentWithLeftChildren, + TextboxComponentWithPositionedChildren, + TextboxValidationsAsAStringWithTooltipDefault, + TextboxValidationsAsABoolean, + TextboxValidationsAsAString, + TextboxValidationsAsAStringWithTooltipCustom, + TextboxValidationsAsAStringDisplayedOnLabel, + TextboxNewDesignsValidation, +} from "./components.test-pw"; +import { + textbox, + textboxInput, + textboxPrefix, +} from "../../../playwright/components/textbox"; + +import { + CHARACTERS, + SIZE, + VALIDATION, +} from "../../../playwright/support/constants"; + +import { BUTTON, ICON } from "../../../playwright/components/locators"; +import Box from "../../../src/components/box"; + +const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS]; + +const keysToTrigger = ["Enter", "Space"] as const; + +test.describe("Prop checks for Textbox component", () => { + ([ + [SIZE.SMALL, "32px", "--sizing400"], + [SIZE.MEDIUM, "40px", "--sizing500"], + [SIZE.LARGE, "48px", "--sizing600"], + ] as [TextboxProps["size"], string, string][]).forEach( + ([size, height, token]) => { + test(`should render with ${size} size and ${height} height when size prop is ${size}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(textbox(page)).toHaveCSS("min-height", height); + + const tokenValues = await getDesignTokensByCssProperty( + page, + textbox(page), + "min-height" + ); + expect(tokenValues[0]).toBe(token); + }); + } + ); + + [ + ["background", "--colorsUtilityYang100"], + ["border", "--colorsUtilityMajor300"], + ].forEach(([cssProp, token]) => { + test(`should render with correct ${cssProp} token`, async ({ + mount, + page, + }) => { + await mount(); + + const tokenValues = await getDesignTokensByCssProperty( + page, + textbox(page), + cssProp + ); + expect(tokenValues[0]).toBe(token); + }); + }); + + test("should render with isOptional prop", async ({ mount, page }) => { + await mount(); + + const labelParent = getDataElementByValue(page, "label").locator(".."); + const contentValue = await getStyle(labelParent, "content", ":after"); + + expect(contentValue).toContain("(optional)"); + }); + + test("should render with data-component prop", async ({ mount, page }) => { + await mount(); + + await expect( + getDataComponentByValue(page, CHARACTERS.STANDARD) + ).toBeVisible(); + }); + + test("should render with data-element prop", async ({ mount, page }) => { + await mount(); + + await expect( + getDataElementByValue(page, CHARACTERS.STANDARD) + ).toBeVisible(); + }); + + test("should render with data-role prop", async ({ mount, page }) => { + await mount(); + + await expect(getDataRoleByValue(page, CHARACTERS.STANDARD)).toBeVisible(); + }); + + test("should render with id prop", async ({ mount, page }) => { + await mount(); + + await expect(textboxInput(page)).toHaveId(CHARACTERS.STANDARD); + }); + + testData.forEach((labelVals) => { + test(`should render with label prop ${labelVals}`, async ({ + mount, + page, + }) => { + await mount(); + + const label = getDataElementByValue(page, "label"); + + await expect(label).toHaveText(labelVals); + }); + }); + + test("should render with labelInline prop", async ({ mount, page }) => { + await mount(); + + const labelParent = getDataElementByValue(page, "label").locator(".."); + await expect(labelParent).toHaveCSS("justify-content", "flex-end"); + }); + + ([ + ["left", "start"], + ["right", "end"], + ] as [TextboxProps["labelAlign"], string][]).forEach( + ([labelAlign, cssValue]) => { + test(`should render with ${labelAlign} labelAlign prop`, async ({ + mount, + page, + }) => { + await mount(); + + const labelParent = getDataElementByValue(page, "label").locator(".."); + await expect(labelParent).toHaveCSS( + "justify-content", + `flex-${cssValue}` + ); + }); + } + ); + + testData.forEach((labelHelp) => { + test(`should render with labelHelp prop ${labelHelp}`, async ({ + mount, + page, + }) => { + await mount(); + + const question = getDataElementByValue(page, "question"); + await question.click(); + + await expect(tooltipPreview(page)).toHaveText(labelHelp); + }); + }); + + ([ + [1, "8px"], + [2, "16px"], + ] as [TextboxProps["labelSpacing"], string][]).forEach( + ([spacing, padding]) => { + test(`should render with labelSpacing prop ${spacing}`, async ({ + mount, + page, + }) => { + await mount(); + + const labelParent = getDataElementByValue(page, "label").locator(".."); + await expect(labelParent).toHaveCSS("padding-right", padding); + }); + } + ); + + ([ + [10, 90, 135, 1229], + [30, 70, 409, 956], + [80, 20, 1092, 273], + ] as [ + TextboxProps["labelWidth"], + TextboxProps["inputWidth"], + number, + number + ][]).forEach(([labelVal, inputVal, labelRatio, inputRatio]) => { + test(`should render with correct ratios when inputWidth prop is ${inputVal} and labelWidth prop is ${labelVal}`, async ({ + mount, + page, + }) => { + await mount( + + ); + + const labelParent = getDataElementByValue(page, "label").locator(".."); + const inputParent = getDataElementByValue(page, "input").locator(".."); + + await assertCssValueIsApproximately(labelParent, "width", labelRatio); + await assertCssValueIsApproximately(inputParent, "width", inputRatio); + }); + }); + + [ + ["foo", "with"], + ["", "without"], + ].forEach(([hint, renderStatus]) => { + test(`should render ${renderStatus} an input hint depending on the inputHint prop value`, async ({ + mount, + page, + }) => { + await mount(); + + const inputHint = getDataElementByValue(page, "input-hint"); + + if (renderStatus === "with") { + await expect(inputHint).toHaveCount(1); + } else { + await expect(inputHint).toHaveCount(0); + } + }); + }); + + ([ + [4, "with", "is"], + ["", "without", "is not"], + ] as [TextboxProps["characterLimit"], string, string][]).forEach( + ([characterLimitVal, renderStatus, characterLimitStatus]) => { + test(`should render ${renderStatus} a character count when the characterLimit prop ${characterLimitStatus} passed`, async ({ + mount, + page, + }) => { + await mount(); + + const characterCount = getDataElementByValue(page, "character-count"); + + if (renderStatus === "with") { + await expect(characterCount).toHaveCount(1); + } else { + await expect(characterCount).toHaveCount(0); + } + }); + } + ); + + ([ + [4, "with", "is"], + ["", "without", "is not"], + ] as [TextboxProps["characterLimit"], string, string][]).forEach( + ([characterLimitVal, renderStatus, characterLimitStatus]) => { + test(`should render ${renderStatus} a visually hidden character count when the characterLimit prop ${characterLimitStatus} passed`, async ({ + mount, + page, + }) => { + await mount(); + + const visuallyHiddenCharacterCount = getDataElementByValue( + page, + "visually-hidden-character-count" + ); + + if (renderStatus === "with") { + await expect(visuallyHiddenCharacterCount).toHaveCount(1); + } else { + await expect(visuallyHiddenCharacterCount).toHaveCount(0); + } + }); + } + ); + + ([ + [4, "with", "is"], + ["", "without", "is not"], + ] as [TextboxProps["characterLimit"], string, string][]).forEach( + ([characterLimitVal, renderStatus, characterLimitStatus]) => { + test(`should render ${renderStatus} a visually hidden character count hint when the characterLimit prop ${characterLimitStatus} passed`, async ({ + mount, + page, + }) => { + await mount(); + + const visuallyHiddenCharacterCountHint = getDataElementByValue( + page, + "visually-hidden-hint" + ); + + if (renderStatus === "with") { + await expect(visuallyHiddenCharacterCountHint).toHaveCount(1); + } else { + await expect(visuallyHiddenCharacterCountHint).toHaveCount(0); + } + }); + } + ); + + ["10%", "30%", "50%", "80%", "100%"].forEach((maxWidth) => { + test(`should render with ${maxWidth} max-width`, async ({ + mount, + page, + }) => { + await mount(); + + const textboxParent = textbox(page).locator(".."); + await expect(textboxParent).toHaveCSS("max-width", maxWidth); + }); + }); + + test("should render with max-width of 100%, when maxWidth prop is not passed", async ({ + mount, + page, + }) => { + await mount(); + + const textboxParent = textbox(page).locator(".."); + await expect(textboxParent).toHaveCSS("max-width", "100%"); + }); + + test("should render with required prop", async ({ mount, page }) => { + await mount(); + + await verifyRequiredAsteriskForLabel(page); + }); + + [5, 10, 11, 15, 20].forEach((limit) => { + test(`should render and warn if the number of characters typed exceeds the set characterLimit of ${limit} and the enforceCharacterLimit prop is false`, async ({ + mount, + page, + }) => { + await mount(); + + const inputValue = "12345678901"; + const underCharacters = + limit - inputValue.length === 1 ? "character" : "characters"; + const overCharacters = + inputValue.length - limit === 1 ? "character" : "characters"; + + await textboxInput(page).fill(inputValue); + + if (inputValue.length > limit) { + await expect(characterLimit(page)).toHaveText( + `${inputValue.length - limit} ${overCharacters} too many` + ); + await expect(characterLimit(page)).toHaveCSS( + "color", + "rgb(203, 55, 74)" + ); + } else { + await expect(characterLimit(page)).toHaveText( + `${limit - inputValue.length} ${underCharacters} left` + ); + await expect(characterLimit(page)).toHaveCSS( + "color", + "rgba(0, 0, 0, 0.55)" + ); + } + }); + }); + + test("should render with name prop", async ({ mount, page }) => { + await mount(); + + await expect(textboxInput(page)).toHaveAttribute( + "name", + CHARACTERS.STANDARD + ); + }); + + test("should render with disabled prop", async ({ mount, page }) => { + await mount(); + + await expect(textboxInput(page)).toBeDisabled(); + }); + + test("should render with an icon and the disabled prop", async ({ + mount, + page, + }) => { + await mount(); + + const inputIcon = textbox(page).locator(ICON); + + await expect(inputIcon).toBeVisible(); + await expect(inputIcon).toHaveCSS("color", "rgba(0, 0, 0, 0.3)"); + }); + + testData.forEach((placeholder) => { + test(`should render with placeholder prop ${placeholder}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(textboxInput(page)).toHaveAttribute( + "placeholder", + placeholder + ); + }); + }); + + test("should render with autoFocus prop and correct styling when focusRedesignOpt out is false", async ({ + mount, + page, + }) => { + await mount(, { + hooksConfig: { focusRedesignOptOut: false }, + }); + + await expect(textboxInput(page)).toBeFocused(); + await expect(textbox(page)).toHaveCSS( + "box-shadow", + "rgb(255, 188, 25) 0px 0px 0px 3px, rgba(0, 0, 0, 0.9) 0px 0px 0px 6px" + ); + await expect(textbox(page)).toHaveCSS( + "outline", + "rgba(0, 0, 0, 0) solid 3px" + ); + }); + + test("should render with autoFocus prop and correct styling when focusRedesignOpt out is true", async ({ + mount, + page, + }) => { + await mount(, { + hooksConfig: { focusRedesignOptOut: true }, + }); + + await expect(textboxInput(page)).toBeFocused(); + await checkGoldenOutline(textbox(page)); + }); + + test("should render with readOnly prop", async ({ mount, page }) => { + await mount(); + + await expect(textboxInput(page)).not.toBeEditable(); + }); + + test("should render with an icon and the readOnly prop", async ({ + mount, + page, + }) => { + await mount(); + + const inputIcon = textbox(page).locator(ICON); + + await expect(inputIcon).toBeVisible(); + await expect(inputIcon).toHaveCSS("color", "rgba(0, 0, 0, 0.3)"); + }); + + ["error", "warning", "info"].forEach((type) => { + test(`should render with a ${type} validation icon on input`, async ({ + mount, + page, + }) => { + await mount( + + ); + + await expect(textbox(page).locator(ICON)).toHaveAttribute( + "data-element", + type + ); + }); + }); + + ["error", "warning", "info"].forEach((type) => { + test(`should render with a ${type} validation icon on label`, async ({ + mount, + page, + }) => { + await mount( + + ); + + const labelParent = getDataElementByValue(page, "label").locator(".."); + const validationIcon = labelParent.locator(`[data-element="${type}"]`); + + await expect(validationIcon).toBeVisible(); + }); + }); + + ([ + [VALIDATION.ERROR, "error", true], + [VALIDATION.WARNING, "warning", true], + [VALIDATION.INFO, "info", true], + ["rgb(102, 132, 148)", "error", false], + ["rgb(102, 132, 148)", "warning", false], + ["rgb(102, 132, 148)", "info", false], + ] as [string, string, boolean][]).forEach(([borderColor, type, bool]) => { + test(`should render with ${borderColor} input border when validation type ${type} is ${bool}`, async ({ + mount, + page, + }) => { + await mount( + + ); + + await expect(textbox(page)).toHaveCSS("border-bottom-color", borderColor); + }); + }); + + test("should render with leftChildren prop", async ({ mount, page }) => { + await mount(); + + await expect(textbox(page).locator(BUTTON)).toBeVisible(); + }); + + testData.forEach((prefix) => { + test(`should render with prefix prop ${prefix}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(textboxPrefix(page)).toHaveText(prefix); + await expect(textboxPrefix(page)).toHaveCSS("font-size", "14px"); + await expect(textboxPrefix(page)).toHaveCSS("font-weight", "900"); + await expect(textboxPrefix(page)).toHaveCSS("margin-left", "12px"); + }); + }); + + [ + ["", "without", "row-reverse"], + ["foo", "with", "row"], + ].forEach(([prefix, prefixStatus, flexDirection]) => { + test(`should render ${prefixStatus} prefix prop when the align prop is also set to 'right'`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(textbox(page)).toHaveCSS("flex-direction", flexDirection); + }); + }); + + test("should render with positionedChildren prop", async ({ + mount, + page, + }) => { + await mount(); + + const textboxParent = textbox(page).locator(".."); + await expect(textboxParent.locator("button")).toBeVisible(); + }); + + ([ + ["flex", 399], + ["flex", 400], + ["block", 401], + ] as [string, TextboxProps["adaptiveLabelBreakpoint"]][]).forEach( + ([displayValue, breakpoint]) => { + test(`should render with ${displayValue} label alignment when the adaptiveLabelBreakpoint prop is ${breakpoint} with a set viewport of 400`, async ({ + mount, + page, + }) => { + await page.setViewportSize({ width: 400, height: 300 }); + await mount(); + + const labelParentParent = getDataElementByValue(page, "label") + .locator("..") + .locator(".."); + await expect(labelParentParent).toHaveCSS("display", displayValue); + }); + } + ); + + test("should render with labelId prop", async ({ mount, page }) => { + await mount(); + + const label = getDataElementByValue(page, "label"); + + await expect(label).toHaveAttribute("id", `${CHARACTERS.STANDARD}-label`); + await expect(label).toHaveAttribute("for", CHARACTERS.STANDARD); + }); + + testData.forEach(([fieldHelp]) => { + test(`should render with fieldHelp prop ${fieldHelp}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(fieldHelpPreview(page)).toHaveText(fieldHelp); + }); + }); + + test("should render with formattedValue prop", async ({ mount, page }) => { + await mount(); + + await expect(textboxInput(page)).toHaveAttribute( + "value", + CHARACTERS.STANDARD + ); + }); + + (["add", "filter", "play"] as TextboxProps["inputIcon"][]).forEach((icon) => { + test(`should render with ${icon} input icon`, async ({ mount, page }) => { + await mount(); + + const inputIcon = getDataElementByValue(page, "input-icon-toggle"); + await expect(textbox(page).locator(inputIcon)).toBeVisible(); + }); + }); + + test("should render with iconTabIndex prop", async ({ mount, page }) => { + await mount(); + + const inputIcon = getDataElementByValue(page, "input-icon-toggle"); + + await expect(inputIcon).toHaveAttribute("tabindex", "25"); + }); + + ([ + "top", + "bottom", + "left", + "right", + ] as TextboxProps["tooltipPosition"][]).forEach((position) => { + test(`should render with tooltip positioned to the ${position}`, async ({ + mount, + page, + }) => { + await mount( + + + + ); + + await getDataElementByValue(page, "error").hover(); + + await expect(tooltipPreview(page)).toHaveText(CHARACTERS.STANDARD); + await expect(tooltipPreview(page)).toHaveAttribute( + "data-placement", + position as string + ); + }); + }); + + test("should render with helpAriaLabel prop", async ({ mount, page }) => { + await mount( + + ); + + const help = getDataComponentByValue(page, "help"); + + await expect(help).toHaveAttribute("aria-label", CHARACTERS.STANDARD); + }); + + (["left", "right"] as TextboxProps["align"][]).forEach((align) => { + test(`should render with align prop set to ${align}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(textboxInput(page)).toHaveCSS("text-align", align as string); + }); + }); + + test("should render with inputRef prop and focus on input via click on ref", async ({ + mount, + page, + }) => { + await mount(); + + await getDataComponentByValue(page, "button").click(); + + await expect(textboxInput(page)).toBeFocused(); + }); + + testData.forEach((value) => { + test(`should render and input ${value} as value`, async ({ + mount, + page, + }) => { + await mount(); + + await textboxInput(page).fill(value); + + await expect(textboxInput(page)).toHaveValue(value); + }); + }); +}); + +test.describe("Event checks", () => { + test("should call onChange callback when a type event is triggered", async ({ + mount, + page, + }) => { + const inputValue = "1"; + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await textboxInput(page).type(inputValue); + + expect(callbackCount).toBe(1); + }); + + test("should call onBlur callback when a blur event is triggered", async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await textboxInput(page).click(); + await textboxInput(page).blur(); + + expect(callbackCount).toBe(1); + }); + + test("should call onClick callback when a click event is triggered", async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await textboxInput(page).click(); + + expect(callbackCount).toBe(1); + }); + + test("should call onFocus callback when a focus event is triggered", async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await textboxInput(page).focus(); + + expect(callbackCount).toBe(1); + }); + + test("should call onMouseDown callback when a mousedown event is triggered", async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await textboxInput(page).dispatchEvent("mousedown"); + + expect(callbackCount).toBe(1); + }); + + test("should call iconOnMouseDown callback when a click event is triggered", async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + inputIcon="add" + /> + ); + + await getDataComponentByValue(page, "icon").click({ button: "left" }); + + expect(callbackCount).toBe(1); + }); + + test("should call iconOnClick callback when a click event is triggered", async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + inputIcon="add" + /> + ); + + await getDataComponentByValue(page, "icon").click(); + + expect(callbackCount).toBe(1); + }); + + [keysToTrigger[0], keysToTrigger[1]].forEach((key) => { + test(`should call iconOnClick callback when ${key} key is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + iconTabIndex={0} + /> + ); + + await getDataElementByValue(page, "input-icon-toggle").press(key); + + expect(callbackCount).toEqual(1); + }); + }); + + [keysToTrigger[0], keysToTrigger[1]].forEach((key) => { + test(`should call onKeyDown callback when ${key} key is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await textboxInput(page).press(key); + + expect(callbackCount).toEqual(1); + }); + }); +}); + +test("Component should have the expected border radius styling", async ({ + mount, + page, +}) => { + await mount(); + await expect(textboxInput(page)).toHaveCSS("border-radius", "4px"); +}); + +test.describe("Accessibility tests for Textbox component", () => { + test("should pass accessibility tests for default component with a set value and label", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when autoFocus prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when characterLimit prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when disabled prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + (["left", "right"] as TextboxProps["align"][]).forEach((align) => { + test(`should pass accessibility tests when LabelAlign prop is ${align}`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + }); + + test("should pass accessibility tests when margin prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when prefix prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when readOnly prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when required prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + (["small", "medium", "large"] as TextboxProps["size"][]).forEach((align) => { + test(`should pass accessibility tests when sizes prop is ${align}`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + }); + + test("should pass accessibility tests when opted into new validation designs", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when boolean validations are passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when string validations are passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when string validations are passed and displayed on label", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when custom string validations are passed and displayed on tooltip", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when string validations are displayed on tooltip", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when inputWidth and labelWidth props are passed and labelInline is true", async ({ + mount, + page, + }) => { + await mount( + + ); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when maxWidth prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when fieldHelp prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when labelHelp and helpAriaLabel props are passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test("should pass accessibility tests when labelInline prop is passed", async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); +});