diff --git a/cypress/components/tooltip/tooltip.cy.tsx b/cypress/components/tooltip/tooltip.cy.tsx deleted file mode 100644 index b4c876db14..0000000000 --- a/cypress/components/tooltip/tooltip.cy.tsx +++ /dev/null @@ -1,274 +0,0 @@ -import React from "react"; -import CypressMountWithProviders from "../../support/component-helper/cypress-mount"; -import { TooltipProps } from "../../../src/components/tooltip"; -import * as testStories from "../../../src/components/tooltip/tooltip-test.stories"; -import * as stories from "../../../src/components/tooltip/tooltip.stories"; -import { - tooltipPreview, - tooltipTrigger, - tooltipTriggerToggle, -} from "../../locators/tooltip/index"; -import { - SIZE, - COLOR, - CHARACTERS, -} from "../../support/component-helper/constants"; -import { assertCssValueIsApproximately } from "../../support/component-helper/common-steps"; -import { getDataElementByValue } from "../../locators"; -import { TooltipPositions } from "../../../src/components/tooltip/tooltip.config"; - -const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS]; -const backgroundColors = [COLOR.ORANGE, COLOR.RED, COLOR.BLACK, COLOR.BROWN]; - -context("Tests for Tooltip component", () => { - describe("should check Tooltip component properties", () => { - it.each(testData)( - "should check %s as message for Tooltip component", - (message) => { - CypressMountWithProviders( - - ); - tooltipPreview().should("have.text", message); - } - ); - - it.each(testData)("should check %s as Id for Tooltip component", (id) => { - CypressMountWithProviders(); - tooltipPreview().should("have.id", id); - }); - - it.each([ - [true, "be.visible"], - [false, "not.exist"], - ])( - "should check when tooltip visibility is %s for Tooltip component", - (bool, state) => { - CypressMountWithProviders( - - ); - tooltipPreview().should(state); - } - ); - - it.each(["bottom", "left", "right", "top"] as TooltipProps["position"][])( - "should check %s position of tooltip for Tooltip component", - (position) => { - CypressMountWithProviders( - - <>{`This tooltip is positioned ${position}`} - - ); - tooltipPreview().should("be.visible").and("have.css", position); - } - ); - - it.each(["undefined", "error"])( - "should check %s type for Tooltip component", - (type) => { - CypressMountWithProviders(); - tooltipPreview().should("have.attr", "type", type); - } - ); - - it.each(backgroundColors)( - "should check tooltip background-color as %s for Tooltip component", - (color) => { - CypressMountWithProviders( - - ); - tooltipPreview().should("have.css", "background-color", color); - } - ); - - it.each(backgroundColors)( - "should check tooltip font color as %s for Tooltip component", - (color) => { - CypressMountWithProviders( - - ); - tooltipPreview().should("have.css", "color", color); - } - ); - - it.each([ - [SIZE.MEDIUM, 14], - [SIZE.LARGE, 16], - ] as [TooltipProps["size"], number][])( - "should check %s size for Tooltip component", - (size, fontSize) => { - CypressMountWithProviders(); - tooltipPreview().should("have.css", "font-size", `${fontSize}px`); - } - ); - - it.each([ - ["left", "bottom", "top"], - ["top", "bottom", "top"], - ["left", "top", "bottom"], - ["bottom", "top", "bottom"], - ["bottom", "left", "bottom"], - ["bottom", "right", "bottom"], - ["top", "left", "top"], - ["top", "right", "top"], - ["right", "bottom", "right"], - ["right", "top", "right"], - ] as [TooltipPositions, TooltipProps["position"], Cypress.PositionType][])( - "should check flip position to the %s when tooltip position is %s and scrolling to the %s side for Tooltip component", - (flipPosition, tooltipPosition, scrollPosition) => { - CypressMountWithProviders( -
- -
- ); - cy.viewport(700, 120); - cy.scrollTo(scrollPosition); - tooltipPreview().should("have.attr", "data-placement", flipPosition); - } - ); - - describe.each(["top", "bottom", "right", "left"] as NonNullable< - TooltipProps["position"] - >[])("when tooltip has %s position and", (position) => { - it.each([ - [SIZE.SMALL, { top: 15, bottom: 631, left: 47, right: 1040 }], - [SIZE.MEDIUM, { top: 14, bottom: 630, left: 44, right: 1037 }], - [SIZE.LARGE, { top: 10, bottom: 626, left: 40, right: 1033 }], - ] as [TooltipProps["inputSize"], { top: number; bottom: number; left: number; right: number }][])( - "when inputSize is %s should have correct styles applied", - (inputSize, offset) => { - CypressMountWithProviders( - - ); - tooltipPreview().then(($el) => { - assertCssValueIsApproximately($el, position, offset[position]); - Cypress.dom.isVisible($el); - }); - } - ); - }); - - it("should show tooltip when target is hovered", () => { - CypressMountWithProviders(); - tooltipPreview().should("not.exist"); - tooltipTrigger().trigger("mouseenter"); - tooltipPreview().should("be.visible"); - }); - - it("should hide tooltip when mouse leaves target", () => { - CypressMountWithProviders(); - tooltipPreview().should("not.exist"); - tooltipTrigger().trigger("mouseenter"); - tooltipPreview().should("be.visible"); - tooltipTrigger().trigger("mouseleave"); - tooltipPreview().should("not.exist"); - }); - - it("should show tooltip when target is focused", () => { - CypressMountWithProviders(); - tooltipPreview().should("not.exist"); - tooltipTrigger().focus(); - tooltipPreview().should("be.visible"); - }); - - it("should hide tooltip when target is blurred", () => { - CypressMountWithProviders(); - tooltipPreview().should("not.exist"); - tooltipTrigger().focus(); - tooltipPreview().should("be.visible"); - tooltipTrigger().blur(); - tooltipPreview().should("not.exist"); - }); - - it("new tooltip target should still trigger tooltip visibility", () => { - CypressMountWithProviders( - - ); - tooltipTrigger().should("have.text", "Target"); - - tooltipTrigger().trigger("mouseenter"); - tooltipPreview().should("be.visible"); - tooltipTrigger().trigger("mouseleave"); - tooltipPreview().should("not.exist"); - - tooltipTriggerToggle().click(); - tooltipTrigger().should("have.text", "Secondary target"); - - tooltipTrigger().trigger("mouseenter"); - tooltipPreview().should("be.visible"); - tooltipTrigger().trigger("mouseleave"); - tooltipPreview().should("not.exist"); - }); - }); - - describe("Accessibility tests for Tooltip component", () => { - it("should pass accessibility tests for Tooltip Default story", () => { - CypressMountWithProviders(); - - getDataElementByValue("main-text").click(); - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Tooltip Controlled story", () => { - CypressMountWithProviders(); - - getDataElementByValue("main-text").eq(0).click(); - cy.checkAccessibility(); - }); - - it.each([ - ["top", 0], - ["bottom", 1], - ["left", 2], - ["right", 3], - ])( - "should pass accessibility tests for Tooltip Positioning story %s position", - (position, button) => { - CypressMountWithProviders(); - - getDataElementByValue("main-text").eq(button).click(); - cy.checkAccessibility(); - } - ); - - it("should pass accessibility tests for Tooltip FlipBehaviourOverrides story", () => { - CypressMountWithProviders(); - - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Tooltip LargeTooltip story", () => { - CypressMountWithProviders(); - - getDataElementByValue("main-text").click(); - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Tooltip Types story", () => { - CypressMountWithProviders(); - - getDataElementByValue("main-text").eq(1).click(); - - getDataElementByValue("main-text").eq(2).click(); - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for Tooltip ColorOverrides story", () => { - CypressMountWithProviders(); - - getDataElementByValue("main-text").click(); - cy.checkAccessibility(); - }); - - it("should render the Tooltip with the expected border radius styling", () => { - CypressMountWithProviders(); - tooltipPreview().should("have.css", "border-radius", "4px"); - }); - }); -}); diff --git a/cypress/locators/tooltip/index.js b/cypress/locators/tooltip/index.js deleted file mode 100644 index 40a2a0a1d9..0000000000 --- a/cypress/locators/tooltip/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import { - TOOLTIP_PREVIEW, - TOOLTIP_POINTER, - TOOLTIP_TRIGGER, - TOOLTIP_TRIGGER_TOGGLE, -} from "./locators"; - -// component preview locators -export const tooltipPreview = () => cy.get(TOOLTIP_PREVIEW); -export const tooltipPointer = () => cy.get(TOOLTIP_POINTER); -export const tooltipTrigger = () => cy.get(TOOLTIP_TRIGGER); -export const tooltipTriggerToggle = () => cy.get(TOOLTIP_TRIGGER_TOGGLE); diff --git a/cypress/locators/tooltip/locators.js b/cypress/locators/tooltip/locators.js deleted file mode 100644 index 6b1c2eaf7a..0000000000 --- a/cypress/locators/tooltip/locators.js +++ /dev/null @@ -1,6 +0,0 @@ -// component preview locators -export const TOOLTIP_PREVIEW = '[data-element="tooltip"]'; -export const TOOLTIP_POINTER = `${TOOLTIP_PREVIEW} > div[data-element="tooltip-pointer"]`; -export const TOOLTIP_TRIGGER = '[data-component="tooltip-trigger"]'; -export const TOOLTIP_TRIGGER_TOGGLE = - '[data-component="tooltip-trigger-toggle"]'; diff --git a/src/components/tooltip/components.test-pw.tsx b/src/components/tooltip/components.test-pw.tsx new file mode 100644 index 0000000000..209f375e73 --- /dev/null +++ b/src/components/tooltip/components.test-pw.tsx @@ -0,0 +1,129 @@ +import React, { useState } from "react"; +import Tooltip, { TooltipProps } from "."; +import Button from "../button"; +import Box from "../box"; +import { TooltipPositions } from "./tooltip.config"; + +export const TooltipComponent = ({ + message, + ...props +}: Partial) => ( +
+ + + +
+); + +export const UncontrolledTooltipComponent = () => ( +
+ + + +
+); + +export const TooltipWithChangingTargetComponent = () => { + const [displayOther, setDisplayOther] = useState(false); + + return ( + <> + + + {displayOther ? ( + + ) : ( + + )} + + + ); +}; + +export const Controlled = () => { + const [isVisible, setIsVisible] = useState(false); + return ( + <> + + + + + + + + + + ); +}; + +export const Positioning = () => { + const [position, setPosition] = useState("top"); + return ( + <> + + + + + + + + + + + + + ); +}; + +export const FlipBehaviourOverrides = () => { + return ( + + + + + + ); +}; + +export const LargeTooltip = () => { + return ( + + + + + + ); +}; + +export const ColorOverrides = () => { + return ( + + + + + + ); +}; diff --git a/src/components/tooltip/tooltip-test.stories.tsx b/src/components/tooltip/tooltip-test.stories.tsx index 75857beeec..a8897dd3b9 100644 --- a/src/components/tooltip/tooltip-test.stories.tsx +++ b/src/components/tooltip/tooltip-test.stories.tsx @@ -149,53 +149,3 @@ const SecondaryButton = forwardRef( ) ); SecondaryButton.displayName = "Tooltip"; - -export const TooltipComponent = ({ - message, - ...props -}: Partial) => ( -
- - - -
-); - -export const UncontrolledTooltipComponent = () => ( -
- - - -
-); - -export const TooltipWithChangingTargetComponent = () => { - const [displayOther, setDisplayOther] = useState(false); - - return ( - <> - - - {displayOther ? ( - Secondary target - ) : ( - - )} - - - ); -}; diff --git a/src/components/tooltip/tooltip.pw.tsx b/src/components/tooltip/tooltip.pw.tsx new file mode 100644 index 0000000000..bf9c0711d6 --- /dev/null +++ b/src/components/tooltip/tooltip.pw.tsx @@ -0,0 +1,465 @@ +import React from "react"; +import { test, expect } from "@playwright/experimental-ct-react17"; +import { Locator } from "@playwright/test"; +import { + TooltipComponent, + UncontrolledTooltipComponent, + Controlled, + Positioning, + FlipBehaviourOverrides, + LargeTooltip, + ColorOverrides, + TooltipWithChangingTargetComponent, +} from "./components.test-pw"; +import { + checkAccessibility, + assertCssValueIsApproximately, +} from "../../../playwright/support/helper"; +import { SIZE, COLOR, CHARACTERS } from "../../../playwright/support/constants"; +import { TooltipProps } from "../../../src/components/tooltip/tooltip.component"; +import { getDataElementByValue } from "../../../playwright/components"; + +const backgroundColors = [COLOR.ORANGE, COLOR.RED, COLOR.BLACK, COLOR.BROWN]; +const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS]; +const getXValue = (locator: Locator) => + locator.evaluate((element) => element.getBoundingClientRect().x); +const getYValue = (locator: Locator) => + locator.evaluate((element) => element.getBoundingClientRect().y); + +test.describe("Tooltip component", () => { + testData.forEach((message) => { + test(`should render with message prop set to ${message}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toHaveText(message); + }); + }); + + testData.forEach((id) => { + test(`should render with id prop set to ${id}`, async ({ mount, page }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toHaveId(id); + }); + }); + + test("tooltip should be visible when isVisible prop is true", async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + }); + + test("tooltip should not be visible when isVisible prop is false", async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + }); + + (["bottom", "left", "right", "top"] as [ + "bottom", + "left", + "right", + "top" + ]).forEach((position) => { + test(`should render with tooltip position set to ${position}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + await expect(getDataElementByValue(page, "tooltip")).toHaveAttribute( + "data-placement", + position + ); + }); + }); + + ["undefined", "error"].forEach((type) => { + test(`should render with type prop set to ${type}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toHaveAttribute( + "type", + type + ); + }); + }); + + backgroundColors.forEach((color) => { + test(`should render with tooltip background color set to ${color}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toHaveCSS( + "background-color", + color + ); + }); + }); + + backgroundColors.forEach((color) => { + test(`should render with tooltip font color set to ${color}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toHaveCSS( + "color", + color + ); + }); + }); + + ([ + [SIZE.MEDIUM, 14], + [SIZE.LARGE, 16], + ] as [TooltipProps["size"], number][]).forEach(([size, fontSize]) => { + test(`should render with size prop set to ${size}`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toHaveCSS( + "font-size", + `${fontSize}px` + ); + }); + }); + + test("tooltip should be rendered in 'top' position when 'top' passed to flipOverrides prop and tooltip needs to be repositioned to fit available space", async ({ + mount, + page, + }) => { + await page.setViewportSize({ height: 120, width: 700 }); + await mount(); + + const tooltip = page.getByRole("tooltip"); + const button = page.getByRole("button"); + + await expect(tooltip).toBeVisible(); + await expect(button).toBeVisible(); + + expect(await getYValue(tooltip)).toBeLessThan(await getYValue(button)); + }); + + test("tooltip should be rendered in 'left' position when 'left' passed to flipOverrides prop and tooltip needs to be repositioned to fit available space", async ({ + mount, + page, + }) => { + await page.setViewportSize({ height: 120, width: 700 }); + await mount( + + ); + + const tooltip = page.getByRole("tooltip"); + const button = page.getByRole("button"); + + await expect(tooltip).toBeVisible(); + await expect(button).toBeVisible(); + + expect(await getXValue(tooltip)).toBeLessThan(await getXValue(button)); + }); + + test("tooltip should be rendered in 'right' position when 'right' passed to flipOverrides prop and tooltip needs to be repositioned to fit available space", async ({ + mount, + page, + }) => { + await page.setViewportSize({ height: 120, width: 700 }); + await mount( + + ); + + const tooltip = page.getByRole("tooltip"); + const button = page.getByRole("button"); + + await expect(tooltip).toBeVisible(); + await expect(button).toBeVisible(); + + expect(await getXValue(tooltip)).toBeGreaterThan(await getXValue(button)); + }); + + ([ + [SIZE.SMALL, 15], + [SIZE.MEDIUM, 14], + [SIZE.LARGE, 10], + ] as [TooltipProps["inputSize"], number][]).forEach(([inputSize, offset]) => { + test(`should have correct styles applied when inputSize is ${inputSize} and tooltip position is set to top`, async ({ + mount, + page, + }) => { + await mount( + + ); + + const element = getDataElementByValue(page, "tooltip"); + await assertCssValueIsApproximately(element, "top", offset); + }); + }); + + ([ + [SIZE.SMALL, 5], + [SIZE.MEDIUM, 6], + [SIZE.LARGE, 10], + ] as [TooltipProps["inputSize"], number][]).forEach(([inputSize, offset]) => { + test(`should have correct styles applied when inputSize is ${inputSize} and tooltip position is set to bottom`, async ({ + mount, + page, + }) => { + await mount( + + ); + + const buttonRect = await page + .getByRole("button") + .evaluate((element) => element.getBoundingClientRect()); + + const element = getDataElementByValue(page, "tooltip"); + await assertCssValueIsApproximately( + element, + "top", + buttonRect.top + buttonRect.height + offset + ); + }); + }); + + ([ + [SIZE.SMALL, 47], + [SIZE.MEDIUM, 44], + [SIZE.LARGE, 40], + ] as [TooltipProps["inputSize"], number][]).forEach(([inputSize, offset]) => { + test(`should have correct styles applied when inputSize is ${inputSize} and tooltip position is set to left`, async ({ + mount, + page, + }) => { + await mount( + + ); + + const element = getDataElementByValue(page, "tooltip"); + await assertCssValueIsApproximately(element, "left", offset); + }); + }); + + ([ + [SIZE.SMALL, 5], + [SIZE.MEDIUM, 8], + [SIZE.LARGE, 12], + ] as [TooltipProps["inputSize"], number][]).forEach(([inputSize, offset]) => { + test(`should have correct styles applied when inputSize is ${inputSize} and tooltip position is set to right`, async ({ + mount, + page, + }) => { + await mount( + + ); + + const buttonRect = await page + .getByRole("button") + .evaluate((element) => element.getBoundingClientRect()); + + const element = getDataElementByValue(page, "tooltip"); + await assertCssValueIsApproximately( + element, + "left", + buttonRect.left + buttonRect.width + offset + ); + }); + }); + + test(`should show tooltip when target is hovered`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + const buttonElement = page.getByRole("button"); + await buttonElement.hover(); + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + }); + + test(`should hide tooltip when mouse leaves target`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + const buttonElement = page.getByRole("button"); + await buttonElement.hover(); + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + await page.mouse.move(100, 100); + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + }); + + test(`should show tooltip when target is focused`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + const buttonElement = page.getByRole("button"); + await buttonElement.focus(); + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + }); + + test(`should hide tooltip when target is blurred`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + const buttonElement = page.getByRole("button"); + await buttonElement.focus(); + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + await buttonElement.blur(); + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + }); + + test(`new tooltip target should still trigger tooltip visibility`, async ({ + mount, + page, + }) => { + await mount(); + + const buttonElement = getDataElementByValue(page, "main-text"); + await expect(buttonElement).toHaveText("Target"); + await buttonElement.hover(); + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + await page.mouse.move(100, 100); + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + await page.getByText("Change target").click(); + await expect(buttonElement).toHaveText("Secondary target"); + await buttonElement.hover(); + await expect(getDataElementByValue(page, "tooltip")).toBeVisible(); + await page.mouse.move(100, 100); + await expect(getDataElementByValue(page, "tooltip")).not.toBeVisible(); + }); + + test(`should have the expected border radius styling`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "tooltip")).toHaveCSS( + "border-radius", + "4px" + ); + }); +}); + +test.describe("Accessibility tests for Tooltip component", () => { + test(`should pass accessibility tests for Default example`, async ({ + mount, + page, + }) => { + await mount(); + + await getDataElementByValue(page, "main-text").nth(0).click(); + await expect(page.getByRole("tooltip")).toBeVisible(); + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for Controlled story`, async ({ + mount, + page, + }) => { + await mount(); + + await getDataElementByValue(page, "main-text").nth(0).click(); + await expect(page.getByRole("tooltip")).toBeVisible(); + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for FlipBehaviourOverrides story`, async ({ + mount, + page, + }) => { + await mount(); + + await getDataElementByValue(page, "main-text").nth(0).click(); + await expect(page.getByRole("tooltip")).toBeVisible(); + await checkAccessibility(page); + }); + + ([ + ["top", 0], + ["bottom", 1], + ["left", 2], + ["right", 3], + ] as [string, number][]).forEach(([position, button]) => { + test(`should pass accessibility tests for Positioning story when position is set to ${position}`, async ({ + mount, + page, + }) => { + await mount(); + + await getDataElementByValue(page, "main-text").nth(button).click(); + await expect(page.getByRole("tooltip")).toBeVisible(); + await checkAccessibility(page); + }); + }); + + test(`should pass accessibility tests for LargeTooltip story`, async ({ + mount, + page, + }) => { + await mount(); + + await getDataElementByValue(page, "main-text").click(); + await expect(page.getByRole("tooltip")).toBeVisible(); + await checkAccessibility(page); + }); + + ["undefined", "error"].forEach((type) => { + test(`should pass accessibility tests when type is set to ${type}`, async ({ + mount, + page, + }) => { + await mount(); + + await page.getByText("target").click(); + await expect(page.getByRole("tooltip")).toBeVisible(); + await checkAccessibility(page); + }); + }); + + test(`should pass accessibility tests for ColorOverrides story`, async ({ + mount, + page, + }) => { + await mount(); + + await getDataElementByValue(page, "main-text").click(); + await expect(page.getByRole("tooltip")).toBeVisible(); + await checkAccessibility(page); + }); +});