diff --git a/src/components/tabs/__internal__/tab-title/tab-title.component.tsx b/src/components/tabs/__internal__/tab-title/tab-title.component.tsx
index 3822909e46..2bb97a664c 100644
--- a/src/components/tabs/__internal__/tab-title/tab-title.component.tsx
+++ b/src/components/tabs/__internal__/tab-title/tab-title.component.tsx
@@ -175,6 +175,7 @@ const TabTitle = React.forwardRef(
const titleChildren = (
<>
)}
diff --git a/src/components/tabs/__internal__/tab-title/tab-title.spec.tsx b/src/components/tabs/__internal__/tab-title/tab-title.spec.tsx
deleted file mode 100644
index 4ae7caf6de..0000000000
--- a/src/components/tabs/__internal__/tab-title/tab-title.spec.tsx
+++ /dev/null
@@ -1,1417 +0,0 @@
-import React from "react";
-import { mount, ReactWrapper, shallow, ShallowWrapper } from "enzyme";
-import { act } from "react-dom/test-utils";
-import TabTitle, { TabTitleProps } from "./tab-title.component";
-import {
- StyledTabTitleButton,
- StyledTabTitleLink,
- StyledTitleContent,
- StyledLayoutWrapper,
- StyledSelectedIndicator,
-} from "./tab-title.style";
-import { sageTheme } from "../../../../style/themes";
-import { assertStyleMatch } from "../../../../__spec_helper__/__internal__/test-utils";
-import ValidationIcon from "../../../../__internal__/validations/validation-icon.component";
-import StyledValidationIcon from "../../../../__internal__/validations/validation-icon.style";
-import Icon from "../../../icon";
-import StyledIcon from "../../../icon/icon.style";
-import Tooltip from "../../../tooltip";
-import { ThemeObject } from "../../../../style/themes/base";
-
-function render(
- props: Partial & {
- theme?: Partial;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ref?: React.ForwardedRef;
- } = {},
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- renderer: any = shallow,
- container = {}
-) {
- return renderer(
- {}}
- onKeyDown={() => {}}
- title="Tab Title 1"
- dataTabId="uniqueid1"
- {...props}
- />,
- container
- );
-}
-
-const TEST_SIBLINGS = [foo, bar];
-
-describe("TabTitle", () => {
- let wrapper: ReactWrapper | ShallowWrapper;
-
- let globalOpenMock: jest.SpyInstance;
-
- beforeEach(() => {
- globalOpenMock = jest.spyOn(global, "open").mockImplementation(() => null);
- });
-
- afterEach(() => {
- globalOpenMock.mockClear();
- });
-
- it("renders as expected", () => {
- assertStyleMatch(
- {
- backgroundColor: "transparent",
- display: "inline-block",
- fontWeight: "500",
- height: "var(--sizing500)",
- },
- render({}, mount).find(StyledTabTitleButton)
- );
- });
-
- it("renders a title as its child with a text passed as a prop", () => {
- const firstTabTitle = "Tab Title 1";
- wrapper = render();
- expect(wrapper.children()).toHaveLength(1);
- expect(wrapper.children().text()).toEqual(firstTabTitle);
- });
-
- it("contains custom className if passed as a prop", () => {
- wrapper = render({ className: "class" });
- expect(wrapper.find(".class").exists()).toEqual(true);
- });
-
- describe("attributes", () => {
- it('role equals "tab"', () => {
- wrapper = render();
- expect(wrapper.find("[role='tab']").exists()).toEqual(true);
- });
- it('data-element equals "select-tab"', () => {
- wrapper = render();
- expect(wrapper.find("[data-element='select-tab']").exists()).toEqual(
- true
- );
- });
- it("data-tabid equals tabId", () => {
- wrapper = render();
- expect(wrapper.find("[data-tabid='uniqueid1']").exists()).toEqual(true);
- });
- it("when 'data-role' is set, pass to title element", () => {
- wrapper = render({ "data-role": "foobar" });
- expect(wrapper.find("[data-role='foobar']").exists()).toBeTruthy();
- });
- });
-
- describe('when size is set to "large"', () => {
- it("applies proper styling", () => {
- wrapper = render({ size: "large" }, mount);
- assertStyleMatch(
- { padding: "14px 24px" },
- wrapper.find(StyledTitleContent)
- );
- assertStyleMatch(
- { height: "var(--sizing600)" },
- wrapper.find(StyledTabTitleButton)
- );
- });
- });
-
- describe("when `href` provided", () => {
- it("should trigger open in new tab if pressed with Enter or Space", () => {
- wrapper = render({ href: "randomUrl" });
-
- wrapper
- .find(StyledTabTitleLink)
- .props()
- .onKeyDown({ key: " ", stopPropagation: () => {} });
- expect(globalOpenMock).toHaveBeenCalledWith("randomUrl", "_blank");
- });
-
- it("should trigger open in new tab if clicked", () => {
- wrapper = mount(
- {}}
- onKeyDown={() => {}}
- >
-
-
- );
-
- wrapper
- .find(StyledTabTitleLink)
- .props()
- .onClick({ stopPropagation: () => {}, preventDefault: () => {} });
- expect(globalOpenMock).toHaveBeenCalledWith("randomUrl", "_blank");
- });
- });
-
- describe("when `href` is not provided", () => {
- it("should not trigger open in new tab if pressed with Enter or Space", () => {
- wrapper = render({ onKeyDown: () => {} });
-
- wrapper
- .find(StyledTabTitleButton)
- .props()
- .onKeyDown({ key: " ", stopPropagation: () => {} });
- expect(globalOpenMock).not.toHaveBeenCalled();
- });
- });
-
- describe('when position is "top', () => {
- describe("with borders", () => {
- it("applies proper styling", () => {
- wrapper = render({ size: "large", borders: true }, mount);
-
- assertStyleMatch(
- {
- borderTop: "1px solid var(--colorsActionMinor100)",
- borderLeft: "1px solid var(--colorsActionMinor100)",
- borderRight: "1px solid var(--colorsActionMinor100)",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- marginLeft: "-1px",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":nth-of-type(n + 1):not(:first-of-type)" }
- );
- });
-
- it('applies proper styling when size is "large" and isTabSelected is true', () => {
- wrapper = render(
- { size: "large", borders: true, isTabSelected: true },
- mount
- );
-
- assertStyleMatch(
- { paddingBottom: "9px" },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- bottom: "0px",
- left: "0px",
- right: "0px",
- boxShadow:
- "inset 0px calc(-1 * var(--sizing050)) 0px var(--colorsActionMajor500)",
- },
- wrapper.find(StyledSelectedIndicator)
- );
- });
-
- it('applies proper styling when size is not "large" and isTabSelected is true', () => {
- wrapper = render(
- { borders: true, isTabSelected: true, size: "default" },
- mount
- );
-
- assertStyleMatch(
- { padding: "10px 16px" },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- bottom: "0px",
- left: "0px",
- boxShadow:
- "inset 0px calc(-1 * var(--sizing050)) 0px var(--colorsActionMajor500)",
- height: "var(--sizing050)",
- },
- wrapper.find(StyledSelectedIndicator)
- );
- });
- });
- });
-
- describe('when position prop is set to "left"', () => {
- it("applies proper styles", () => {
- wrapper = render({ position: "left" }, mount);
- assertStyleMatch(
- {
- backgroundColor: "transparent",
- borderBottom: "0px",
- borderRight: "2px solid var(--colorsActionMinor100)",
- display: "flex",
- height: "auto",
- marginLeft: "0px",
- },
- wrapper.find(StyledTabTitleButton)
- );
-
- assertStyleMatch(
- {
- background: "var(--colorsActionMinor100)",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":hover" }
- );
-
- assertStyleMatch(
- {
- marginTop: "0",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":first-child" }
- );
- });
-
- it("renders as expected when `align='left'`", () => {
- assertStyleMatch(
- { justifyContent: "flex-start", textAlign: "left" },
- render({ position: "left", align: "left" }, mount).find(
- StyledTitleContent
- )
- );
- });
-
- it("renders as expected when `align='right'`", () => {
- assertStyleMatch(
- { justifyContent: "flex-end", textAlign: "right" },
- render({ align: "right", position: "left" }, mount).find(
- StyledTitleContent
- )
- );
- });
-
- it('applies proper styling when size is "large"', () => {
- wrapper = render({ position: "left", size: "large" }, mount);
-
- assertStyleMatch(
- {
- padding: "14px 24px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- describe("with borders", () => {
- it.each(["default", "large"])(
- "applies proper styling when isTabSelected is %s",
- () => {
- wrapper = render(
- { borders: true, isTabSelected: true, position: "left" },
- mount
- );
-
- assertStyleMatch(
- {
- borderTop: "1px solid var(--colorsActionMinor100)",
- borderLeft: "1px solid var(--colorsActionMinor100)",
- borderBottom: "1px solid var(--colorsActionMinor100)",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- marginTop: "-1px",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":nth-of-type(n + 1):not(:first-of-type)" }
- );
-
- assertStyleMatch(
- {
- top: "0px",
- right: "0px",
- bottom: "0px",
- boxShadow:
- "inset calc(-1 * var(--sizing050)) 0px 0px 0px var(--colorsActionMajor500)",
- },
- wrapper.find(StyledSelectedIndicator)
- );
- }
- );
- });
- });
-
- describe("when tab is selected", () => {
- it("has aria-selected attribute set to true", () => {
- wrapper = render({ isTabSelected: true });
- expect(wrapper.find("[aria-selected=true]").exists()).toEqual(true);
- });
-
- it("applies proper styling", () => {
- wrapper = render({ isTabSelected: true }, mount);
- assertStyleMatch(
- {
- color: "var(--colorsActionMajorYin090)",
- backgroundColor: "var(--colorsActionMajorYang100)",
- },
- wrapper.find(StyledTabTitleButton)
- );
-
- assertStyleMatch(
- {
- backgroundColor: "var(--colorsActionMajorYang100)",
- borderBottomColor: "var(--colorsActionMajor500)",
- color: "var(--colorsActionMajorYin090)",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":hover" }
- );
- });
-
- it("does not apply selected styling", () => {
- wrapper = render({ isTabSelected: true, error: true }, mount);
-
- expect(wrapper.find(StyledSelectedIndicator).exists()).toBeFalsy();
- });
-
- it("does not apply selected styling when it has error or warning when size is large", () => {
- wrapper = render(
- { isTabSelected: true, error: true, size: "large" },
- mount
- );
-
- expect(wrapper.find(StyledSelectedIndicator).exists()).toBeFalsy();
- });
-
- describe("when position prop is set to left", () => {
- it("applies proper styling", () => {
- wrapper = render({ position: "left", isTabSelected: true }, mount);
-
- assertStyleMatch(
- {
- backgroundColor: "var(--colorsActionMajorYang100)",
- },
- wrapper.find(StyledTabTitleButton)
- );
-
- assertStyleMatch(
- {
- backgroundColor: "var(--colorsActionMajorYang100)",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":hover" }
- );
- });
-
- it("does not apply selected styling when it has error or warning", () => {
- wrapper = render(
- { isTabSelected: true, error: true, position: "left" },
- mount
- );
-
- expect(wrapper.find(StyledSelectedIndicator).exists()).toBeFalsy();
- });
-
- it("does not apply selected styling when it has error or warning and size is large", () => {
- wrapper = render(
- { isTabSelected: true, error: true, position: "left", size: "large" },
- mount
- );
-
- expect(wrapper.find(StyledSelectedIndicator).exists()).toBeFalsy();
- });
- });
- });
-
- describe("when tab is not selected", () => {
- it("has aria-selected attribute set to false", () => {
- wrapper = render({ isTabSelected: false });
- expect(wrapper.find("[aria-selected=false]").exists()).toEqual(true);
- });
-
- it("applies proper styling", () => {
- wrapper = render({ isTabSelected: false }, mount);
- assertStyleMatch(
- {
- color: "var(--colorsActionMinorYin090)",
- },
- wrapper.find(StyledTabTitleButton)
- );
-
- assertStyleMatch(
- {
- background: "var(--colorsActionMinor100)",
- color: "var(--colorsActionMinorYin090)",
- outline: "none",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":hover" }
- );
- });
- });
-
- describe("when title has siblings", () => {
- it('renders them as expected when titlePosition is "before"', () => {
- wrapper = render(
- {
- title: "Tab 1",
- siblings: TEST_SIBLINGS,
- titlePosition: "before",
- },
- mount
- );
-
- expect(wrapper.find(StyledTitleContent).props().hasSiblings).toEqual(
- true
- );
- expect(
- wrapper.find(StyledTitleContent).props().children[0][0].props.children
- ).toEqual("Tab 1");
- assertStyleMatch(
- { padding: "10px 16px" },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders them as expected when titlePosition is "after"', () => {
- wrapper = render(
- {
- title: "Tab 1",
- siblings: TEST_SIBLINGS,
- titlePosition: "after",
- },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().children[0][1].props.children
- ).toEqual("Tab 1");
- assertStyleMatch(
- {
- padding: "10px 16px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('does not adjust padding when isTabSelected is true and position is "left"', () => {
- wrapper = render(
- {
- title: "Tab 1",
- siblings: TEST_SIBLINGS,
- titlePosition: "before",
- isTabSelected: true,
- position: "left",
- },
- mount
- );
-
- expect(wrapper.find(StyledTitleContent).props().hasSiblings).toEqual(
- true
- );
- expect(
- wrapper.find(StyledTitleContent).props().children[0][0].props.children
- ).toEqual("Tab 1");
- assertStyleMatch(
- { padding: "10px 16px" },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders as expected when size is "large"', () => {
- wrapper = render(
- {
- title: "Tab 1",
- siblings: TEST_SIBLINGS,
- titlePosition: "before",
- size: "large",
- },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().children[0][0].props.children
- ).toEqual("Tab 1");
- assertStyleMatch(
- {
- padding: "14px 24px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders as expected when size is "large" and position is "left"', () => {
- wrapper = render(
- {
- title: "Tab 1",
- siblings: TEST_SIBLINGS,
- titlePosition: "before",
- size: "large",
- position: "left",
- },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().children[0][0].props.children
- ).toEqual("Tab 1");
- assertStyleMatch(
- {
- padding: "14px 24px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
- });
-
- describe("when tab has info", () => {
- describe('when positioned "top"', () => {
- it("applies proper styling", () => {
- wrapper = render({ info: true }, mount);
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it('applies proper styling when size is "large"', () => {
- wrapper = render({ info: true, size: "large" }, mount);
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it("renders the ValidationIcon", () => {
- wrapper = render({ info: true, infoMessage: "foo" }, mount);
- const icon = wrapper.find(ValidationIcon);
-
- expect(icon.exists()).toBeTruthy();
- expect(icon.props().info).toEqual("foo");
-
- assertStyleMatch({ zIndex: "10" }, wrapper.find(StyledLayoutWrapper), {
- modifier: `${StyledValidationIcon}`,
- });
-
- assertStyleMatch(
- {
- height: "16px",
- top: "3px",
- left: "-2px",
- },
- wrapper.find(StyledLayoutWrapper),
- {
- modifier: `${StyledValidationIcon} ${StyledIcon}`,
- }
- );
- });
- });
-
- describe('when positioned "left"', () => {
- it('applies proper styling when position is "left"', () => {
- wrapper = render({ position: "left", info: true }, mount);
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- paddingRight: "18px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- paddingRight: "18px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it('applies proper styling when size is "large"', () => {
- wrapper = render(
- { position: "left", info: true, size: "large" },
- mount
- );
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- paddingRight: "26px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticInfo500)",
- paddingRight: "26px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
- });
- });
-
- describe("when tab has warning", () => {
- describe('when positioned "top"', () => {
- it("applies proper styling", () => {
- wrapper = render({ warning: true }, mount);
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it('applies proper styling when size is "large"', () => {
- wrapper = render({ warning: true, size: "large" }, mount);
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it("renders the ValidationIcon", () => {
- wrapper = render({ warning: true, warningMessage: "foo" }, mount);
- const icon = wrapper.find(ValidationIcon);
-
- expect(icon.exists()).toBeTruthy();
- expect(icon.props().warning).toEqual("foo");
-
- assertStyleMatch({ zIndex: "10" }, wrapper.find(StyledLayoutWrapper), {
- modifier: `${StyledValidationIcon}`,
- });
-
- assertStyleMatch(
- {
- height: "16px",
- top: "3px",
- left: "-2px",
- },
- wrapper.find(StyledLayoutWrapper),
- {
- modifier: `${StyledValidationIcon} ${StyledIcon}`,
- }
- );
- });
- });
-
- describe('when positioned "left"', () => {
- it('applies proper styling when position is "left"', () => {
- wrapper = render({ position: "left", warning: true }, mount);
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- paddingRight: "18px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- paddingRight: "18px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it('applies proper styling when size is "large"', () => {
- wrapper = render(
- { position: "left", warning: true, size: "large" },
- mount
- );
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- paddingRight: "26px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "1px solid",
- outlineOffset: "-1px",
- outlineColor: "var(--colorsSemanticCaution500)",
- paddingRight: "26px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
- });
- });
-
- describe("when tab has error", () => {
- describe('when positioned "top"', () => {
- it("applies proper styling", () => {
- wrapper = render({ error: true }, mount);
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it('applies proper styling when size is "large"', () => {
- wrapper = render({ error: true, size: "large" }, mount);
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderBottomColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it("renders the ValidationIcon", () => {
- wrapper = render({ error: true, errorMessage: "foo" }, mount);
- const icon = wrapper.find(ValidationIcon);
- expect(icon.exists()).toBeTruthy();
- expect(icon.props().error).toEqual("foo");
-
- assertStyleMatch({ zIndex: "10" }, wrapper.find(StyledLayoutWrapper), {
- modifier: `${StyledValidationIcon}`,
- });
-
- assertStyleMatch(
- {
- height: "16px",
- top: "3px",
- left: "-2px",
- },
- wrapper.find(StyledLayoutWrapper),
- {
- modifier: `${StyledValidationIcon} ${StyledIcon}`,
- }
- );
- });
- });
-
- describe('when positioned "left"', () => {
- it("applies proper styling", () => {
- wrapper = render({ position: "left", error: true }, mount);
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- paddingRight: "18px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- paddingRight: "18px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it('applies proper styling when size is "large"', () => {
- wrapper = render(
- { position: "left", error: true, size: "large" },
- mount
- );
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- paddingRight: "26px",
- zIndex: "2",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- borderRightColor: "transparent",
- outline: "2px solid var(--colorsSemanticNegative500)",
- outlineOffset: "-2px",
- paddingRight: "26px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
- });
- });
-
- describe("noLeftBorder", () => {
- it('sets border-left to "none"', () => {
- wrapper = render(
- {
- theme: sageTheme,
- size: "default",
- borders: true,
- noLeftBorder: true,
- },
- mount
- );
-
- assertStyleMatch(
- { borderLeft: "none" },
- wrapper.find(StyledTitleContent)
- );
- });
- });
-
- describe("noRightBorder", () => {
- it('sets border-right to "none"', () => {
- wrapper = render(
- {
- theme: sageTheme,
- size: "default",
- borders: true,
- noRightBorder: true,
- },
- mount
- );
-
- assertStyleMatch(
- { borderRight: "none" },
- wrapper.find(StyledTitleContent)
- );
- });
- });
-
- describe("setting alternateStyling prop", () => {
- describe("when TabTitle is selected, focused or hovered", () => {
- it.each(["default", "large"] as const)(
- "applies the correct background-color when size is %s",
- (size) => {
- wrapper = render({ alternateStyling: true, size }, mount);
- assertStyleMatch(
- {
- backgroundColor: "var(--colorsActionMinor200)",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":focus" }
- );
-
- assertStyleMatch(
- {
- backgroundColor: "var(--colorsActionMinor250)",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":hover" }
- );
-
- assertStyleMatch(
- {
- backgroundColor: "var(--colorsActionMinor200)",
- },
- render(
- { alternateStyling: true, isTabSelected: true, size },
- mount
- ).find(StyledTabTitleButton)
- );
- }
- );
-
- it('overrides the border-right-color when position is "left"', () => {
- wrapper = render({ alternateStyling: true, position: "left" }, mount);
-
- assertStyleMatch(
- {
- borderRightColor: "var(--colorsActionMinor100)",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":hover" }
- );
- });
-
- it("applies proper styling when borders prop is true", () => {
- wrapper = render(
- {
- borders: true,
- isTabSelected: true,
- position: "left",
- alternateStyling: true,
- },
- mount
- );
-
- assertStyleMatch(
- {
- borderTop: "1px solid var(--colorsActionMinor100)",
- borderLeft: "1px solid var(--colorsActionMinor100)",
- borderBottom: "1px solid var(--colorsActionMinor100)",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('overrides the border-right-color when position is "left" and isTabSelected is true', () => {
- wrapper = render(
- { alternateStyling: true, position: "left", isTabSelected: true },
- mount
- );
-
- assertStyleMatch(
- {
- borderRightColor: "var(--colorsActionMinor100)",
- },
- wrapper.find(StyledTabTitleButton)
- );
-
- assertStyleMatch(
- {
- borderRightColor: "var(--colorsActionMinor100)",
- },
- wrapper.find(StyledTabTitleButton),
- { modifier: ":hover" }
- );
- });
- });
- });
-
- describe("Click event on title content", () => {
- it("calls the handler", () => {
- const onClick = jest.fn();
- const stopPropagation = jest.fn();
- const preventDefault = jest.fn();
- const customEvent = {
- preventDefault,
- stopPropagation,
- target: { dataset: { tabid: "uniqueid1" } },
- };
- wrapper = render(
- { onClick, ref: { current: { focus: jest.fn() } } },
- mount
- );
-
- wrapper
- .find(StyledTabTitleButton)
- .props()
- .onClick({ stopPropagation, target: {}, preventDefault });
- expect(onClick).toHaveBeenCalledWith(customEvent);
- });
- });
-
- describe("customLayout", () => {
- describe('when position is "top"', () => {
- it("renders the correct styles when size is default", () => {
- wrapper = render({ customLayout: foo
}, mount);
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- display: "flex",
- padding: "0px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders the correct styles when size is "default" and isTabSelected is true', () => {
- wrapper = render(
- { customLayout: foo
, isTabSelected: true },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- paddingBottom: "0px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders the correct styles when size is "default" and has error', () => {
- wrapper = render({ customLayout: foo
, error: true }, mount);
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- paddingBottom: "2px",
- paddingRight: "14px",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- paddingBottom: "2px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- it('renders the correct styles when size is "large"', () => {
- wrapper = render(
- { customLayout: foo
, size: "large" },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- display: "flex",
- padding: "2px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders the correct styles when size is "large" and has warning', () => {
- wrapper = render(
- { customLayout: foo
, size: "large", warning: true },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- paddingBottom: "4px",
- paddingRight: "18px",
- },
- wrapper.find(StyledTitleContent)
- );
-
- assertStyleMatch(
- {
- paddingBottom: "4px",
- },
- wrapper.find(StyledTitleContent),
- { modifier: ":hover" }
- );
- });
-
- describe('when position is "left"', () => {
- it('renders the correct styles when size is "default"', () => {
- wrapper = render(
- { customLayout: foo
, position: "left" },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- display: "flex",
- padding: "0px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders the correct styles when size is "default" and isTabSelected is true', () => {
- wrapper = render(
- {
- customLayout: foo
,
- position: "left",
- isTabSelected: true,
- },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- paddingRight: "0px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders the correct styles when size is "default" and has error', () => {
- wrapper = render(
- { customLayout: foo
, error: true, position: "left" },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- paddingRight: "18px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders the correct styles when size is "large"', () => {
- wrapper = render(
- { customLayout: foo
, position: "left", size: "large" },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- display: "flex",
- padding: "2px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
-
- it('renders the correct styles when size is "large" and has warning', () => {
- wrapper = render(
- {
- customLayout: foo
,
- position: "left",
- size: "large",
- warning: true,
- },
- mount
- );
-
- expect(
- wrapper.find(StyledTitleContent).props().hasCustomLayout
- ).toEqual(true);
-
- assertStyleMatch(
- {
- paddingRight: "26px",
- },
- wrapper.find(StyledTitleContent)
- );
- });
- });
- });
- });
-
- describe("TabTitleContext", () => {
- let container: HTMLDivElement | null;
-
- beforeEach(() => {
- container = document.createElement("div");
- container.id = "enzymeContainer";
- document.body.appendChild(container);
-
- wrapper = render(
- {
- siblings: [],
- ref: { current: null },
- },
- mount,
- {
- attachTo: document.getElementById("enzymeContainer"),
- }
- );
- });
-
- afterEach(() => {
- if (container && container.parentNode) {
- container.parentNode.removeChild(container);
- }
-
- container = null;
- });
-
- it("sets showTooltip to true when the TabTitle is focused", () => {
- act(() => {
- wrapper.find(StyledTabTitleButton).prop("onFocus")();
- });
-
- expect(wrapper.update().find(Tooltip).prop("isVisible")).toBe(true);
- });
-
- it("sets showTooltip to true when the TabTitle is hovered", () => {
- act(() => {
- wrapper.find(StyledTabTitleButton).prop("onMouseOver")();
- });
-
- expect(wrapper.update().find(Tooltip).prop("isVisible")).toBe(true);
- });
-
- it("sets showTooltip to false when the TabTitle is blurred", () => {
- act(() => {
- wrapper.find(StyledTabTitleButton).prop("onFocus")();
- wrapper.update().find(StyledTabTitleButton).prop("onBlur")();
- });
-
- expect(wrapper.update().find(Tooltip).prop("isVisible")).toBe(false);
- });
-
- it("sets showTooltip to false when the TabTitle is not hovered", () => {
- act(() => {
- wrapper.find(StyledTabTitleButton).prop("onMouseOver")();
- wrapper.update().find(StyledTabTitleButton).prop("onMouseLeave")();
- });
-
- expect(wrapper.update().find(Tooltip).prop("isVisible")).toBe(false);
- });
-
- it("does not set showTooltip to false when TabTitle is blurred but still hovered with mouse", () => {
- act(() => {
- wrapper.find(StyledTabTitleButton).prop("onMouseOver")();
- wrapper.update().find(StyledTabTitleButton).prop("onBlur")();
- });
-
- expect(wrapper.update().find(Tooltip).prop("isVisible")).toBe(true);
- });
- });
-
- it.each(["top", "left"])(
- "has the expected border radius styling when position is %s",
- (position) => {
- const radiusStyling =
- position === "top"
- ? {
- borderTopLeftRadius: "var(--borderRadius100)",
- borderTopRightRadius: "var(--borderRadius100)",
- borderBottomRightRadius: "var(--borderRadius000)",
- borderBottomLeftRadius: "var(--borderRadius000)",
- }
- : {
- borderTopLeftRadius: "var(--borderRadius100)",
- borderTopRightRadius: "var(--borderRadius000)",
- borderBottomRightRadius: "var(--borderRadius000)",
- borderBottomLeftRadius: "var(--borderRadius100)",
- };
- assertStyleMatch(
- radiusStyling,
- render({ position }, mount).find(StyledTitleContent)
- );
- }
- );
-});
diff --git a/src/components/tabs/__internal__/tab-title/tab-title.test.tsx b/src/components/tabs/__internal__/tab-title/tab-title.test.tsx
new file mode 100644
index 0000000000..ca61fb6135
--- /dev/null
+++ b/src/components/tabs/__internal__/tab-title/tab-title.test.tsx
@@ -0,0 +1,612 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import TabTitle from "./tab-title.component";
+
+test("renders the component as a tab with text passed as `title` prop", () => {
+ render(
+ {}} onKeyDown={() => {}} />
+ );
+
+ const tabTitle = screen.getByRole("tab");
+ expect(tabTitle).toBeVisible();
+ expect(tabTitle).toHaveTextContent("example title");
+});
+
+test("accepts `className` as a prop", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ const tabTitle = screen.getByRole("tab");
+ expect(tabTitle).toHaveClass("class1", "class2");
+});
+
+test("renders with the expected `data-element` attribute", () => {
+ render( {}} onKeyDown={() => {}} />);
+
+ expect(screen.getByRole("tab")).toHaveAttribute("data-element", "select-tab");
+});
+
+test("renders with `data-tabid` attribute set to `dataTabId` prop", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByRole("tab")).toHaveAttribute(
+ "data-tabid",
+ "unique-tab-id"
+ );
+});
+
+test("renders with `data-role` attribute set to the prop value", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByRole("tab")).toHaveAttribute(
+ "data-role",
+ "custom-data-role"
+ );
+});
+
+test.each([
+ ["Enter", "{Enter}"],
+ ["Space", " "],
+])(
+ "when the `href` prop is provided, the specified URL should open in a new tab if the %s key is pressed with the tab title focused",
+ async (_, key) => {
+ const globalOpenMock = jest
+ .spyOn(global, "open")
+ .mockImplementation(() => null);
+ const user = userEvent.setup();
+ render(
+ {}} onKeyDown={() => {}} />
+ );
+
+ screen.getByRole("tab").focus();
+ await user.keyboard(key);
+
+ expect(globalOpenMock).toHaveBeenCalledWith("randomUrl", "_blank");
+ globalOpenMock.mockRestore();
+ }
+);
+
+test("when the `href` prop is provided, the specified URL should open in a new tab on click", async () => {
+ const globalOpenMock = jest
+ .spyOn(global, "open")
+ .mockImplementation(() => null);
+ const user = userEvent.setup();
+ render( {}} onKeyDown={() => {}} />);
+
+ await user.click(screen.getByRole("tab"));
+
+ expect(globalOpenMock).toHaveBeenCalledWith("randomUrl", "_blank");
+ globalOpenMock.mockRestore();
+});
+
+test.each([
+ ["Enter", "{Enter}"],
+ ["Space", " "],
+])(
+ "when the `href` prop is not provided, no new tab should be opened when %s is pressed",
+ async (_, key) => {
+ const globalOpenMock = jest
+ .spyOn(global, "open")
+ .mockImplementation(() => null);
+ const user = userEvent.setup();
+ render( {}} onKeyDown={() => {}} />);
+
+ screen.getByRole("tab").focus();
+ await user.keyboard(key);
+
+ expect(globalOpenMock).not.toHaveBeenCalled();
+ globalOpenMock.mockRestore();
+ }
+);
+
+test("when the `isTabSelected` prop is set, the `aria-selected` attribute is set to `true`", () => {
+ render( {}} onKeyDown={() => {}} />);
+
+ expect(screen.getByRole("tab")).toHaveAttribute("aria-selected", "true");
+});
+
+test("when the `isTabSelected` prop is not set, the `aria-selected` attribute is not set", () => {
+ render( {}} onKeyDown={() => {}} />);
+
+ expect(screen.getByRole("tab")).not.toHaveAttribute("aria-selected");
+});
+
+test("`siblings` are rendered after the title when `titlePosition` is `before`", () => {
+ render(
+ bar}
+ titlePosition="before"
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("sibling")).toBeVisible();
+ expect(screen.getByTestId("sibling")).toHaveTextContent("bar");
+ expect(screen.getByRole("tab")).toHaveTextContent("foobar");
+});
+
+test("`siblings` are rendered before the title when `titlePosition` is `after`", () => {
+ render(
+ bar}
+ titlePosition="after"
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("sibling")).toBeVisible();
+ expect(screen.getByTestId("sibling")).toHaveTextContent("bar");
+ expect(screen.getByRole("tab")).toHaveTextContent("barfoo");
+});
+
+test("`siblings` are rendered after the title when `titlePosition` is not passed", () => {
+ render(
+ bar}
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("sibling")).toBeVisible();
+ expect(screen.getByTestId("sibling")).toHaveTextContent("bar");
+ expect(screen.getByRole("tab")).toHaveTextContent("foobar");
+});
+
+test("calls the `onClick` prop when clicked", async () => {
+ const user = userEvent.setup();
+ const onClick = jest.fn();
+ render(
+ {}} />
+ );
+
+ await user.click(screen.getByRole("tab"));
+
+ expect(onClick).toHaveBeenCalledTimes(1);
+ expect(onClick).toHaveBeenCalledWith(
+ expect.objectContaining({
+ target: expect.objectContaining({ dataset: { tabid: "uniqueid1" } }),
+ })
+ );
+});
+
+test.each(["error", "warning", "info"])(
+ "displays a tooltip on focus when %s validation is failed",
+ (validationType) => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ {...{
+ [`${validationType}Message`]: "validation message",
+ [validationType]: true,
+ }}
+ />
+ );
+
+ screen.getByRole("tab").focus();
+
+ expect(screen.getByTestId("tooltip")).toBeVisible();
+ expect(screen.getByTestId("tooltip")).toHaveTextContent(
+ "validation message"
+ );
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "hides the tooltip on blur when %s validation is failed",
+ async (validationType) => {
+ const user = userEvent.setup();
+ render(
+ {}}
+ onKeyDown={() => {}}
+ {...{
+ [`${validationType}Message`]: "validation message",
+ [validationType]: true,
+ }}
+ />
+ );
+
+ screen.getByRole("tab").focus();
+ await user.tab();
+
+ expect(screen.queryByTestId("tooltip")).not.toBeInTheDocument();
+ expect(screen.queryByText("validation message")).not.toBeInTheDocument();
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "displays a tooltip on hover when %s validation is failed",
+ async (validationType) => {
+ const user = userEvent.setup();
+ render(
+ {}}
+ onKeyDown={() => {}}
+ {...{
+ [`${validationType}Message`]: "validation message",
+ [validationType]: true,
+ }}
+ />
+ );
+
+ await user.hover(screen.getByRole("tab"));
+
+ expect(screen.getByTestId("tooltip")).toBeVisible();
+ expect(screen.getByTestId("tooltip")).toHaveTextContent(
+ "validation message"
+ );
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "hides the tooltip on mouse leave when %s validation is failed",
+ async (validationType) => {
+ const user = userEvent.setup();
+ render(
+ {}}
+ onKeyDown={() => {}}
+ {...{
+ [`${validationType}Message`]: "validation message",
+ [validationType]: true,
+ }}
+ />
+ );
+
+ await user.hover(screen.getByRole("tab"));
+ await user.unhover(screen.getByRole("tab"));
+
+ expect(screen.queryByTestId("tooltip")).not.toBeInTheDocument();
+ expect(screen.queryByText("validation message")).not.toBeInTheDocument();
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "does not hide the tooltip on blur when already hovered if %s validation is failed",
+ async (validationType) => {
+ const user = userEvent.setup();
+ render(
+ {}}
+ onKeyDown={() => {}}
+ {...{
+ [`${validationType}Message`]: "validation message",
+ [validationType]: true,
+ }}
+ />
+ );
+
+ await user.hover(screen.getByRole("tab"));
+ screen.getByRole("tab").focus();
+ await user.tab();
+
+ expect(screen.getByTestId("tooltip")).toBeVisible();
+ expect(screen.getByTestId("tooltip")).toHaveTextContent(
+ "validation message"
+ );
+ }
+);
+
+// coverage
+test("renders the correct styles when a custom layout is used", () => {
+ render(
+ foo}
+ position="left"
+ info
+ isTabSelected
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("my-custom-layout")).toBeVisible();
+ expect(screen.getByTestId("my-custom-layout")).toHaveTextContent("foo");
+});
+
+// coverage
+test("renders as expected when `position` prop is `left` and `align` prop is `right`", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "justify-content": "flex-end",
+ });
+});
+
+// coverage
+test("renders as expected when `size` prop is `large` and `position` prop is `top`", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ padding: "14px 24px",
+ "font-size": "16px",
+ });
+});
+
+// coverage
+test("renders as expected when `size` prop is `large` and `position` prop is `left`", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ padding: "14px 24px",
+ "font-size": "16px",
+ });
+});
+
+// coverage
+test("does not apply selected styling when it has error or warning", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(
+ screen.queryByTestId("tab-selected-indicator")
+ ).not.toBeInTheDocument();
+});
+
+// coverage
+test("applies proper styling when `position` prop is `left` and there is a warning message", () => {
+ render(
+ {}} onKeyDown={() => {}} />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "border-right-color": "transparent",
+ outline: "1px solid",
+ "outline-offset": "-1px",
+ "padding-right": "18px",
+ zIndex: "2",
+ });
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "outline-color",
+ "var(--colorsSemanticCaution500)"
+ );
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "border-right-color",
+ "transparent",
+ {
+ modifier: ":hover",
+ }
+ );
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "outline",
+ "1px solid",
+ { modifier: ":hover" }
+ );
+ expect(
+ screen.getByTestId("tab-title-content")
+ ).toHaveStyleRule("outline-offset", "-1px", { modifier: ":hover" });
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "outline-color",
+ "var(--colorsSemanticCaution500)",
+ {
+ modifier: ":hover",
+ }
+ );
+ expect(
+ screen.getByTestId("tab-title-content")
+ ).toHaveStyleRule("padding-right", "18px", { modifier: ":hover" });
+});
+
+// coverage
+test("renders the correct styles when an error is present and a custom layout is used", () => {
+ render(
+ foo}
+ error
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "padding-bottom": "2px",
+ "padding-right": "14px",
+ });
+ expect(
+ screen.getByTestId("tab-title-content")
+ ).toHaveStyleRule("padding-bottom", "2px", { modifier: ":hover" });
+});
+
+// coverage
+test("renders the correct styles when size is `large`, a warning is present and a custom layout is used", () => {
+ render(
+ foo}
+ size="large"
+ warning
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "padding-bottom": "4px",
+ "padding-right": "18px",
+ });
+
+ expect(
+ screen.getByTestId("tab-title-content")
+ ).toHaveStyleRule("padding-bottom", "4px", { modifier: ":hover" });
+});
+
+// coverage
+test("renders the correct styles when size is `large`, position is `left`, a warning is present and a custom layout is used", () => {
+ render(
+ foo}
+ size="large"
+ position="left"
+ warning
+ isTabSelected
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "padding-right": "26px",
+ });
+});
+
+// coverage
+test("the `noLeftBorder` prop removes the left border", () => {
+ render( {}} onKeyDown={() => {}} />);
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "border-left": "none",
+ });
+});
+
+// coverage
+test("applies proper styling when `borders` prop is true and `position` is `left`", () => {
+ render(
+ {}} onKeyDown={() => {}} />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "border-top",
+ "1px solid var(--colorsActionMinor100)"
+ );
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "border-left",
+ "1px solid var(--colorsActionMinor100)"
+ );
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "border-bottom",
+ "1px solid var(--colorsActionMinor100)"
+ );
+});
+
+// coverage
+test("applies proper styling when `borders` prop is true and `size` is `large`", () => {
+ render(
+ {}} onKeyDown={() => {}} />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "border-top",
+ "1px solid var(--colorsActionMinor100)"
+ );
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "border-left",
+ "1px solid var(--colorsActionMinor100)"
+ );
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "border-bottom",
+ "1px solid var(--colorsActionMinor100)"
+ );
+});
+
+// coverage
+test("overrides the border-right-color when `alternateStyling` prop is `true` and `position` prop is `left`", () => {
+ render(
+ {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByRole("tab")).toHaveStyleRule(
+ "border-right-color",
+ "var(--colorsActionMinor100)",
+ { modifier: ":hover" }
+ );
+});
+
+// coverage
+test("sets border-left to `none` when the `noLeftBorder` prop is set", () => {
+ render(
+ {}} onKeyDown={() => {}} />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "border-left": "none",
+ });
+});
+
+// coverage
+test("sets border-right to `none` when the `noRightBorder` prop is set", () => {
+ render(
+ {}} onKeyDown={() => {}} />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyle({
+ "border-right": "none",
+ });
+});
+
+// coverage
+test("renders the correct styles when a custom layout is used, `size` is `default` `isTabSelected` is `true`", () => {
+ render(
+ foo}
+ isTabSelected
+ onClick={() => {}}
+ onKeyDown={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId("tab-title-content")).toHaveStyleRule(
+ "padding-bottom",
+ "0px"
+ );
+});
diff --git a/src/components/tabs/__internal__/tabs-header/tabs-header.spec.tsx b/src/components/tabs/__internal__/tabs-header/tabs-header.spec.tsx
deleted file mode 100644
index 17d41c04e1..0000000000
--- a/src/components/tabs/__internal__/tabs-header/tabs-header.spec.tsx
+++ /dev/null
@@ -1,298 +0,0 @@
-import React from "react";
-import { mount, shallow } from "enzyme";
-
-import TabsHeader, { TabHeaderProps } from "./tabs-header.component";
-import {
- StyledTabsHeaderWrapper,
- StyledTabsHeaderList,
- StyledTabsHeaderListProps,
- StyledTabsBottomBorder,
-} from "./tabs-header.style";
-import TabTitle from "../tab-title/tab-title.component";
-import { assertStyleMatch } from "../../../../__spec_helper__/__internal__/test-utils";
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function render(props: Partial = {}, renderer: any = shallow) {
- return renderer(
-
- {}} onKeyDown={() => {}} />
- {}} onKeyDown={() => {}} />
-
- );
-}
-
-function renderStyles(props: Partial = {}) {
- return mount();
-}
-
-describe("TabsHeader", () => {
- it("renders as expected", () => {
- assertStyleMatch(
- {
- display: "flex",
- cursor: "default",
- listStyle: "none",
- padding: "3px",
- },
- renderStyles()
- );
- });
-
- it("renders children correctly", () => {
- expect(render().find(TabTitle)).toHaveLength(2);
- });
-
- it("has the role of a role prop value", () => {
- expect(
- render({ role: "tablist" }).find(StyledTabsHeaderList).props().role
- ).toEqual("tablist");
- });
-
- describe("when position prop is set to left", () => {
- it("applies proper styles", () => {
- const wrapper = render({ position: "left" }, mount);
-
- assertStyleMatch(
- {
- flexDirection: "column",
- boxShadow: `none`,
- },
- wrapper.find(StyledTabsHeaderList)
- );
-
- assertStyleMatch(
- {
- minWidth: "20%",
- overflowY: "auto",
- padding: "3px",
- },
- wrapper.find(StyledTabsHeaderWrapper)
- );
- });
-
- it("applies proper styles when noRightBorder true", () => {
- assertStyleMatch(
- {
- boxShadow: "none",
- },
- renderStyles({ position: "left", noRightBorder: true })
- );
- });
-
- it("does not render the bottom border element", () => {
- const wrapper = render({ position: "left" }, mount);
- expect(wrapper.find(StyledTabsBottomBorder)).toHaveLength(0);
- });
- });
-
- describe("when position prop is set to top", () => {
- it("renders the bottom border element", () => {
- const wrapper = render({ position: "top" }, mount);
- expect(wrapper.find(StyledTabsBottomBorder)).toHaveLength(1);
- });
- });
-
- describe("when align prop is set to right", () => {
- it("applies proper styles", () => {
- assertStyleMatch(
- {
- justifyContent: "flex-end",
- textAlign: "right",
- },
- renderStyles({ align: "right" })
- );
- });
-
- it("applies proper styles when positioned left", () => {
- assertStyleMatch(
- {
- justifyContent: "flex-start",
- },
- renderStyles({ position: "left", align: "right" })
- );
- });
- });
-
- describe("when extendedLine is false", () => {
- it("applies proper styles", () => {
- assertStyleMatch(
- {
- width: "fit-content",
- },
- renderStyles({ extendedLine: false })
- );
- });
- });
-
- describe("custom target styling", () => {
- const wrapper = render({ isInSidebar: true, position: "left" }, mount);
- assertStyleMatch(
- {
- minWidth: "100%",
- },
- wrapper.find(StyledTabsHeaderWrapper)
- );
- });
-
- describe("horizontal scroll", () => {
- const mockScroll = ({
- element,
- ...values
- }: {
- element: HTMLElement;
- scrollWidth?: number;
- clientWidth?: number;
- scrollLeft?: number;
- }) => {
- Object.entries(values).forEach(([key, value]) => {
- Object.defineProperty(element, key, {
- configurable: true,
- value,
- });
- });
- };
-
- describe("when is scrollable", () => {
- it("renders before and after pseudoelements", () => {
- const wrapper = render({}, mount);
-
- const list = wrapper.find(StyledTabsHeaderList).getDOMNode();
- mockScroll({
- element: list,
- scrollWidth: 512,
- clientWidth: 256,
- });
-
- wrapper.setProps({});
-
- assertStyleMatch(
- {
- pointerEvents: "none",
- content: '""',
- backgroundRepeat: "no-repeat",
- backgroundSize: "16px 48px",
- backgroundAttachment: "scroll",
- zIndex: "1000",
- position: "sticky",
- minWidth: "16px",
- transition: "opacity 0.1s ease-in-out",
- background:
- "radial-gradient( farthest-side at 0 50%, rgba(0,0,0,0.2), rgba(0,0,0,0) )",
- backgroundPosition: "left calc(50% - 4px)",
- left: "-3px",
- marginRight: "-16px",
- },
- list,
- { modifier: ":before" }
- );
-
- assertStyleMatch(
- {
- pointerEvents: "none",
- content: '""',
- backgroundRepeat: "no-repeat",
- backgroundSize: "16px 48px",
- backgroundAttachment: "scroll",
- zIndex: "1000",
- position: "sticky",
- minWidth: "16px",
- transition: "opacity 0.1s ease-in-out",
- background:
- "radial-gradient( farthest-side at 100% 50%, rgba(0,0,0,0.2), rgba(0,0,0,0) )",
- backgroundPosition: "right calc(50% - 4px)",
- right: "-3px",
- marginLeft: "-16px",
- },
- list,
- { modifier: ":after" }
- );
- });
-
- it.each([
- [0, "0"],
- [64, "0.5"],
- [128, "1"],
- [256, "1"],
- ])(
- "renders before element with correct opacity",
- (scrollLeft, opacity) => {
- const wrapper = render({}, mount);
- const list = wrapper.find(StyledTabsHeaderList).getDOMNode();
-
- mockScroll({
- element: list,
- scrollWidth: 768,
- clientWidth: 256,
- scrollLeft,
- });
-
- wrapper.setProps({});
-
- wrapper.find(StyledTabsHeaderList).simulate("scroll");
-
- assertStyleMatch(
- {
- opacity,
- },
- list,
- { modifier: ":before" }
- );
- }
- );
-
- it.each([
- [256, "1"],
- [384, "1"],
- [448, "0.5"],
- [512, "0"],
- ])(
- "renders after element with correct opacity",
- (scrollLeft, opacity) => {
- const wrapper = render({}, mount);
- const list = wrapper.find(StyledTabsHeaderList).getDOMNode();
-
- mockScroll({
- element: list,
- scrollWidth: 768,
- clientWidth: 256,
- scrollLeft,
- });
-
- wrapper.setProps({});
-
- wrapper.find(StyledTabsHeaderList).simulate("scroll");
-
- assertStyleMatch(
- {
- opacity,
- },
- list,
- { modifier: ":after" }
- );
- }
- );
- });
-
- describe("when is not scrollable", () => {
- it("does not render before and after pseudoelements", () => {
- const wrapper = render({}, mount);
- const list = wrapper.find(StyledTabsHeaderList).getDOMNode();
-
- mockScroll({
- element: list,
- scrollWidth: 256,
- clientWidth: 256,
- });
-
- wrapper.setProps({});
-
- expect(list).not.toHaveStyleRule("min-width", "16px", {
- modifier: ":after",
- });
- expect(list).not.toHaveStyleRule("min-width", "16px", {
- modifier: ":before",
- });
- });
- });
- });
-});
diff --git a/src/components/tabs/__internal__/tabs-header/tabs-header.test.tsx b/src/components/tabs/__internal__/tabs-header/tabs-header.test.tsx
new file mode 100644
index 0000000000..a4829bdef7
--- /dev/null
+++ b/src/components/tabs/__internal__/tabs-header/tabs-header.test.tsx
@@ -0,0 +1,127 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+
+import TabsHeader from "./tabs-header.component";
+import { StyledTabsHeaderList } from "./tabs-header.style";
+import TabTitle from "../tab-title/tab-title.component";
+
+test("renders children correctly", () => {
+ render(
+
+ {}} onKeyDown={() => {}} />
+ {}} onKeyDown={() => {}} />
+
+ );
+
+ expect(screen.getAllByRole("tab")).toHaveLength(2);
+ expect(screen.getAllByRole("tab")[0]).toHaveTextContent("title-1");
+ expect(screen.getAllByRole("tab")[1]).toHaveTextContent("title-2");
+});
+
+test("accepts a `role` prop", () => {
+ render(
+
+ {}} onKeyDown={() => {}} />
+ {}} onKeyDown={() => {}} />
+
+ );
+
+ expect(screen.getByRole("tablist")).toBeVisible();
+ expect(screen.getByRole("tablist")).toHaveTextContent("title-1title-2");
+});
+
+// coverage
+test("renders before element with correct opacity when scroll position is not at the left", () => {
+ render(
+
+ {}} onKeyDown={() => {}} />
+ {}} onKeyDown={() => {}} />
+
+ );
+
+ const mockScrollValues = {
+ scrollWidth: 768,
+ clientWidth: 256,
+ scrollLeft: 64,
+ };
+
+ const tabList = screen.getByRole("tablist");
+
+ Object.entries(mockScrollValues).forEach(([key, value]) => {
+ Object.defineProperty(tabList, key, {
+ configurable: true,
+ value,
+ });
+ });
+
+ fireEvent.scroll(tabList);
+
+ expect(tabList).toHaveStyleRule("opacity", "0.5", { modifier: ":before" });
+});
+
+// coverage
+test("applies proper styles when the `extendedLine` prop is `false`", () => {
+ render(
+
+ {}} onKeyDown={() => {}} />
+ {}} onKeyDown={() => {}} />
+
+ );
+
+ expect(screen.getByRole("tablist")).toHaveStyle({ width: "fit-content" });
+});
+
+// coverage
+test("applies proper styles when the `align` prop is `right`", () => {
+ render(
+
+ {}} onKeyDown={() => {}} />
+ {}} onKeyDown={() => {}} />
+
+ );
+
+ expect(screen.getByRole("tablist")).toHaveStyle({
+ "justify-content": "flex-end",
+ "text-align": "right",
+ });
+});
+
+// coverage
+test("applies proper styles when the `noRightBorder` prop is `true` and `position` is `left`", () => {
+ render(
+
+ {}} onKeyDown={() => {}} />
+ {}} onKeyDown={() => {}} />
+
+ );
+
+ expect(screen.getByRole("tablist")).toHaveStyle({
+ "box-shadow": "none",
+ });
+});
+
+// coverage
+test("applies proper styles when the `align` prop is `right` and `position` is `left`", () => {
+ render(
+
+ {}} onKeyDown={() => {}} />
+ {}} onKeyDown={() => {}} />
+
+ );
+
+ expect(screen.getByRole("tablist")).toHaveStyle({
+ "justify-content": "flex-start",
+ });
+});
+
+// coverage - need to render the styled component directly to cover the default prop assignments
+test("renders with correct styles", () => {
+ render();
+
+ expect(screen.getByRole("tablist")).toHaveStyle({
+ display: "flex",
+ cursor: "default",
+ "list-style": "none",
+ padding: "3px",
+ });
+});
diff --git a/src/components/tabs/components.test-pw.tsx b/src/components/tabs/components.test-pw.tsx
index 7f3e1f1eae..4571836824 100644
--- a/src/components/tabs/components.test-pw.tsx
+++ b/src/components/tabs/components.test-pw.tsx
@@ -5,6 +5,7 @@ import { Checkbox } from "../checkbox";
import Box from "../box";
import Icon from "../icon";
import Pill from "../pill";
+import Button from "../button";
import DrawerSidebarContext from "../drawer/__internal__/drawer-sidebar.context";
@@ -653,3 +654,22 @@ export const Responsive = () => {
);
};
+
+export const WithUpdatingChild = () => {
+ const [updated, setUpdated] = useState(false);
+ const onButtonClick = () => setUpdated((prev) => !prev);
+
+ return (
+
+
+
+ Content for tab 1
+
+
+ Content for tab 2{updated ? "Foo" : "Bar"}
+
+
+
+
+ );
+};
diff --git a/src/components/tabs/tab/tab.spec.tsx b/src/components/tabs/tab/tab.spec.tsx
deleted file mode 100644
index af5bddab3d..0000000000
--- a/src/components/tabs/tab/tab.spec.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-import React from "react";
-import TestRenderer from "react-test-renderer";
-import { mount } from "enzyme";
-import Tab, { TabProps } from ".";
-import Textbox from "../../textbox";
-import StyledTab, { StyledTabProps } from "./tab.style";
-import {
- assertStyleMatch,
- testStyledSystemPadding,
-} from "../../../__spec_helper__/__internal__/test-utils";
-
-const updateErrors = jest.fn();
-const updateWarnings = jest.fn();
-const tabId = "uniqueid1";
-function render(props: Partial = {}) {
- return mount(
-
- TabContent 1
- TabContent 2
-
- );
-}
-
-function renderWithValidation(
- props: Partial & {
- id?: string;
- validations: Record;
- }
-) {
- return mount(
-
-
-
- );
-}
-
-function renderStyles(props: StyledTabProps = {}) {
- return TestRenderer.create();
-}
-
-describe("Tab", () => {
- let wrapper;
-
- testStyledSystemPadding((props) => (
-
- TabContent
-
- ));
-
- testStyledSystemPadding((props) => (
-
- TabContent
-
- ));
-
- it("has display property equals to none", () => {
- wrapper = renderStyles();
- assertStyleMatch(
- {
- display: "none",
- },
- wrapper.toJSON()
- );
- });
-
- it("renders its children correctly", () => {
- expect(render().find(StyledTab).find("div").children()).toHaveLength(2);
- });
-
- it("contains custom className if passed as a prop", () => {
- wrapper = render({ className: "class" });
- expect(wrapper.find(".class").exists()).toEqual(true);
- });
-
- it("has a default role if not set", () => {
- wrapper = render();
- expect(wrapper.find("[role='tabpanel']").exists()).toEqual(true);
- });
-
- it("has a custom role if provided", () => {
- wrapper = render({ role: "anotherRole" });
- expect(wrapper.find("[role='anotherRole']").exists()).toEqual(true);
- });
-
- it("sets the aria-labelledby based on ariaLabelledBy prop", () => {
- wrapper = render({ ariaLabelledby: "ariaLabelledby" });
- expect(wrapper.find("[aria-labelledby='ariaLabelledby']").exists()).toEqual(
- true
- );
- });
-
- it("does not pass title prop down to DOM element", () => {
- expect(render({}).getDOMNode().getAttribute("title")).toEqual(null);
- });
-
- describe("if `href` prop provided", () => {
- it("should not render the content", () => {
- wrapper = render({ href: "#" });
-
- expect(wrapper.find("p").exists()).toBe(false);
- });
- });
-
- describe("when a tab is selected", () => {
- it("applies display block property", () => {
- wrapper = renderStyles({
- isTabSelected: true,
- });
-
- assertStyleMatch(
- {
- display: "block",
- },
- wrapper.toJSON()
- );
- });
-
- describe("when position prop is set to left and the tab is selected", () => {
- it("applies width of 80%", () => {
- wrapper = renderStyles({
- isTabSelected: true,
- position: "left",
- });
- assertStyleMatch(
- {
- width: "calc(80% + var(--spacing075))",
- },
- wrapper.toJSON()
- );
- });
- });
- });
-
- describe("Tab validation", () => {
- describe("updateErrors", () => {
- it("calls the parent tab context with the new state", () => {
- wrapper = renderWithValidation({
- updateErrors,
- updateWarnings,
- id: "foo",
- validations: { error: true },
- });
- expect(updateErrors).toHaveBeenCalledWith(tabId, { foo: true });
- });
- });
-
- describe("updateWarnings", () => {
- it("calls the parent tab context with the new state", () => {
- wrapper = renderWithValidation({
- updateErrors,
- updateWarnings,
- id: "foo",
- validations: { warning: true },
- });
- expect(updateWarnings).toHaveBeenCalledWith(tabId, { foo: true });
- });
- });
- });
-});
diff --git a/src/components/tabs/tab/tab.test.tsx b/src/components/tabs/tab/tab.test.tsx
new file mode 100644
index 0000000000..f7cb249d83
--- /dev/null
+++ b/src/components/tabs/tab/tab.test.tsx
@@ -0,0 +1,135 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import Tab from ".";
+import Textbox from "../../textbox";
+import StyledTab from "./tab.style";
+import { testStyledSystemPadding } from "../../../__spec_helper__/__internal__/test-utils";
+
+testStyledSystemPadding((props) => (
+
+ TabContent
+
+));
+
+testStyledSystemPadding((props) => (
+
+ TabContent
+
+));
+
+test("has a role of `tabpanel` if none is specified", () => {
+ render(tab content);
+
+ expect(screen.getByText("tab content")).toHaveAttribute("role", "tabpanel");
+});
+
+test("is not visible by default", () => {
+ render(tab content);
+
+ expect(screen.getByRole("tabpanel", { hidden: true })).not.toBeVisible();
+});
+
+test("is visible when selected", () => {
+ render(
+
+ tab content
+
+ );
+
+ expect(screen.getByRole("tabpanel")).toBeVisible();
+});
+
+test("renders its children correctly", () => {
+ render(tab content);
+
+ expect(screen.getByText("tab content")).toBeInTheDocument();
+});
+
+test("passes the `className` prop to the element", () => {
+ render(
+
+ tab content
+
+ );
+
+ expect(screen.getByRole("tabpanel", { hidden: true })).toHaveClass(
+ "foo-class",
+ "bar-class"
+ );
+});
+
+test("can be given a custom role via the `role` prop", () => {
+ render(
+
+ tab content
+
+ );
+
+ expect(screen.getByText("tab content")).toHaveAttribute("role", "article");
+});
+
+test("accepts an `ariaLabelledBy` prop to set the accessible name", () => {
+ render(
+
+ tab content
+ an accessible name
+
+ );
+
+ expect(screen.getByRole("tabpanel")).toHaveAccessibleName(
+ "an accessible name"
+ );
+});
+
+test("the `title` prop is not passed to the DOM element", () => {
+ render(
+
+ tab content
+
+ );
+
+ expect(screen.getByRole("tabpanel", { hidden: true })).not.toHaveAttribute(
+ "title"
+ );
+});
+
+test("does not render the children if the `href` prop is passed", () => {
+ render(
+
+ tab content
+
+ );
+
+ expect(screen.queryByText("tab content")).not.toBeInTheDocument();
+});
+
+test("calls the `updateErrors` function prop when an error is present in a child component", () => {
+ const updateErrors = jest.fn();
+ render(
+
+ {}} id="bar" error />
+
+ );
+
+ expect(updateErrors).toHaveBeenCalledWith("foo", { bar: true });
+});
+
+test("calls the `updateWarnings` function prop when a warning is present in a child component", () => {
+ const updateWarnings = jest.fn();
+ render(
+
+ {}} id="bar" warning />
+
+ );
+
+ expect(updateWarnings).toHaveBeenCalledWith("foo", { bar: true });
+});
+
+// coverage - need to render the styled component directly to cover the default prop assignments
+test("renders with correct styles", () => {
+ render();
+
+ expect(screen.getByTestId("styled-component")).toHaveStyle({
+ display: "none",
+ });
+});
diff --git a/src/components/tabs/tabs-test.stories.tsx b/src/components/tabs/tabs-test.stories.tsx
index 2124719e92..c0f0ca4c76 100644
--- a/src/components/tabs/tabs-test.stories.tsx
+++ b/src/components/tabs/tabs-test.stories.tsx
@@ -1,9 +1,17 @@
import React from "react";
-import { Tabs, Tab, TabsProps } from ".";
+import { Tabs, Tab, TabsProps, TabProps } from ".";
+import DrawerSidebarContext from "../drawer/__internal__/drawer-sidebar.context";
+import Box from "../box";
+import { allModes } from "../../../.storybook/modes";
export default {
title: "Tabs/Test",
- includeStories: ["Default"],
+ includeStories: [
+ "Default",
+ "TabsInSidebar",
+ "TabsInSidebarPositionedLeft",
+ "WithHorizontalScrollbar",
+ ],
parameters: {
info: { disable: true },
chromatic: {
@@ -111,3 +119,173 @@ export const Default = (args: TabsProps) => {
};
Default.storyName = "default";
+
+export const TabsInSidebar = (
+ props: Partial & Partial
+) => {
+ return (
+
+
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ Content for tab 4
+
+
+ Content for tab 5
+
+
+
+
+ );
+};
+
+TabsInSidebar.storyName = "Tabs in sidebar";
+TabsInSidebar.parameters = {
+ info: { disable: true },
+ chromatic: {
+ disableSnapshot: false,
+ },
+};
+
+export const TabsInSidebarPositionedLeft = (
+ props: Partial & Partial
+) => {
+ return (
+
+
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ Content for tab 4
+
+
+ Content for tab 5
+
+
+
+
+ );
+};
+
+TabsInSidebarPositionedLeft.storyName = "Tabs in sidebar positioned left";
+TabsInSidebarPositionedLeft.parameters = {
+ info: { disable: true },
+ chromatic: {
+ disableSnapshot: false,
+ },
+};
+
+export const WithHorizontalScrollbar = () => {
+ return (
+
+ {Array.from({ length: 30 }).map((_, index) => (
+
+ Content for tab {index + 1}
+
+ ))}
+
+ );
+};
+
+WithHorizontalScrollbar.storyName = "With horizontal scrollbar";
+WithHorizontalScrollbar.parameters = {
+ chromatic: {
+ disableSnapshot: false,
+ modes: {
+ desktop: allModes.chromatic,
+ },
+ },
+};
diff --git a/src/components/tabs/tabs.component.tsx b/src/components/tabs/tabs.component.tsx
index d88d4b73a6..9b2a1a4619 100644
--- a/src/components/tabs/tabs.component.tsx
+++ b/src/components/tabs/tabs.component.tsx
@@ -182,6 +182,8 @@ const Tabs = ({
/** Handles the changing of tabs with the mouse */
const handleTabClick = (ev: React.MouseEvent) => {
+ // istanbul ignore if
+ // (code doesn't seem to be ever reached - FE-6835 raised to investigate and hopefully remove this)
if (Event.isEventType(ev, "keydown")) {
return;
}
@@ -381,6 +383,7 @@ const Tabs = ({
{
});
});
});
+
+ test("when the children of a Tab update, the selected tab does not change", async ({
+ mount,
+ page,
+ }) => {
+ await mount();
+
+ await page.getByRole("button").click();
+
+ await expect(page.getByText("Content for tab 2")).toBeVisible();
+ await expect(page.getByText("Content for tab 1")).not.toBeVisible();
+ });
});
diff --git a/src/components/tabs/tabs.spec.tsx b/src/components/tabs/tabs.spec.tsx
deleted file mode 100644
index efc031e923..0000000000
--- a/src/components/tabs/tabs.spec.tsx
+++ /dev/null
@@ -1,1269 +0,0 @@
-/* eslint-disable react/prop-types */
-import React, { useEffect, useContext } from "react";
-import { mount, shallow, MountRendererProps, ReactWrapper } from "enzyme";
-import { act } from "react-dom/test-utils";
-import TabTitle from "./__internal__/tab-title/tab-title.component";
-import { Tabs, Tab, TabsProps } from ".";
-import TabContext from "./tab/__internal__/tab.context";
-import rootTagTest from "../../__internal__/utils/helpers/tags/tags-specs";
-import StyledTabs, { StyledTabsProps } from "./tabs.style";
-import StyledTab from "./tab/tab.style";
-import { StyledTabTitleButton } from "./__internal__/tab-title/tab-title.style";
-import {
- assertStyleMatch,
- simulate,
- testStyledSystemMargin,
-} from "../../__spec_helper__/__internal__/test-utils";
-import { StyledTabsHeaderWrapper } from "./__internal__/tabs-header/tabs-header.style";
-import DrawerSidebarContext from "../drawer/__internal__/drawer-sidebar.context";
-import Textbox from "../textbox";
-import NumeralDate from "../numeral-date";
-import ValidationIcon, {
- ValidationProps,
-} from "../../__internal__/validations";
-
-const mockTarget = (tabid: string) =>
- (({ dataset: { tabid } } as unknown) as HTMLElement);
-const mockEvent = (type: string, tabId: string) =>
- (({ type, target: mockTarget(tabId) } as unknown) as React.MouseEvent<
- HTMLButtonElement | HTMLAnchorElement
- >);
-
-function render(
- props: Partial = {},
- mountOptions: MountRendererProps = {}
-) {
- return mount(
-
-
- TabContent
-
-
- TabContent
-
-
- TabContent
-
- ,
- mountOptions
- );
-}
-
-function renderStyles(props: StyledTabsProps) {
- return mount();
-}
-
-interface MockValidationProps {
- one?: string | boolean;
- two?: string | boolean;
- three?: string | boolean;
- four?: string | boolean;
-}
-
-interface TabChildrenProps extends ValidationProps {
- id: string;
- text?: string | boolean;
-}
-
-const TabChildren = ({ id, error, warning, info, text }: TabChildrenProps) => {
- const context = useContext(TabContext);
-
- useEffect(() => {
- context.setError?.(id, !!error);
- context.setWarning?.(id, !!warning);
- context.setInfo?.(id, !!info);
- }, [id, context, error, warning, info]);
-
- return (
-
- {text}
-
- );
-};
-
-type MockWrapperProps = Partial & {
- errors?: MockValidationProps;
- warnings?: MockValidationProps;
- infos?: MockValidationProps;
-};
-
-const MockWrapper = ({
- errors = {},
- warnings = {},
- infos = {},
- validationStatusOverride = undefined,
- renderHiddenTabs = true,
-}: MockWrapperProps) => {
- return (
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-describe("Tabs", () => {
- testStyledSystemMargin((props) => (
-
-
- TabContent
-
-
- ));
-
- testStyledSystemMargin((props) => (
-
-
- TabContent
-
-
- ));
-
- // TODO: move this test into playwright FE-6425
- describe("when children of a Tab update", () => {
- it("does not update the selected tab", () => {
- const MockComponent = ({
- selectedTabId,
- updateChild,
- }: {
- selectedTabId?: string;
- updateChild: boolean;
- }) => {
- return (
-
-
- TabContent
-
-
- {updateChild ? "Foo" : "Bar"}
-
-
- );
- };
- const wrapper = mount(
-
- );
-
- expect(wrapper.find(StyledTab).last().prop("isTabSelected")).toBe(true);
-
- wrapper.setProps({ updateChild: true });
-
- expect(
- wrapper.update().find(StyledTab).last().prop("isTabSelected")
- ).toBe(true);
- });
- });
-
- describe("when used with NumeralDate as a child", () => {
- it("should not throw", () => {
- expect(() => {
- mount(
-
-
-
- {}} />
-
-
- );
- }).not.toThrow();
- });
- });
-
- describe("when `headerWidth` is provided", () => {
- describe.each(["35%", "100px", "5em"])(
- "and value of %s is provided",
- (headerWidth) => {
- it("should render correct `width` in `TabsHeader` component, and `Tab` `width` should be computed based on the headerWidth", () => {
- const wrapper = mount(
-
-
- TabContent
-
-
- );
-
- assertStyleMatch(
- {
- width: headerWidth,
- },
- wrapper,
- {
- modifier: `${StyledTabsHeaderWrapper}`,
- }
- );
-
- assertStyleMatch(
- {
- width: `calc(100% - ${headerWidth})`,
- },
- wrapper,
- {
- modifier: `${StyledTab}`,
- }
- );
- });
- }
- );
- });
-
- describe('when `headerWidth` is provided, and `position="top"`', () => {
- it("should render console error", () => {
- const consoleSpy = jest
- .spyOn(global.console, "error")
- .mockImplementation(() => {});
-
- mount(
-
-
- TabContent
-
-
- );
-
- expect(consoleSpy).toHaveBeenCalledWith(
- "Invalid usage of prop headerWidth in Tabs. The headerWidth can be used only if position is set to left"
- );
-
- consoleSpy.mockRestore();
- });
- });
-
- describe("when passing custom className as a prop", () => {
- it("adds it to the classList", () => {
- const wrapper = render({ className: "class" });
- expect(wrapper.exists(".class")).toEqual(true);
- });
- });
-
- describe('When position is "left"', () => {
- it("applies proper styling", () => {
- const wrapper = renderStyles({ position: "left" });
- assertStyleMatch(
- {
- display: "flex",
- width: "100%",
- },
- wrapper
- );
- });
- });
-
- describe('When "selectedTabId" is passed a valid "tabId"', () => {
- it("displays the specified Tab", () => {
- const wrapper = render({ selectedTabId: "uniqueid2" });
-
- expect(wrapper.find(Tab).at(1).props().isTabSelected).toEqual(true);
- });
- });
-
- describe("When renderHiddenTabs", () => {
- describe("is false", () => {
- it("renders only the currently visible tab", () => {
- const tab = mount(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ).find(Tab);
-
- expect(tab).toHaveLength(1);
- expect(tab.props().isTabSelected).toEqual(true);
- expect(tab.props().title).toEqual("Tab Title 1");
- expect(tab.props().tabId).toEqual("uniqueid1");
- });
-
- it.each(["error", "warning", "info"] as const)(
- "adds the correct %s state to the tab header",
- (validation) => {
- const validationProp = {
- [`${validation}s`]: { one: true },
- };
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props()[validation]).toEqual(true);
- expect(tabTitle.at(1).props()[validation]).toEqual(false);
- }
- );
- });
-
- describe("is true", () => {
- it("returns an array of all Tab components with the first selected", () => {
- const tabs = mount(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ).find(Tab);
-
- expect(tabs).toHaveLength(3);
- expect(tabs.at(0).props().isTabSelected).toEqual(true);
- expect(tabs.at(1).props().isTabSelected).toEqual(false);
- expect(tabs.at(2).props().isTabSelected).toEqual(false);
- });
- });
- });
-
- describe("When a TabTitle has click event", () => {
- it("does nothing if triggered by keydown", () => {
- const wrapper = render();
- act(() => {
- wrapper
- .find(TabTitle)
- .at(1)
- .props()
- .onClick(mockEvent("keydown", "uniqueid2"));
- });
- wrapper.update();
- expect(wrapper.find(Tab).at(1).props().isTabSelected).toEqual(false);
- });
-
- it("updates to make the associated Tab visible", () => {
- const wrapper = render();
- act(() => {
- wrapper
- .find(TabTitle)
- .at(1)
- .props()
- .onClick(mockEvent("click", "uniqueid2"));
- });
- wrapper.update();
- expect(wrapper.find(Tab).at(1).props().isTabSelected).toEqual(true);
- });
-
- it('calls the "onTabChange" callback if one is passed on click of a TabTitle', () => {
- const onTabChange = jest.fn();
- const wrapper = render({ onTabChange });
- act(() => {
- wrapper
- .find(TabTitle)
- .at(1)
- .props()
- .onClick(mockEvent("click", "uniqueid2"));
- });
- wrapper.update();
- expect(onTabChange).toHaveBeenCalledTimes(1);
- expect(onTabChange).toHaveBeenCalledWith("uniqueid2");
- });
-
- it("updates the selected tab when a new id is passed via the 'selectedTabId' prop", () => {
- const wrapper = render({ selectedTabId: "uniqueid1" });
- wrapper.setProps({ selectedTabId: "uniqueid2" });
- wrapper.update();
- expect(wrapper.find(Tab).at(1).props().isTabSelected).toEqual(true);
- wrapper.setProps({ selectedTabId: "uniqueid1" });
- wrapper.update();
- expect(wrapper.find(Tab).at(0).props().isTabSelected).toEqual(true);
- });
-
- it("blurs the selected TabTitle when a new id is passed via the selectedTabId prop", () => {
- const container = document.createElement("div");
- container.id = "container";
- document.body.appendChild(container);
-
- const wrapper = render(
- { selectedTabId: "uniqueid1" },
- {
- attachTo: document.querySelector("#container") as HTMLElement,
- }
- );
-
- act(() => {
- (wrapper
- .find(StyledTabTitleButton)
- .at(0)
- .getDOMNode() as HTMLElement).focus();
- });
-
- expect(document.activeElement).toBe(
- wrapper.find(StyledTabTitleButton).at(0).getDOMNode()
- );
-
- wrapper.setProps({ selectedTabId: "uniqueid2" });
- wrapper.update();
-
- expect(document.activeElement).not.toBe(
- wrapper.find(StyledTabTitleButton).at(0).getDOMNode()
- );
-
- wrapper.detach();
- document.body.removeChild(container);
- });
-
- it('only calls the "onTabChange" callback when visible tabId does not match new tabId', () => {
- const onTabChange = jest.fn();
- const wrapper = render({ onTabChange, selectedTabId: "uniqueid1" });
- act(() => {
- wrapper
- .find(TabTitle)
- .at(1)
- .props()
- .onClick(mockEvent("click", "uniqueid2"));
- });
- wrapper.update();
- wrapper.setProps({ selectedTabId: "uniqueid2" });
- wrapper.update();
- expect(onTabChange).toHaveBeenCalledTimes(1);
- });
- });
-
- describe("When a key is pressed on TabTitle", () => {
- let container: HTMLDivElement | null;
- let wrapper: ReactWrapper;
-
- beforeEach(() => {
- container = document.createElement("div");
- container.id = "enzymeContainer";
- document.body.appendChild(container);
- });
-
- afterEach(() => {
- if (container && container.parentNode) {
- container.parentNode.removeChild(container);
- }
-
- container = null;
- });
-
- const getTabNode = (index: number): HTMLButtonElement =>
- wrapper
- .find(StyledTabTitleButton)
- .at(index)
- .getDOMNode() as HTMLButtonElement;
-
- describe('and the component has position "top" (default)', () => {
- it.each([0, 1, 2])(
- "focuses the next TabTitle when the right key is pressed",
- (index) => {
- wrapper = render({}, { attachTo: container });
- act(() => {
- simulate.keydown.pressArrowRight(wrapper.find(TabTitle).at(index));
- });
- wrapper.update();
- const newIndex = index === 2 ? 0 : index + 1;
- expect(getTabNode(newIndex)).toEqual(document.activeElement);
- }
- );
-
- it.each([0, 2, 1])(
- "focuses the previous TabTitle when the left key is pressed",
- (index) => {
- wrapper = render({}, { attachTo: container });
- act(() => {
- simulate.keydown.pressArrowLeft(wrapper.find(TabTitle).at(index));
- });
- wrapper.update();
- const newIndex = index === 0 ? 2 : index - 1;
- expect(getTabNode(newIndex)).toEqual(document.activeElement);
- }
- );
- });
-
- describe('and the component has position "left"', () => {
- it.each([0, 1, 2])(
- "focuses the next TabTitle when the down key is pressed",
- (index) => {
- wrapper = render({ position: "left" }, { attachTo: container });
- act(() => {
- simulate.keydown.pressArrowDown(wrapper.find(TabTitle).at(index));
- });
- wrapper.update();
- const newIndex = index === 2 ? 0 : index + 1;
- expect(getTabNode(newIndex)).toEqual(document.activeElement);
- }
- );
-
- it.each([0, 2, 1])(
- "focuses the previous TabTitle when the up key is pressed",
- (index) => {
- wrapper = render({ position: "left" }, { attachTo: container });
- act(() => {
- simulate.keydown.pressArrowUp(wrapper.find(TabTitle).at(index));
- });
- wrapper.update();
- const newIndex = index === 0 ? 2 : index - 1;
- expect(getTabNode(newIndex)).toEqual(document.activeElement);
- }
- );
- });
-
- it.each([0, 1, 2])(
- "activates the Tab when the enter key is pressed",
- (index) => {
- wrapper = render({}, { attachTo: container });
- act(() => {
- // Click event is used because thats the event handler that is used in the component
- // In the browser the click event is fired when the enter key is pressed
- // Enzyme does not simulate this behaviour correctly
- wrapper.find(StyledTabTitleButton).at(index).simulate("click");
- });
- wrapper.update();
- expect(wrapper.find(Tab).at(index).props().isTabSelected).toEqual(true);
- }
- );
-
- it.each([0, 1, 2])("does nothing if key is not an arrow key", (index) => {
- wrapper = render({}, { attachTo: container });
- getTabNode(0).focus();
- act(() => {
- simulate.keydown.pressD(wrapper.find(TabTitle).at(index));
- });
- wrapper.update();
- expect(getTabNode(0)).toEqual(document.activeElement);
- });
- });
-
- describe("when in Drawer sidebar", () => {
- let container: HTMLElement | null;
- let wrapper: ReactWrapper;
-
- beforeEach(() => {
- container = document.createElement("div");
- container.id = "enzymeContainer";
- document.body.appendChild(container);
-
- wrapper = mount(
-
-
-
-
-
-
- ,
- {
- attachTo: document.getElementById("enzymeContainer"),
- }
- );
- });
-
- afterEach(() => {
- if (container?.parentNode) {
- container.parentNode.removeChild(container);
- }
-
- container = null;
-
- wrapper?.unmount();
- });
-
- it.each([0, 1, 2])(
- "updates to make the next Tab visible when the down key is pressed",
- (index) => {
- act(() => {
- simulate.keydown.pressArrowDown(wrapper.find(TabTitle).at(index));
- });
- wrapper.update();
- const newIndex = index === 2 ? 0 : index + 1;
-
- expect(
- wrapper.find(StyledTabTitleButton).at(newIndex).getDOMNode()
- ).toBeFocused();
- }
- );
-
- it.each([0, 1, 2])(
- "updates to make the next Tab visible when the up key is pressed",
- (index) => {
- act(() => {
- simulate.keydown.pressArrowUp(wrapper.find(TabTitle).at(index));
- });
- wrapper.update();
- const newIndex = index === 0 ? 2 : index - 1;
-
- expect(
- wrapper.find(StyledTabTitleButton).at(newIndex).getDOMNode()
- ).toBeFocused();
- }
- );
- });
-});
-
-describe("With one Tab", () => {
- it("renders as the visible tab", () => {
- const tab = mount(
-
-
-
-
-
- ).find(Tab);
-
- expect(tab.props().isTabSelected).toEqual(true);
- expect(tab.props().title).toEqual("Tab Title 1");
- expect(tab.props().tabId).toEqual("uniqueid1");
- });
-});
-
-describe("Validation", () => {
- const updateProps = (
- wrapper: ReactWrapper,
- props: MockWrapperProps
- ) => {
- wrapper.setProps({
- errors: { ...wrapper.props().errors, ...props.errors },
- warnings: { ...wrapper.props().warnings, ...props.warnings },
- infos: { ...wrapper.props().infos, ...props.infos },
- });
- wrapper.update();
- };
-
- describe("When a Tab child has an error", () => {
- it('sets "tabHasError" to false when a Tab has no errors', () => {
- const tabTitle = mount().find(TabTitle);
-
- expect(tabTitle.at(0).props().error).toEqual(false);
- expect(tabTitle.at(1).props().error).toEqual(false);
- });
-
- it('sets "tabHasError" to true when a Tab has errors', () => {
- const tabTitle = mount().find(
- TabTitle
- );
-
- expect(tabTitle.at(0).props().error).toEqual(true);
- expect(tabTitle.at(1).props().error).toEqual(false);
- });
-
- it('sets "tabHasError" to true for any Tab that has an error', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().error).toEqual(true);
- expect(tabTitle.at(1).props().error).toEqual(true);
- });
-
- it('maintains "tabHasError" status when Tab children update', () => {
- const wrapper = mount(
-
- );
- updateProps(wrapper, { errors: { two: true, three: false } });
- let tabTitle = wrapper.find(TabTitle);
- expect(tabTitle.at(0).props().error).toEqual(true);
- expect(tabTitle.at(1).props().error).toEqual(false);
- updateProps(wrapper, { errors: { one: false, two: false } });
- tabTitle = wrapper.find(TabTitle);
- expect(tabTitle.at(0).props().error).toEqual(false);
- });
-
- it('does not set warnings and infos if "tabHasErrors" is true', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().error).toEqual(true);
- expect(tabTitle.at(1).props().error).toEqual(true);
- expect(tabTitle.at(0).props().warning).toEqual(false);
- expect(tabTitle.at(1).props().warning).toEqual(false);
- expect(tabTitle.at(0).props().info).toEqual(false);
- expect(tabTitle.at(1).props().info).toEqual(false);
- });
- });
-
- describe("When a Tab child has an error and a warning", () => {
- it('sets "tabHasWarning" to false when a Tab has no warnings', () => {
- const tabTitle = mount().find(TabTitle);
-
- expect(tabTitle.at(0).props().warning).toEqual(false);
- expect(tabTitle.at(1).props().warning).toEqual(false);
- });
-
- it('does not set "tabHasWarning" when "tabHasError" is true', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().error).toEqual(true);
- expect(tabTitle.at(0).props().warning).toEqual(false);
- });
- });
-
- describe("When a Tab child has a warning and no errors", () => {
- it('sets "tabHasWarning" is true and "tabHasError" is falsy', () => {
- const tabTitle = mount().find(
- TabTitle
- );
-
- expect(tabTitle.at(0).props().warning).toEqual(true);
- expect(tabTitle.at(1).props().warning).toEqual(false);
- });
-
- it('sets "tabHasWarning" for each Tab that has warning and "tabHasError" is falsy', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().warning).toEqual(true);
- expect(tabTitle.at(1).props().warning).toEqual(true);
- });
-
- it('maintains "tabHasWarning" status when Tab children update', () => {
- const wrapper = mount(
-
- );
- updateProps(wrapper, { warnings: { two: true, three: false } });
- let tabTitle = wrapper.find(TabTitle);
- expect(tabTitle.at(0).props().warning).toEqual(true);
- expect(tabTitle.at(1).props().warning).toEqual(false);
- updateProps(wrapper, { warnings: { one: false, two: false } });
- tabTitle = wrapper.find(TabTitle);
- expect(tabTitle.at(0).props().warning).toEqual(false);
- });
- });
-
- describe("When a Tab child has an info and no errors or warnings", () => {
- it('sets "tabHasWarning" is true and "tabHasError" is falsy', () => {
- const tabTitle = mount().find(
- TabTitle
- );
-
- expect(tabTitle.at(0).props().info).toEqual(true);
- expect(tabTitle.at(1).props().info).toEqual(false);
- });
-
- it('sets "tabHasInfo" for each Tab that has info and "tabHasError" and "tabHasWarning" are falsy', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().info).toEqual(true);
- expect(tabTitle.at(1).props().info).toEqual(true);
- });
-
- it('maintains "tabHasInfo" status when Tab children update', () => {
- const wrapper = mount();
- updateProps(wrapper, { infos: { two: true, three: false } });
- let tabTitle = wrapper.find(TabTitle);
- expect(tabTitle.at(0).props().info).toEqual(true);
- expect(tabTitle.at(1).props().info).toEqual(false);
- updateProps(wrapper, { infos: { one: false, two: false } });
- tabTitle = wrapper.find(TabTitle);
- expect(tabTitle.at(0).props().info).toEqual(false);
- });
- });
-
- describe("custom targeting", () => {
- it("supports overriding the targeted content", () => {
- const wrapper = mount(
-
-
-
- TabContent
-
-
-
- );
- act(() => {
- wrapper.find(TabTitle).props().onClick(mockEvent("click", "uniqueid1"));
- });
- expect(wrapper.find(Tab).exists()).toEqual(false);
- });
- });
-
- describe("validation status overrides", () => {
- it('sets "tabHasError" to true when override is set', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().error).toEqual(true);
- expect(tabTitle.at(1).props().error).toEqual(false);
- });
-
- it('sets "tabHasWarning" to true when override is set', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().warning).toEqual(true);
- expect(tabTitle.at(1).props().warning).toEqual(false);
- });
-
- it('sets "tabHasInfo" to true when override is set', () => {
- const tabTitle = mount(
-
- ).find(TabTitle);
-
- expect(tabTitle.at(0).props().info).toEqual(true);
- expect(tabTitle.at(1).props().info).toEqual(false);
- });
- });
-
- describe("Keyboard behaviour", () => {
- let container: HTMLElement | null;
- let wrapper: ReactWrapper;
- const tabTitles = ["tab-1", "tab-2", "tab-3"];
-
- const ConditionalChildrenMock = () => {
- const [showAllTabs, setShowAllTabs] = React.useState(true);
-
- const generateTab = (tabTitle: string) => (
-
- {tabTitle}
-
- );
-
- return (
- <>
-
-
- {!showAllTabs && generateTab(tabTitles[0])}
- {showAllTabs && tabTitles.map((tabTitle) => generateTab(tabTitle))}
-
- >
- );
- };
-
- beforeEach(() => {
- container = document.createElement("div");
- container.id = "enzymeContainer";
- document.body.appendChild(container);
-
- wrapper = mount(, {
- attachTo: document.getElementById("enzymeContainer"),
- });
- });
-
- afterEach(() => {
- if (container?.parentNode) {
- container.parentNode.removeChild(container);
- }
-
- container = null;
- });
-
- const runFocusExpectations = (keyDown: string, array: number[]) =>
- array.forEach((index) => {
- const child = wrapper.update().find(StyledTabTitleButton).at(index);
- expect(child.getDOMNode()).toBeFocused();
- simulate.keydown[keyDown](child);
- });
-
- const toggleChildren = () => {
- act(() => {
- wrapper.find("#foo").simulate("click");
- });
-
- expect(wrapper.update().find(StyledTabTitleButton).length).toEqual(1);
-
- act(() => {
- wrapper.find("#foo").simulate("click");
- });
-
- expect(wrapper.update().find(StyledTabTitleButton).length).toEqual(3);
- };
-
- it("is consistent when navigating with the arrow keys and the composition of the children changes", () => {
- (wrapper
- .find(StyledTabTitleButton)
- .first()
- .getDOMNode() as HTMLElement).focus();
-
- runFocusExpectations("pressArrowLeft", [0, 2, 1, 0, 2]);
-
- toggleChildren();
-
- (wrapper
- .find(StyledTabTitleButton)
- .first()
- .getDOMNode() as HTMLElement).focus();
-
- runFocusExpectations("pressArrowLeft", [0, 2, 1, 0, 2]);
-
- toggleChildren();
-
- (wrapper
- .find(StyledTabTitleButton)
- .first()
- .getDOMNode() as HTMLElement).focus();
-
- runFocusExpectations("pressArrowRight", [0, 1, 2, 0]);
- });
- });
-});
-
-describe("tags", () => {
- describe("on component", () => {
- const wrapper = shallow(
-
-
-
- ).find(StyledTabs);
-
- it("include correct component, element and role data tags", () => {
- rootTagTest(wrapper, "tabs", "bar", "baz");
- });
- });
-
- it("when child Tab has `data-role` prop set, renders corresponding tab title with that `data-role`", () => {
- const dataRole = "foobar";
- const wrapper = mount(
-
-
- Content for Tab 1
-
-
- );
- expect(wrapper.find(TabTitle).prop("data-role")).toEqual(dataRole);
- });
-
- describe("when children of Tab have validation failures", () => {
- const MockComponent = ({
- show = true,
- error,
- warning,
- info,
- }: ValidationProps & { show?: boolean }) => (
-
-
- {show && (
- {}}
- error={error}
- warning={warning}
- info={info}
- />
- )}
-
-
- );
-
- it.each(["error", "warning", "info"])(
- "any %s failure in a child component is correctly reported in the TabTitle",
- (validation) => {
- const validationProp = { [validation]: true };
- const wrapper = mount();
-
- expect(wrapper.find(ValidationIcon).exists()).toBe(true);
-
- wrapper.setProps({ show: false });
- wrapper.update();
- expect(wrapper.update().find(ValidationIcon).exists()).toBe(false);
- }
- );
- });
-
- describe.each(["error", "warning", "info"])(
- "showValidationsSummary",
- (validation) => {
- it(`passes the ${validation} validation failures from the child inputs to the Tab's title when they are strings`, () => {
- const message = mount(
-
-
- {}}
- {...{ [validation]: validation }}
- />
- {}}
- {...{ [validation]: validation }}
- />
- {}}
- {...{ [validation]: validation }}
- />
-
-
- )
- .find(TabTitle)
- .prop(`${validation}Message`);
-
- expect(message).toEqual(
- `• ${validation}\n• ${validation}\n• ${validation}`
- );
- });
-
- it(`does not pass the ${validation} validation failures from the child inputs to the Tab's title when they are not strings`, () => {
- const message = mount(
-
-
- {}}
- {...{ [validation]: validation }}
- />
- {}}
- {...{ [validation]: validation }}
- />
- {}}
- {...{ [validation]: true }}
- />
-
-
- )
- .find(TabTitle)
- .prop(`${validation}Message`);
-
- expect(message).toEqual(`• ${validation}\n• ${validation}`);
- });
-
- it(`does not add a "•" when there is only one string ${validation} validation failure`, () => {
- const message = mount(
-
-
- {}}
- {...{ [validation]: validation }}
- />
-
-
- )
- .find(TabTitle)
- .prop(`${validation}Message`);
-
- expect(message).toEqual(validation);
- });
-
- it(`passes the ${validation}Message if there is only boolean ${validation} validation failures`, () => {
- const message = mount(
-
-
- {}}
- {...{ [validation]: true }}
- />
- {}}
- {...{ [validation]: true }}
- />
- {}}
- {...{ [validation]: true }}
- />
-
-
- )
- .find(TabTitle)
- .prop(`${validation}Message`);
-
- expect(message).toEqual(`${validation} message`);
- });
- }
- );
-});
diff --git a/src/components/tabs/tabs.test.tsx b/src/components/tabs/tabs.test.tsx
new file mode 100644
index 0000000000..39b9b16534
--- /dev/null
+++ b/src/components/tabs/tabs.test.tsx
@@ -0,0 +1,1525 @@
+import React, { useState } from "react";
+import { render, screen, within } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Tabs, Tab } from ".";
+import { StyledTabsHeaderWrapper } from "./__internal__/tabs-header/tabs-header.style";
+import StyledTab from "./tab/tab.style";
+import { testStyledSystemMargin } from "../../__spec_helper__/__internal__/test-utils";
+import Drawer from "../drawer";
+import Textbox from "../textbox";
+import NumeralDate from "../numeral-date";
+
+testStyledSystemMargin((props) => (
+
+
+ TabContent
+
+
+));
+
+testStyledSystemMargin((props) => (
+
+
+ TabContent
+
+
+));
+
+test("should not throw an error when rendered with NumeralDate as a child", () => {
+ expect(() => {
+ render(
+
+
+
+ {}} />
+
+
+ );
+ }).not.toThrow();
+});
+
+test("should throw an error if rendered with `headerWidth` prop and `position` prop set to `top`", () => {
+ const consoleSpy = jest
+ .spyOn(global.console, "error")
+ .mockImplementation(() => {});
+
+ render(
+
+
+ TabContent
+
+
+ );
+
+ expect(consoleSpy).toHaveBeenCalledWith(
+ "Invalid usage of prop headerWidth in Tabs. The headerWidth can be used only if position is set to left"
+ );
+
+ consoleSpy.mockRestore();
+});
+
+test("passes the `className` prop down to the element", () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId("tabs")).toHaveClass(
+ "custom-class-1",
+ "custom-class-2"
+ );
+});
+
+test("the `selectedTabId` prop determines which child `Tab` is displayed", () => {
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ expect(screen.getByText("Content for tab 1")).not.toBeVisible();
+ expect(screen.getByText("Content for tab 2")).toBeVisible();
+ expect(screen.getByText("Content for tab 3")).not.toBeVisible();
+});
+
+test("when the `renderHiddenTabs` prop is false, only the currently visible tab is rendered in the DOM", () => {
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ expect(screen.queryByText("Content for tab 1")).not.toBeInTheDocument();
+ expect(screen.getByText("Content for tab 2")).toBeVisible();
+ expect(screen.queryByText("Content for tab 3")).not.toBeInTheDocument();
+});
+
+test.each(["error", "warning", "info"] as const)(
+ "when `renderHiddenTabs` is false, adds the correct %s state to the tab header only in the visible tab when the validation prop is set",
+ (validation) => {
+ render(
+
+
+ {}} {...{ [validation]: true }} />
+
+
+ {}} {...{ [validation]: true }} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab Title 1" })).getByTestId(
+ `icon-${validation}`
+ )
+ ).toBeVisible();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab Title 2" })).queryByTestId(
+ `icon-${validation}`
+ )
+ ).not.toBeInTheDocument();
+ }
+);
+
+test("when the `renderHiddenTabs` prop is true, all tabs are rendered in the DOM with only the first visible", () => {
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ expect(screen.getByText("Content for tab 1")).toBeVisible();
+ expect(screen.getByText("Content for tab 2")).not.toBeVisible();
+ expect(screen.getByText("Content for tab 3")).not.toBeVisible();
+});
+
+test("when a tab title is clicked, the associated tab becomes the visible one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ expect(screen.getByText("Content for tab 1")).toBeVisible();
+ expect(screen.getByText("Content for tab 2")).not.toBeVisible();
+
+ await user.click(screen.getByRole("tab", { name: "Tab 2" }));
+ expect(screen.getByText("Content for tab 1")).not.toBeVisible();
+ expect(screen.getByText("Content for tab 2")).toBeVisible();
+});
+
+test("when a tab title is clicked, the `onTabChange` callback prop gets called with the tab id", async () => {
+ const user = userEvent.setup();
+ const onTabChange = jest.fn();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ await user.click(screen.getByRole("tab", { name: "Tab 2" }));
+ expect(onTabChange).toHaveBeenCalledTimes(1);
+ expect(onTabChange).toHaveBeenCalledWith("tab-2");
+});
+
+test("updates the selected tab when the `selectedTabId` prop is updated", () => {
+ const { rerender } = render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ expect(screen.getByText("Content for tab 1")).toBeVisible();
+ expect(screen.getByText("Content for tab 2")).not.toBeVisible();
+
+ rerender(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ expect(screen.getByText("Content for tab 1")).not.toBeVisible();
+ expect(screen.getByText("Content for tab 2")).toBeVisible();
+});
+
+test("blurs the previously-selected tab title when the `selectedTabId` prop is updated", () => {
+ const { rerender } = render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ rerender(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ expect(screen.getByRole("tab", { name: "Tab 1" })).not.toHaveFocus();
+});
+
+test("does not call the `onTabChange` callback when rerendered with the selected tab as the new `selectedTabId", async () => {
+ const user = userEvent.setup();
+ const onTabChange = jest.fn();
+ const { rerender } = render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ await user.click(screen.getByRole("tab", { name: "Tab 2" }));
+ expect(onTabChange).toHaveBeenCalledTimes(1);
+
+ rerender(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ expect(onTabChange).toHaveBeenCalledTimes(1);
+});
+
+test("when the position is `top` (the default), pressing the right arrow key focuses the next tab title", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ await user.keyboard("{ArrowRight}");
+ expect(screen.getByRole("tab", { name: "Tab 2" })).toHaveFocus();
+
+ await user.keyboard("{ArrowRight}");
+ expect(screen.getByRole("tab", { name: "Tab 3" })).toHaveFocus();
+});
+
+test("when the position is `top` (the default), pressing the right arrow key when focused on the last tab title focuses the first one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 3" }).focus();
+
+ await user.keyboard("{ArrowRight}");
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveFocus();
+});
+
+test("when the position is `top` (the default), pressing the left arrow key focuses the previous tab title", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 3" }).focus();
+
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "Tab 2" })).toHaveFocus();
+
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveFocus();
+});
+
+test("when the position is `top` (the default), pressing the left arrow key when focused on the first tab title focuses the last one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "Tab 3" })).toHaveFocus();
+});
+
+test("when the position is `left`, pressing the down arrow key focuses the next tab title", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ await user.keyboard("{ArrowDown}");
+ expect(screen.getByRole("tab", { name: "Tab 2" })).toHaveFocus();
+
+ await user.keyboard("{ArrowDown}");
+ expect(screen.getByRole("tab", { name: "Tab 3" })).toHaveFocus();
+});
+
+test("when the position is `left`, pressing the down arrow key when focused on the last tab title focuses the first one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 3" }).focus();
+
+ await user.keyboard("{ArrowDown}");
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveFocus();
+});
+
+test("when the position is `left`, pressing the up arrow key focuses the previous tab title", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 3" }).focus();
+
+ await user.keyboard("{ArrowUp}");
+ expect(screen.getByRole("tab", { name: "Tab 2" })).toHaveFocus();
+
+ await user.keyboard("{ArrowUp}");
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveFocus();
+});
+
+test("when the position is `left`, pressing the up arrow key when focused on the first tab title focuses the last one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ await user.keyboard("{ArrowUp}");
+ expect(screen.getByRole("tab", { name: "Tab 3" })).toHaveFocus();
+});
+
+test("when the Enter key is pressed on a tab title, the associated tab becomes the visible one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ expect(screen.getByText("Content for tab 1")).toBeVisible();
+ expect(screen.getByText("Content for tab 2")).not.toBeVisible();
+
+ screen.getByRole("tab", { name: "Tab 2" }).focus();
+ await user.keyboard("{Enter}");
+ expect(screen.getByText("Content for tab 1")).not.toBeVisible();
+ expect(screen.getByText("Content for tab 2")).toBeVisible();
+});
+
+test("when a non-Enter, non-arrow key is pressed on a tab title, neither the visible tab nor the focused tab title updates", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+ expect(screen.getByText("Content for tab 1")).toBeVisible();
+ expect(screen.getByText("Content for tab 2")).not.toBeVisible();
+ expect(screen.getByText("Content for tab 3")).not.toBeVisible();
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ await user.keyboard("a");
+ expect(screen.getByText("Content for tab 1")).toBeVisible();
+ expect(screen.getByText("Content for tab 2")).not.toBeVisible();
+ expect(screen.getByText("Content for tab 3")).not.toBeVisible();
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveFocus();
+});
+
+test("when rendered in a Drawer sidebar, pressing the down arrow key focuses the next tab title", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ }
+ >
+ drawer content
+
+ );
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ await user.keyboard("{ArrowDown}");
+ expect(screen.getByRole("tab", { name: "Tab 2" })).toHaveFocus();
+
+ await user.keyboard("{ArrowDown}");
+ expect(screen.getByRole("tab", { name: "Tab 3" })).toHaveFocus();
+});
+
+test("when rendered in a Drawer sidebar`, pressing the down arrow key when focused on the last tab title focuses the first one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ }
+ >
+ drawer content
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 3" }).focus();
+
+ await user.keyboard("{ArrowDown}");
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveFocus();
+});
+
+test("when rendered in a Drawer sidebar, pressing the up arrow key focuses the previous tab title", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ }
+ >
+ drawer content
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 3" }).focus();
+
+ await user.keyboard("{ArrowUp}");
+ expect(screen.getByRole("tab", { name: "Tab 2" })).toHaveFocus();
+
+ await user.keyboard("{ArrowUp}");
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveFocus();
+});
+
+test("when rendered in a Drawer sidebar, pressing the up arrow key when focused on the first tab title focuses the last one", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ }
+ >
+ drawer content
+
+ );
+
+ screen.getByRole("tab", { name: "Tab 1" }).focus();
+
+ await user.keyboard("{ArrowUp}");
+ expect(screen.getByRole("tab", { name: "Tab 3" })).toHaveFocus();
+});
+
+test("when there is no validation issue in a tab, no validation icon is displayed in the tab title", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+});
+
+test("when there is an error in a tab, an error icon is displayed in the corresponding tab title", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId("icon-error")
+ ).toBeVisible();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+});
+
+test("when errors and warnings are both present in a tab, only the error icon is displayed in the corresponding tab title", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId("icon-error")
+ ).toBeVisible();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+});
+
+test("when errors, warnings and infos are all present in a tab, only the error icon is displayed in the corresponding tab title", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+ {}} />
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId("icon-error")
+ ).toBeVisible();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+});
+
+test("when a tab has warnings and no errors, a warning icon is displayed in the corresponding tab title", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId(
+ "icon-warning"
+ )
+ ).toBeVisible();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+});
+
+test("when a tab has info and no errors or warnings, an info icon is displayed in the corresponding tab title", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId("icon-info")
+ ).toBeVisible();
+});
+
+test("an error icon is displayed in the corresponding tab title when specified by the `validationStatusOverride` prop", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId("icon-error")
+ ).toBeVisible();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+});
+
+test("a warning icon is displayed in the corresponding tab title when specified by the `validationStatusOverride` prop", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId(
+ "icon-warning"
+ )
+ ).toBeVisible();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+});
+
+test("an info icon is displayed in the corresponding tab title when specified by the `validationStatusOverride` prop", () => {
+ render(
+
+
+ {}} />
+
+
+ {}} />
+
+
+ );
+
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-error"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).queryByTestId(
+ "icon-warning"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 1" })).queryByTestId(
+ "icon-info"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ within(screen.getByRole("tab", { name: "Tab 2" })).getByTestId("icon-info")
+ ).toBeVisible();
+});
+
+test("arrow key navigation remains consistent when tab children are added and removed", async () => {
+ const user = userEvent.setup();
+ const WithConditionalChildren = () => {
+ const [showAllTabs, setShowAllTabs] = useState(true);
+
+ const generateTab = (tabTitle: string) => (
+
+ {tabTitle}
+
+ );
+
+ return (
+ <>
+
+
+ {!showAllTabs && generateTab("tab-1")}
+ {showAllTabs &&
+ [1, 2, 3].map((tabNumber) => generateTab(`tab-${tabNumber}`))}
+
+ >
+ );
+ };
+ render();
+
+ screen.getByRole("tab", { name: "tab-1" }).focus();
+
+ expect(screen.getByRole("tab", { name: "tab-1" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-3" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-2" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-1" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-3" })).toHaveFocus();
+
+ await user.click(screen.getByRole("button", { name: "Toggle children" }));
+ expect(screen.getAllByRole("tab")).toHaveLength(1);
+ await user.click(screen.getByRole("button", { name: "Toggle children" }));
+ expect(screen.getAllByRole("tab")).toHaveLength(3);
+
+ screen.getByRole("tab", { name: "tab-1" }).focus();
+
+ expect(screen.getByRole("tab", { name: "tab-1" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-3" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-2" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-1" })).toHaveFocus();
+ await user.keyboard("{ArrowLeft}");
+ expect(screen.getByRole("tab", { name: "tab-3" })).toHaveFocus();
+ await user.click(screen.getByRole("button", { name: "Toggle children" }));
+ expect(screen.getAllByRole("tab")).toHaveLength(1);
+ await user.click(screen.getByRole("button", { name: "Toggle children" }));
+ expect(screen.getAllByRole("tab")).toHaveLength(3);
+
+ screen.getByRole("tab", { name: "tab-1" }).focus();
+
+ expect(screen.getByRole("tab", { name: "tab-1" })).toHaveFocus();
+ await user.keyboard("{ArrowRight}");
+ expect(screen.getByRole("tab", { name: "tab-2" })).toHaveFocus();
+ await user.keyboard("{ArrowRight}");
+ expect(screen.getByRole("tab", { name: "tab-3" })).toHaveFocus();
+ await user.keyboard("{ArrowRight}");
+ expect(screen.getByRole("tab", { name: "tab-1" })).toHaveFocus();
+});
+
+test("has the expected `data-component` attribute", () => {
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ expect(screen.getByTestId("tabs")).toHaveAttribute("data-component", "tabs");
+});
+
+test("accepts `data-element` and `data-role` props", () => {
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ expect(screen.getByTestId("bar")).toHaveAttribute("data-element", "foo");
+});
+
+test("child Tab components accept a `data-role` prop via `titleProps` which is added to the corresponding tab title", () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole("tab")).toHaveAttribute("data-role", "foo");
+});
+
+test.each(["error", "warning", "info"])(
+ "passes the all %s messages from the child inputs to the tooltip in the Tab title when they are strings and the `showValidationsSummary` prop is set",
+ async (validation) => {
+ const user = userEvent.setup();
+ render(
+
+
+ {}}
+ {...{ [validation]: "first validation message" }}
+ />
+ {}}
+ {...{ [validation]: "second validation message" }}
+ />
+ {}}
+ {...{ [validation]: "third validation message" }}
+ />
+
+
+ );
+
+ await user.hover(
+ within(screen.getByRole("tab", { name: "Test" })).getByTestId(
+ `icon-${validation}`
+ )
+ );
+ expect(screen.getByRole("tooltip")).toHaveTextContent(
+ "• first validation message • second validation message • third validation message"
+ );
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "leaves out boolean values from the %s messages in child inputs from the tooltip in the Tab title when the `showValidationsSummary` prop is set",
+ async (validation) => {
+ const user = userEvent.setup();
+ render(
+
+
+ {}}
+ {...{ [validation]: "first validation message" }}
+ />
+ {}} {...{ [validation]: true }} />
+ {}}
+ {...{ [validation]: "third validation message" }}
+ />
+
+
+ );
+
+ await user.hover(
+ within(screen.getByRole("tab", { name: "Test" })).getByTestId(
+ `icon-${validation}`
+ )
+ );
+ expect(screen.getByRole("tooltip")).toHaveTextContent(
+ "• first validation message • third validation message"
+ );
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "passes the %s message from the child inputs to the tooltip in the Tab title with no `•` character when there is only one such message, and the `showValidationsSummary` prop is set",
+ async (validation) => {
+ const user = userEvent.setup();
+ render(
+
+
+ {}}
+ {...{ [validation]: "first validation message" }}
+ />
+
+
+ );
+
+ await user.hover(
+ within(screen.getByRole("tab", { name: "Test" })).getByTestId(
+ `icon-${validation}`
+ )
+ );
+ expect(screen.getByRole("tooltip")).toHaveTextContent(
+ "first validation message"
+ );
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "uses just the `%sMessage` prop as the tooltip message when all child validation props are boolean and the `showValidationsSummary` prop is set",
+ async (validation) => {
+ const user = userEvent.setup();
+ render(
+
+
+ {}} {...{ [validation]: true }} />
+ {}} {...{ [validation]: true }} />
+ {}} {...{ [validation]: true }} />
+
+
+ );
+
+ await user.hover(
+ within(screen.getByRole("tab", { name: "Test" })).getByTestId(
+ `icon-${validation}`
+ )
+ );
+ expect(screen.getByRole("tooltip")).toHaveTextContent("a single message");
+ }
+);
+
+test.each(["error", "warning", "info"])(
+ "uses just the `%sMessage` prop as the tooltip message when the `showValidationsSummary` prop is not set",
+ async (validation) => {
+ const user = userEvent.setup();
+ render(
+
+
+ {}}
+ {...{ [validation]: "first validation message" }}
+ />
+ {}}
+ {...{ [validation]: "second validation message" }}
+ />
+ {}}
+ {...{ [validation]: "third validation message" }}
+ />
+
+
+ );
+
+ await user.hover(
+ within(screen.getByRole("tab", { name: "Test" })).getByTestId(
+ `icon-${validation}`
+ )
+ );
+ expect(screen.getByRole("tooltip")).toHaveTextContent("a single message");
+ }
+);
+
+// coverage - headerWidth prop tested in Chromatic
+test.each(["35%", "100px", "5em"])(
+ "should render tab header with the correct width when the `headerWidth` prop is provided and the `position` prop is `left`",
+ (headerWidth) => {
+ render(
+
+
+ TabContent
+
+
+ );
+
+ expect(screen.getByTestId("tabs")).toHaveStyleRule("width", headerWidth, {
+ modifier: `${StyledTabsHeaderWrapper}`,
+ });
+ expect(screen.getByTestId("tabs")).toHaveStyleRule(
+ "width",
+ `calc(100% - ${headerWidth})`,
+ {
+ modifier: `${StyledTab}`,
+ }
+ );
+ }
+);
+
+// coverage
+test("leaves the same tab content visible when the already-selected tab is clicked", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ Content for tab 1
+
+
+ Content for tab 2
+
+
+ Content for tab 3
+
+
+ );
+
+ await user.click(screen.getByRole("tab", { name: "Tab 1" }));
+ expect(screen.getByText("Content for tab 1")).toBeVisible();
+ expect(screen.getByText("Content for tab 2")).not.toBeVisible();
+});