diff --git a/src/components/flat-table/__internal__/build-position-map.ts b/src/components/flat-table/__internal__/build-position-map.ts index dce78245c8..703263e984 100644 --- a/src/components/flat-table/__internal__/build-position-map.ts +++ b/src/components/flat-table/__internal__/build-position-map.ts @@ -9,6 +9,8 @@ export default ( acc[currentId] = 0; } else { const previousId = array[index - 1].getAttribute("id"); + + /* istanbul ignore else */ if (previousId) { acc[currentId] = acc[previousId] + array[index - 1][propertyName]; } diff --git a/src/components/flat-table/components.test-pw.tsx b/src/components/flat-table/components.test-pw.tsx index 2456de8532..96e9aa3902 100644 --- a/src/components/flat-table/components.test-pw.tsx +++ b/src/components/flat-table/components.test-pw.tsx @@ -3006,3 +3006,28 @@ export const HighlightedRowWithLoadingState = ( ); }; + +export const FlatTableWithStickyColumn = () => ( + + + + Foo + Foo + Foo + Foo + Foo + + + + + Bar + Bar + Bar + Bar + + + + + + +); diff --git a/src/components/flat-table/flat-table-row/flat-table-row.component.tsx b/src/components/flat-table/flat-table-row/flat-table-row.component.tsx index 59a2415c4f..a77787fb08 100644 --- a/src/components/flat-table/flat-table-row/flat-table-row.component.tsx +++ b/src/components/flat-table/flat-table-row/flat-table-row.component.tsx @@ -7,9 +7,10 @@ import React, { useLayoutEffect, } from "react"; import invariant from "invariant"; -import { TableBorderSize } from ".."; import Event from "../../../__internal__/utils/helpers/events"; +import { TagProps } from "../../../__internal__/utils/helpers/tags"; +import { TableBorderSize } from ".."; import StyledFlatTableRow from "./flat-table-row.style"; import DrawerSidebarContext from "../../drawer/__internal__/drawer-sidebar.context"; import FlatTableRowHeader from "../flat-table-row-header"; @@ -23,7 +24,7 @@ import SubRowProvider, { SubRowContext } from "./__internal__/sub-row-provider"; import { buildPositionMap } from "../__internal__"; import FlatTableHeadContext from "../flat-table-head/__internal__/flat-table-head.context"; -export interface FlatTableRowProps { +export interface FlatTableRowProps extends Omit { /** Overrides default cell color, provide design token, any color from palette or any valid css color value. */ bgColor?: string; /** Array of FlatTableHeader or FlatTableCell. FlatTableRowHeader could also be passed. */ @@ -82,6 +83,8 @@ export const FlatTableRow = React.forwardRef< draggable, findItem, moveItem, + "data-element": dataElement, + "data-role": dataRole, ...rest }: FlatTableRowProps, ref @@ -269,13 +272,20 @@ export const FlatTableRow = React.forwardRef< const isFirstSubRow = firstRowId === internalId.current; + const getDataElement = () => { + if (dataElement) return dataElement; + + return isSubRow ? "flat-table-sub-row" : "flat-table-row"; + }; + const rowComponent = () => ( - - - cell1 - cell2 - - - - ); -} - -function renderRowWithContext(props = {}) { - return mount( - - - - - cell1 - cell2 - - -
-
- ); -} - -describe("FlatTableRow", () => { - describe("when a data prop is added", () => { - it("should be added to the root element", () => { - const wrapper = renderFlatTableRow({ "data-role": "test" }); - - expect(wrapper.find(StyledFlatTableRow).props()["data-role"]).toEqual( - "test" - ); - }); - }); - - describe('when the "onClick" prop is passed', () => { - let wrapper: ReactWrapper; - let onClickFn: jest.Mock; - - beforeEach(() => { - onClickFn = jest.fn(); - wrapper = renderFlatTableRow({ - onClick: onClickFn, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - }); - - it("then the component should have tabIndex set to -1", () => { - expect(wrapper.find(StyledFlatTableRow).prop("tabIndex")).toBe(-1); - }); - - it("then the component should have isRowInteractive prop set to true", () => { - expect(wrapper.find(StyledFlatTableRow).prop("isRowInteractive")).toBe( - true - ); - }); - - it("then the cursor over the element should be set to pointer", () => { - assertStyleMatch({ cursor: "pointer" }, wrapper); - }); - - it("then all Cells of the Row should have proper hover color", () => { - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor025)", - }, - wrapper, - { modifier: `:hover ${StyledFlatTableCell}` } - ); - }); - - it("then the Row Header of the Row should have proper hover color", () => { - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor025)", - }, - wrapper, - { modifier: `:hover ${StyledFlatTableRowHeader}` } - ); - }); - - describe("and space key is pressed", () => { - it("then the onClick prop should be called", () => { - wrapper.find(FlatTableRow).simulate("keydown", { key: "Enter" }); - expect(onClickFn).toHaveBeenCalled(); - }); - }); - - describe("and enter key is pressed", () => { - it("then the onClick prop should be called", () => { - wrapper.find(FlatTableRow).simulate("keydown", { key: " " }); - expect(onClickFn).toHaveBeenCalled(); - }); - }); - - describe("and a key other than space or enter is pressed", () => { - it("then the onClick prop should not be called", () => { - wrapper.find(FlatTableRow).simulate("keydown", { key: "a" }); - expect(onClickFn).not.toHaveBeenCalled(); - }); - }); - }); - - describe('when the "onClick" prop is not passed', () => { - let wrapper: ReactWrapper; - - beforeEach(() => { - wrapper = renderFlatTableRow(); - }); - - it("then the component should have tabIndex undefined", () => { - expect(wrapper.find(StyledFlatTableRow).prop("tabIndex")).toBe(undefined); - }); - - it("then the component should have isRowInteractive prop undefined", () => { - expect(wrapper.find(StyledFlatTableRow).prop("isRowInteractive")).toBe( - undefined - ); - }); - }); - - describe('when the "selected" prop is passed as true', () => { - let wrapper; - it('applies a "background-color" to the "TableCell"', () => { - wrapper = renderFlatTableRow({ - selected: true, - }); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor075)", - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - - describe('when the "onClick" is also provided', () => { - it('applies the correct "background-color" on hover', () => { - wrapper = renderFlatTableRow({ - selected: true, - onClick: jest.fn(), - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor075)", - }, - wrapper, - { modifier: `:hover ${StyledFlatTableCell}` } - ); - }); - }); - - describe.each([ - StyledFlatTableCell, - StyledFlatTableRowHeader, - StyledFlatTableCheckbox, - ])('with the "bgColor" also provided', (element) => { - it(`it overrides the ${element} "background-color"`, () => { - const customColor = "#CCCCCC"; - - wrapper = renderFlatTableRow({ - selected: true, - bgColor: customColor, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - backgroundColor: customColor, - }, - wrapper, - { modifier: `${element}` } - ); - }); - - it('overrides the cell "background-color" on hover', () => { - const customColor = "#CCCCCC"; - - wrapper = renderFlatTableRow({ - selected: true, - bgColor: customColor, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - backgroundColor: customColor, - }, - wrapper, - { modifier: `:hover ${StyledFlatTableCell}` } - ); - }); - }); - }); - - describe('when the "highlighted" prop is passed as true', () => { - let wrapper; - it('applies a "background-color" to the "TableCell"', () => { - wrapper = renderFlatTableRow({ - highlighted: true, - onClick: jest.fn(), - }); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor050)", - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - - it('applies a "background-color" to the "FlatTableRowHeader"', () => { - wrapper = renderFlatTableRow({ - highlighted: true, - onClick: jest.fn(), - }); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor050)", - }, - wrapper, - { modifier: `${StyledFlatTableRowHeader}` } - ); - }); - - describe('with the "bgColor" also provided', () => { - it('overrides the cell "background-color"', () => { - const customColor = "#CCCCCC"; - - wrapper = renderFlatTableRow({ - highlighted: true, - bgColor: customColor, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - backgroundColor: customColor, - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - - it('overrides the cell "background-color" on hover', () => { - const customColor = "#CCCCCC"; - - wrapper = renderFlatTableRow({ - highlighted: true, - bgColor: customColor, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - backgroundColor: customColor, - }, - wrapper, - { modifier: `:hover ${StyledFlatTableCell}` } - ); - }); - }); - - describe('when the "selected" prop is also passed as true', () => { - it('applies the correct "background-color"', () => { - wrapper = renderFlatTableRow({ - selected: true, - highlighted: true, - onClick: jest.fn(), - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor075)", - }, - wrapper, - { modifier: `:hover ${StyledFlatTableCell}` } - ); - }); - - describe('with the "bgColor" also provided', () => { - it('overrides the cell "background-color"', () => { - const customColor = "#CCCCCC"; - - wrapper = renderFlatTableRow({ - selected: true, - highlighted: true, - bgColor: customColor, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - backgroundColor: customColor, - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - }); - }); - }); - - describe("when a child of Sidebar", () => { - let wrapper; - it.each([ - ["StyledFlatTableHeader", StyledFlatTableHeader], - ["StyledFlatTableRowHeader", StyledFlatTableRowHeader], - ["StyledFlatTableCell", StyledFlatTableCell], - ["StyledFlatTableCheckbox", StyledFlatTableCheckbox], - ])("applies the expected styling to %s", (id, el) => { - wrapper = renderRowWithContext({ onClick: () => {} }); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor040)", - }, - wrapper, - { modifier: `${el}` } - ); - - const modifierString = - id === "StyledFlatTableCheckbox" ? `${el}:not(th)` : el; - - if (!["StyledFlatTableHeader", "StyledFlatTableRowHeader"].includes(id)) { - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor075)", - }, - wrapper, - { modifier: `:hover ${modifierString}` } - ); - } - - if (id === "StyledFlatTableCheckbox") { - assertStyleMatch( - { - borderRight: "1px solid var(--colorsUtilityMajor100)", - }, - wrapper, - { modifier: `${el}` } - ); - } - }); - - it('applies an additional "padding-left" to the "FlatTableRow" and removes "border-left" from first child', () => { - wrapper = renderRowWithContext(); - assertStyleMatch( - { - borderLeft: "none", - }, - wrapper, - { modifier: "td:first-of-type" } - ); - }); - - it('removes "border-right" from "FlatTableRow" first child', () => { - wrapper = renderRowWithContext(); - assertStyleMatch( - { - borderRight: "none", - }, - wrapper, - { modifier: "td:last-of-type" } - ); - }); - - describe('and the "selected" prop is passed as true', () => { - it.each([ - ["StyledFlatTableRowHeader", StyledFlatTableRowHeader], - ["StyledFlatTableCell", StyledFlatTableCell], - ["StyledFlatTableCheckbox", StyledFlatTableCheckbox], - ])('applies the correct "background-color" to %s', (id, el) => { - wrapper = renderRowWithContext({ - selected: true, - }); - - if (id !== "StyledFlatTableRowHeader") { - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor150)", - }, - wrapper, - { modifier: `${el}` } - ); - } - - const modifierString = - id === "StyledFlatTableCheckbox" ? `${el}:not(th)` : el; - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor150)", - }, - wrapper, - { modifier: `:hover ${modifierString}` } - ); - }); - }); - - describe('and the "highlighted" prop is passed as true', () => { - it.each([ - ["StyledFlatTableRowHeader", StyledFlatTableRowHeader], - ["StyledFlatTableCell", StyledFlatTableCell], - ["StyledFlatTableCheckbox", StyledFlatTableCheckbox], - ])('applies the correct "background-color" to %s', (id, el) => { - wrapper = renderRowWithContext({ - highlighted: true, - onClick: jest.fn(), - }); - - if (id !== "StyledFlatTableRowHeader") { - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor100)", - }, - wrapper, - { modifier: `${el}` } - ); - } - - const modifierString = - id === "StyledFlatTableCheckbox" ? `${el}:not(th)` : el; - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor100)", - }, - wrapper, - { modifier: `:hover ${modifierString}` } - ); - }); - - describe('and the "selected" prop is also passed as true', () => { - it.each([ - ["StyledFlatTableRowHeader", StyledFlatTableRowHeader], - ["StyledFlatTableCell", StyledFlatTableCell], - ["StyledFlatTableCheckbox", StyledFlatTableCheckbox], - ])('applies the correct "background-color" to %s', (id, el) => { - wrapper = renderRowWithContext({ - selected: true, - highlighted: true, - onClick: jest.fn(), - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - - if (id !== "StyledFlatTableRowHeader") { - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor150)", - }, - wrapper, - { modifier: `${el}` } - ); - } - - const modifierString = - id === "StyledFlatTableCheckbox" ? `${el}:not(th)` : el; - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor150)", - }, - wrapper, - { modifier: `:hover ${modifierString}` } - ); - }); - }); - }); - }); - - describe("with conditionally rendered children", () => { - describe("when child is null", () => { - it("should not render that child", () => { - const wrapper = mount( - - - - cell1 - {false && cell2} - - -
- ); - - expect(wrapper.find(FlatTableCell).length).toEqual(1); - }); - }); - - describe("when child is not null", () => { - it("should render that child", () => { - const wrapper = mount( - - - - cell1 - {true && cell2} - - -
- ); - - expect(wrapper.find(FlatTableCell).length).toEqual(2); - }); - }); - }); - - describe("when FlatTableRowHeader is used", () => { - describe("and is null", () => { - it("should not make any preceding children sticky", () => { - const wrapper = mount( - - - - cell1 - {false && cell2} - {false && test 3} - - -
- ); - - assertStyleMatch( - { - position: undefined, - }, - wrapper.find(StyledFlatTableCell).at(0) - ); - }); - }); - - it("sets any preceding columns to sticky as well", () => { - const wrapper = mount( - - - - test 1 - test 2 - {}} /> - test 3 - test 4 - test 5 - - -
- ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableHeader).at(0) - ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableCell).at(0) - ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableCheckbox) - ); - - assertStyleMatch( - { - position: undefined, - }, - wrapper.find(StyledFlatTableHeader).at(1) - ); - - assertStyleMatch( - { - position: undefined, - }, - wrapper.find(StyledFlatTableCell).at(1) - ); - }); - - describe("and stickyAlignment is 'right'", () => { - it("sets any preceding columns to sticky as well", () => { - const wrapper = mount( - - - - test 1 - test 2 - - test 3 - - {}} /> - test 4 - test 5 - - -
- ); - - assertStyleMatch( - { - position: undefined, - }, - wrapper.find(StyledFlatTableHeader).at(0) - ); - - assertStyleMatch( - { - position: undefined, - }, - wrapper.find(StyledFlatTableCell).at(0) - ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableCheckbox) - ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableHeader).at(1) - ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableCell).at(1) - ); - }); - }); - - it("throws an error when rhsRowHeaderIndex is less than lhsRowHeaderIndex", () => { - const mockGlobal = jest - .spyOn(global.console, "error") - .mockImplementation(() => undefined); - const errorMessage = `Do not render a right hand side \`FlatTableRowHeader\` before left hand side \`FlatTableRowHeader\``; - - expect(() => { - mount( - - - - test 1 - test 2 - {}} /> - - test 3 - - - test 3 - - test 4 - test 5 - - -
- ); - }).toThrow(errorMessage); - - mockGlobal.mockReset(); - }); - - it("applies the correct styling when the row is interactive", () => { - const wrapper = mount( - - - {}}> - test 1 - test 2 - {}} /> - test 3 - test 4 - test 5 - - -
- ); - - assertStyleMatch( - { - borderRight: "2px solid var(--colorsUtilityMajor100)", - }, - wrapper, - { modifier: `${StyledFlatTableRowHeader}:nth-child(4)` } - ); - }); - }); - - describe("when the row is expandable", () => { - const SubRows = [ - - sub1cell1 - sub1cell2 - , - - sub2cell1 - sub2cell2 - , - ]; - - it("applies the expected styling to the Icons", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - }); - - assertStyleMatch( - { - transition: "transform 0.3s", - transform: "rotate(-90deg)", - }, - wrapper.find(FlatTableRow), - { - modifier: `${StyledFlatTableCell}:first-child > div ${StyledIcon}[type="chevron_down_thick"]:first-of-type`, - } - ); - }); - - it("should have the sub rows closed by default", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - }); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - - it("then the component should have tabIndex of undefined if no onClick is passed and firstColumnExpandable", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - expandableArea: "firstColumn", - }); - - expect(wrapper.find(StyledFlatTableRow).prop("tabIndex")).toBe(undefined); - }); - - describe("when clicked", () => { - it("should expand the sub rows", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - }); - - act(() => { - wrapper.find(StyledFlatTableRow).at(0).props().onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - }); - - describe("when onClick prop set", () => { - it("should call the onClick function", () => { - const onClickFn = jest.fn(); - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - onClick: onClickFn, - }); - - act(() => { - wrapper.find(StyledFlatTableRow).at(0).props().onClick(); - }); - - wrapper.update(); - - expect(onClickFn).toHaveBeenCalled(); - }); - - it("then the component should have tabIndex of undefined if firstColumnExpandable", () => { - const onClickFn = jest.fn(); - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - onClick: onClickFn, - expandableArea: "firstColumn", - }); - - expect(wrapper.find(StyledFlatTableRow).prop("tabIndex")).toBe( - undefined - ); - }); - }); - - describe("when used as a controlled component", () => { - it("should update the expanded state of the rows", () => { - const MockComponent = (props: Partial = {}) => { - const [expanded, setExpanded] = React.useState(false); - return ( - <> - - - - - cell1 - cell2 - - -
- - ); - }; - - const wrapper = mount(); - const button = wrapper?.find("button"); - act(() => { - button - ?.props?.() - .onClick?.({} as React.MouseEvent); - }); - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - button - ?.props?.() - .onClick?.({} as React.MouseEvent); - }); - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - }); - }); - - describe("when focused and enter/space is pressed", () => { - let wrapper: ReactWrapper; - const element = document.createElement("div"); - const htmlElement = document.body.appendChild(element); - - beforeEach(() => { - wrapper = mount( - - - - cell1 - cell2 - - -
, - { attachTo: htmlElement } - ); - }); - - afterEach(() => { - wrapper.unmount(); - }); - - it("should toggle the open/close state of the row", () => { - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - - document.querySelectorAll("tr")[0].focus(); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .props() - .onKeyDown(events.enter); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .props() - .onKeyDown(events.space); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - }); - - describe("when expanded prop set to true", () => { - it("should render the sub rows open", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - expanded: true, - }); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - }); - }); - - describe("when first child of sub row is a checkbox", () => { - it("should add the expandable icon to the second child", () => { - const CheckboxSubRows = [ - - {}} /> - sub1cell2 - , - - {}} /> - sub2cell2 - , - ]; - - const wrapper = mount( - - - - {}} /> - cell2 - - -
- ); - - expect( - wrapper - .find(StyledFlatTableRow) - .at(0) - .find(StyledFlatTableCell) - .find(StyledIcon) - .exists() - ).toEqual(true); - }); - }); - - describe("when expandableArea prop is set to 'firstColumn'", () => { - let wrapper: ReactWrapper; - - beforeEach(() => { - wrapper = renderFlatTableRow({ - expandable: true, - subRows: SubRows, - expandableArea: "firstColumn", - }); - }); - - it("should set the cursor to pointer for the first column", () => { - assertStyleMatch({ cursor: "pointer" }, wrapper, { - modifier: `td:nth-child(1)`, - }); - }); - - it("should expand the sub rows when first column clicked", () => { - act(() => { - wrapper - .find(StyledFlatTableRow) - .find(StyledFlatTableCell) - .at(0) - .props() - .onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - }); - - it("should not expand the sub rows when other column clicked", () => { - act(() => { - wrapper.find(StyledFlatTableRow).at(0).props().onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - - describe("when a key is pressed", () => { - const element = document.createElement("div"); - const htmlElement = document.body.appendChild(element); - - beforeEach(() => { - wrapper = mount( - - - - cell1 - cell2 - - -
, - { attachTo: htmlElement } - ); - }); - - afterEach(() => { - wrapper.unmount(); - }); - - describe("when the key is enter/space", () => { - it("should toggle the open/close state of the row", () => { - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - - (wrapper - .find(StyledFlatTableRow) - .at(0) - .find("td") - .at(0) - .getDOMNode() as HTMLElement).focus(); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .find(StyledFlatTableCell) - .at(0) - .props() - .onKeyDown(events.enter); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .find(StyledFlatTableCell) - .at(0) - .props() - .onKeyDown(events.space); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - }); - - describe("when the key is not enter/space", () => { - it("should toggle not the open/close state of the row", () => { - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - - (wrapper - .find(StyledFlatTableRow) - .at(0) - .find("td") - .at(0) - .getDOMNode() as HTMLElement).focus(); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .find(StyledFlatTableCell) - .at(0) - .props() - .onKeyDown(events.c); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - }); - }); - }); - - describe("when passing sub rows as a component", () => { - const SubRowsComponent = () => ( - <> - - sub1cell1 - sub1cell2 - - - sub2cell1 - sub2cell2 - - - ); - - it("should expand the sub rows when row is clicked", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: , - }); - - act(() => { - wrapper.find(StyledFlatTableRow).at(0).props().onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - wrapper.find(StyledFlatTableRow).at(0).props().onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - - it("should expand the sub rows when first column clicked and expandable area is first column", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: , - expandableArea: "firstColumn", - }); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .find(StyledFlatTableCell) - .at(0) - .props() - .onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .find(StyledFlatTableCell) - .at(0) - .props() - .onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - - it("should toggle the open/close state of the row when enter/space pressed", () => { - const element = document.createElement("div"); - const htmlElement = document.body.appendChild(element); - - const wrapper = mount( - - - }> - cell1 - cell2 - - -
, - { attachTo: htmlElement } - ); - - (wrapper - .find(StyledFlatTableRow) - .at(0) - .getDOMNode() as HTMLElement).focus(); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .props() - .onKeyDown(events.enter); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .props() - .onKeyDown(events.space); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - - it("should toggle the open/close state of the row when enter/space pressed and expandable area is first column", () => { - const wrapper = renderFlatTableRow({ - expandable: true, - subRows: , - expandableArea: "firstColumn", - }); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - - (wrapper - .find(StyledFlatTableRow) - .at(0) - .find("td") - .at(0) - .getDOMNode() as HTMLElement).focus(); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .find(StyledFlatTableCell) - .at(0) - .props() - .onKeyDown(events.enter); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - wrapper - .find(StyledFlatTableRow) - .at(0) - .find(StyledFlatTableCell) - .at(0) - .props() - .onKeyDown(events.space); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - }); - - describe.each([ - ["medium", "2px solid var(--colorsUtilityMajor100)"], - ["large", "4px solid var(--colorsUtilityMajor100)"], - ])( - "when the horizontalBorderSize prop is set to %s", - (horizontalBorderSize, expectedValue) => { - let wrapper; - - it("overrides the cell bottom-border size", () => { - wrapper = renderFlatTableRow({ - highlighted: true, - horizontalBorderSize, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - borderBottom: expectedValue, - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - } - ); - - describe.each([ - ["goldTint10", "#FFBC1A"], - ["#000", "#000"], - ])( - "when the horizontalBorderColor prop is set to %s", - (horizontalBorderColor, expectedValue) => { - let wrapper; - - it("overrides the cell bottom-border color", () => { - wrapper = renderFlatTableRow({ - highlighted: true, - horizontalBorderColor, - }); - wrapper.find(FlatTableRow).at(0).simulate("focus"); - assertStyleMatch( - { - borderBottomColor: expectedValue, - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - } - ); - - describe("when a right sticky column is rendered with no left sticky column", () => { - it("applies the expected styling to the row header cell", () => { - const wrapper = mount( - - - - test 1 - test 2 - - test 3 - - - -
- ); - assertStyleMatch( - { - borderLeft: "2px solid var(--colorsUtilityMajor100)", - }, - wrapper, - { modifier: `${StyledFlatTableRowHeader}:nth-child(3)` } - ); - }); - }); - - describe("when the size of the table is 'compact'", () => { - it("should add the correct padding to child row cells", () => { - const wrapper = mount( - "" }} - > - - - - cell1 - cell2 - - -
-
- ); - - assertStyleMatch( - { - paddingLeft: "32px", - }, - wrapper.find(StyledFlatTableRow).at(1), - { - modifier: `${StyledFlatTableCheckbox} + ${StyledFlatTableCell} > div`, - } - ); - }); - }); - - describe("when a table row has content with custom height in Safari", () => { - let isSafariSpy: jest.SpyInstance; - - beforeEach(() => { - isSafariSpy = jest.spyOn(browserTypeCheck, "isSafari"); - }); - - beforeEach(() => { - // Mock the offsetHeight of the table row - Object.defineProperty(HTMLElement.prototype, "offsetHeight", { - configurable: true, - value: 200, - }); - }); - - it("applies the correct height for focus styling", () => { - isSafariSpy.mockReturnValue(true); - - const wrapper = mount( - - - {}}> - - - Option 2 - - - - -
- ); - - const row = wrapper.find(StyledFlatTableRow); - row.simulate("focus"); - - assertStyleMatch( - { - height: "200px", - }, - row, - { modifier: `:focus:after` } - ); - }); - }); - }); - - describe("Draggable Table with subRows", () => { - it("should expand the sub rows", () => { - const subRows = (str: string) => [ - - York - , - - Edinburgh - , - ]; - const component = ( - - - - UK - - - Germany - - - Finland - - -
- ); - - const wrapper = mount(component); - - act(() => { - wrapper.find(StyledFlatTableRow).at(0).props().onClick(); - }); - - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(7); - }); - - it("sets any preceding columns to sticky as well", () => { - const subRows = [ - - 1 - 2 - , - - 1 - 2 - , - ]; - const wrapper = mount( - - - - test 1 - test 2 - {}} /> - test 3 - test 4 - test 5 - - -
- ); - - wrapper.update(); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableHeader).at(0) - ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableCell).at(0) - ); - - assertStyleMatch( - { - position: "sticky", - }, - wrapper.find(StyledFlatTableCheckbox) - ); - - assertStyleMatch( - { - position: undefined, - }, - wrapper.find(StyledFlatTableHeader).at(1) - ); - }); - - it("should update the expanded state of the rows", () => { - const subRows = [ - - 1 - 2 - , - - 1 - 2 - , - ]; - const MockComponent = (props: Partial = {}) => { - const [expanded, setExpanded] = React.useState(false); - return ( - <> - - - - - cell1 - cell2 - - -
- - ); - }; - - const wrapper = mount(); - const button = wrapper.find("button"); - act(() => { - button?.props?.().onClick?.({} as React.MouseEvent); - }); - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(3); - - act(() => { - button?.props?.().onClick?.({} as React.MouseEvent); - }); - wrapper.update(); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(1); - }); - - it("should render the sub rows open when expanded set to true", () => { - const subRows = [ - - York - , - - Edinburgh - , - ]; - const component = ( - - - - UK - - - Germany - - - Finland - - -
- ); - - const wrapper = mount(component); - - expect(wrapper.find(StyledFlatTableRow).length).toEqual(9); - }); - - it("make sure first cell is clickable", () => { - const subRows = [ - - 1 - 2 - , - - 1 - 2 - , - ]; - const wrapper = mount( - - - - test 1 - test 2 - {}} /> - test 3 - test 4 - test 5 - - -
- ); - - const cell = wrapper.find("td").at(0); - - act(() => { - cell?.props?.().onClick?.({} as React.MouseEvent); - }); - wrapper.update(); - - assertStyleMatch( - { - position: undefined, - }, - wrapper.find(StyledFlatTableHeader).at(1) - ); - }); - - it("make sure styling is applied when dragging", () => { - const wrapper = mount( - - - - Test - - -
- ); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor150)", - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - - it("make sure styling is applied when dragging in a sidebar", () => { - const wrapper = mount( - - - - Test - - -
- ); - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor200)", - }, - wrapper, - { modifier: `${StyledFlatTableCell}` } - ); - }); - - describe("with a ref", () => { - it("the ref should be forwarded", () => { - let mockRef = { current: null }; - - const WrapperComponent = () => { - mockRef = useRef(null); - - return ( - - - - test 1 - test 2 - - test 3 - test 4 - test 5 - - -
- ); - }; - - const wrapper = mount(); - - expect(mockRef.current).toBe(wrapper.find("tr").getDOMNode()); - }); - - it("the input callback ref should be called with the DOM element", () => { - let mockRef; - - const WrapperComponent = () => { - mockRef = jest.fn(); - - return ( - - - - test 1 - test 2 - - test 3 - test 4 - test 5 - - -
- ); - }; - - const wrapper = mount(); - - expect(mockRef).toHaveBeenCalledWith(wrapper.find("tr").getDOMNode()); - }); - }); - }); - - describe("wrapping FlatTableRowHeader", () => { - it("calculates the rowHeaderIndex as expected", () => { - const FlatTableRowHeaderWrapper = ({ - children, - }: { - children: React.ReactNode; - }) => {children}; - const rowHeaderIndex = mount( - - - - Foo - Foo - Foo - Foo - Foo - Foo - - -
- ) - .find(StyledFlatTableRow) - .prop("lhsRowHeaderIndex"); - - expect(rowHeaderIndex).toEqual(3); - }); - }); - - describe("when only children passed are checkboxes", () => { - it("does not update first cell index", () => { - const wrapper = mount( - - - - - - - -
- ); - - expect(wrapper.find(StyledFlatTableRow).prop("firstCellIndex")).toBe(0); - }); - }); - - describe("when first cell has no id set", () => { - it("does not set a first cell index", () => { - const wrapper = mount( - - - - - I have an ID - - -
I have no ID
- ); - - expect(wrapper.find(StyledFlatTableRowHeader).prop("leftPositon")).toBe( - undefined - ); - }); - }); -}); diff --git a/src/components/flat-table/flat-table-row/flat-table-row.style.ts b/src/components/flat-table/flat-table-row/flat-table-row.style.ts index a483edab4f..452c9d26cd 100644 --- a/src/components/flat-table/flat-table-row/flat-table-row.style.ts +++ b/src/components/flat-table/flat-table-row/flat-table-row.style.ts @@ -309,6 +309,7 @@ const StyledFlatTableRow = styled.tr` } `} ${!theme.focusRedesignOptOut && + /* istanbul ignore next */ css` position: -webkit-sticky; :after { diff --git a/src/components/flat-table/flat-table-row/flat-table-row.test.tsx b/src/components/flat-table/flat-table-row/flat-table-row.test.tsx new file mode 100644 index 0000000000..f986d8957d --- /dev/null +++ b/src/components/flat-table/flat-table-row/flat-table-row.test.tsx @@ -0,0 +1,1686 @@ +import React from "react"; +import { + render, + screen, + within, + fireEvent, + waitFor, +} from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import CarbonProvider from "components/carbon-provider"; +import FlatTableRow from "./flat-table-row.component"; +import FlatTableCell from "../flat-table-cell/flat-table-cell.component"; +import DrawerSidebarContext from "../../drawer/__internal__/drawer-sidebar.context"; +import FlatTableCheckbox from "../flat-table-checkbox"; +import FlatTableRowHeader from "../flat-table-row-header/flat-table-row-header.component"; +import FlatTableHeader from "../flat-table-header/flat-table-header.component"; +import { FlatTableBodyDraggable } from ".."; +import { StyledFlatTableCell } from "../flat-table-cell/flat-table-cell.style"; +import FlatTableContext from "../__internal__/flat-table.context"; + +test("should render with the expected `data-` attributes on the root element when props are passed", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveAttribute("data-component", "flat-table-row"); + expect(row).toHaveAttribute("data-element", "ft-row-data-element"); + expect(row).toHaveAttribute("data-role", "ft-row-data-role"); +}); + +describe("when the `onClick` prop is passed", () => { + it("should set the `tabIndex` to -1", () => { + render( + + + {}}> + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveAttribute("tabindex", "-1"); + }); + + it("should set the exected cursor styling", () => { + render( + + + {}}> + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveStyle("cursor: pointer"); + }); + + it("should call the callback when the user clicks on the row", async () => { + const onClick = jest.fn(); + const user = userEvent.setup(); + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + await user.click(row); + + expect(onClick).toHaveBeenCalled(); + }); + + it("should call the callback when the user presses the enter key and the row is focused", async () => { + const onClick = jest.fn(); + const user = userEvent.setup(); + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + row.focus(); + await user.keyboard("{enter}"); + + expect(onClick).toHaveBeenCalled(); + }); + + it("should call the callback when the user presses the space key and the row is focused", async () => { + const onClick = jest.fn(); + const user = userEvent.setup(); + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + row.focus(); + await user.keyboard(" "); + + expect(onClick).toHaveBeenCalled(); + }); + + it("should not call the callback when the user presses a key other than enter or space and the row is focused", async () => { + const onClick = jest.fn(); + const user = userEvent.setup(); + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + row.focus(); + await user.keyboard("{ArrowRight}"); + + expect(onClick).not.toHaveBeenCalled(); + }); +}); + +describe("when no `onClick` prop is passed", () => { + it("should not set the `tabIndex`", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).not.toHaveAttribute("tabindex"); + }); + + it("should not set the cursor styling", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).not.toHaveStyle("cursor: pointer"); + }); +}); + +test("should render with `data-selected` attribute set to 'true' when `selected` prop is passed and `expandableArea` is 'wholeRow'", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveAttribute("data-selected", "true"); +}); + +test("should render with `data-selected` attribute set to 'false' when `selected` prop is passed and `expandableArea` is not 'wholeRow'", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveAttribute("data-selected", "false"); +}); + +test("should render with `data-selected` attribute not set when `selected` prop is not passed", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).not.toHaveAttribute("data-selected"); +}); + +test("should render with `data-highlighted` attribute set to 'true' when `highlighted` prop is passed and `expandableArea` is 'wholeRow'", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveAttribute("data-highlighted", "true"); +}); + +test("should render with `data-highlighted` attribute set to 'false' when `highlighted` prop is passed and `expandableArea` is not 'wholeRow'", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveAttribute("data-highlighted", "false"); +}); + +test("should render with `data-highlighted` attribute not set when `highlighted` prop is not passed", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).not.toHaveAttribute("data-highlighted"); +}); + +test("should render the first and last cells with the expected border styling when a child of Sidebar", () => { + render( + + + + {}}> + cell1 + cell2 + + +
+
+ ); + const firstCell = screen.getByRole("cell", { name: "cell1" }); + const lastCell = screen.getByRole("cell", { name: "cell2" }); + + expect(firstCell).toHaveStyle("border-left: none"); + expect(lastCell).toHaveStyle("border-right: none"); +}); + +describe("when FlatTableRowHeader children are passed", () => { + it("should render with first cell sticky positioned", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const cell = screen.getByRole("columnheader", { name: "cell1" }); + + expect(cell).toHaveStyle("position: sticky"); + }); + + it("should render with first cell not sticky positioned when the row header is not conditionally rendered", () => { + render( + + + + cell1 + {false && cell2} + {false && cell3} + + +
+ ); + const cell = screen.getByRole("cell", { name: "cell1" }); + + expect(cell).not.toHaveStyle("position: sticky"); + }); + + it("should render with cells preceding a row header sticky positioned and `stickyAlignment` is not set (defaults to 'left')", () => { + render( + + + + cell1 + cell2 + {}} data-role="cell3" /> + cell4 + cell5 + cell6 + + +
+ ); + const cell1 = screen.getByRole("columnheader", { name: "cell1" }); + const cell2 = screen.getByRole("cell", { name: "cell2" }); + // using test-id here as there are other elements with same role etc + const cell3 = screen.getByTestId("cell3"); + const cell4 = screen.getByRole("columnheader", { name: "cell4" }); + const cell5 = screen.getByRole("columnheader", { name: "cell5" }); + const cell6 = screen.getByRole("cell", { name: "cell6" }); + + expect(cell1).toHaveStyle("position: sticky"); + expect(cell2).toHaveStyle("position: sticky"); + expect(cell3).toHaveStyle("position: sticky"); + expect(cell4).toHaveStyle("position: sticky"); + expect(cell5).not.toHaveStyle("position: sticky"); + expect(cell6).not.toHaveStyle("position: sticky"); + }); + + it("should render with cells preceding row header sticky positioned and `stickyAlignment` is 'left'", () => { + render( + + + + cell1 + cell2 + {}} data-role="cell3" /> + + cell4 + + cell5 + cell6 + + +
+ ); + const cell1 = screen.getByRole("columnheader", { name: "cell1" }); + const cell2 = screen.getByRole("cell", { name: "cell2" }); + // using test-id here as there are other elements with same role etc + const cell3 = screen.getByTestId("cell3"); + const cell4 = screen.getByRole("columnheader", { name: "cell4" }); + const cell5 = screen.getByRole("columnheader", { name: "cell5" }); + const cell6 = screen.getByRole("cell", { name: "cell6" }); + + expect(cell1).toHaveStyle("position: sticky"); + expect(cell2).toHaveStyle("position: sticky"); + expect(cell3).toHaveStyle("position: sticky"); + expect(cell4).toHaveStyle("position: sticky"); + expect(cell5).not.toHaveStyle("position: sticky"); + expect(cell6).not.toHaveStyle("position: sticky"); + }); + + // needed to hit coverage + it("should render with expected focus styling when first column is not sticky and row is interactive", () => { + render( + + + + {}}> + cell1 + cell2 + {}} data-role="cell3" /> + cell4 + cell5 + cell6 + + +
+
+ ); + const cell1 = screen.getByRole("cell", { name: "cell1" }); + const cell2 = screen.getByRole("cell", { name: "cell2" }); + // using test-id here as there are other elements with same role etc + const cell3 = screen.getByTestId("cell3"); + + expect(cell1).toHaveStyle("width: calc(100% + 1px)"); + expect(cell2).toHaveStyle("width: calc(100% + 1px)"); + expect(cell3).toHaveStyle("width: calc(100% + 1px)"); + }); + + it("should render with cells following a row header sticky positioned and `stickyAlignment` set to 'right'", () => { + render( + + + + cell1 + cell2 + + cell3 + + {}} data-role="cell4" /> + cell5 + cell6 + + +
+ ); + + const cell1 = screen.getByRole("columnheader", { name: "cell1" }); + const cell2 = screen.getByRole("cell", { name: "cell2" }); + const cell3 = screen.getByRole("columnheader", { name: "cell3" }); + // using test-id here as there are other elements with same role etc + const cell4 = screen.getByTestId("cell4"); + const cell5 = screen.getByRole("columnheader", { name: "cell5" }); + const cell6 = screen.getByRole("cell", { name: "cell6" }); + + expect(cell1).not.toHaveStyle("position: sticky"); + expect(cell2).not.toHaveStyle("position: sticky"); + expect(cell3).toHaveStyle("position: sticky"); + expect(cell4).toHaveStyle("position: sticky"); + expect(cell5).toHaveStyle("position: sticky"); + expect(cell6).toHaveStyle("position: sticky"); + }); + + test("should throw an error when a left aligned header has a higher index than the right aligned", () => { + const consoleSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + const errorMessage = `Do not render a right hand side \`FlatTableRowHeader\` before left hand side \`FlatTableRowHeader\``; + + expect(() => { + render( + + + + cell1 + cell2 + + cell3 + + + cell4 + + cell5 + cell6 + + +
+ ); + }).toThrow(errorMessage); + + consoleSpy.mockRestore(); + }); +}); + +describe("when the row is `expandable`", () => { + it("should apply the expected cursor styling to the parent row when `expandableArea` is 'wholeRow'", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row", { name: "cell1 cell2" }); + + expect(row).toHaveStyle("cursor: pointer"); + }); + + it("should set tabIndex to -1 when `expandableArea` is 'wholeRow'", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + + expect(row).toHaveAttribute("tabindex", "-1"); + }); + + it("should not set tabIndex on the row when `expandableArea` is 'firstColumn'", () => { + render( + + + + cell1 + cell2 + + +
+ ); + const row = screen.getByRole("row"); + const cell = screen.getByRole("cell", { name: "cell1" }); + + expect(row).not.toHaveAttribute("tabindex"); + expect(cell).toHaveAttribute("tabindex", "-1"); + }); + + it("should add and apply the expected styling to the chevron icon", () => { + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + const cell1 = screen.getByRole("cell", { name: "cell1" }); + const icon = within(cell1).getByTestId("icon"); + + expect(icon).toHaveAttribute("data-element", "chevron_down_thick"); + expect(icon).toHaveStyle({ + transition: "transform 0.3s", + transform: "rotate(-90deg)", + }); + }); + + it("should add icon to the second cell when the first column contains checkbox cells", () => { + render( + + + + {}} /> + sub1cell2 + , + + {}} /> + sub2cell2 + , + ]} + > + {}} /> + cell2 + + +
+ ); + const cell2 = screen.getByRole("cell", { name: "cell2" }); + const icon = within(cell2).getByTestId("icon"); + + expect(icon).toHaveAttribute("data-element", "chevron_down_thick"); + }); + + it("should update the styling of the icon when the sub rows have been expanded", () => { + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + const cell1 = screen.getByRole("cell", { name: "cell1" }); + const icon = within(cell1).getByTestId("icon"); + + expect(icon).toHaveAttribute("data-element", "chevron_down_thick"); + expect(icon).not.toHaveStyle("transform: rotate(-90deg)"); + }); + + it("should not display the sub rows by default", () => { + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + const subRow1 = screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }); + const subRow2 = screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }); + + expect(subRow1).not.toBeInTheDocument(); + expect(subRow2).not.toBeInTheDocument(); + }); + + it("should expand the sub rows when the user clicks on the parent row and `expandableArea` is 'wholeRow'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + await user.click(screen.getByRole("row", { name: "cell1 cell2" })); + jest.advanceTimersByTime(300); + + expect( + await screen.findByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + await screen.findByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + jest.useRealTimers(); + }); + + it("should expand the sub rows when the user presses the Enter key on the focused parent row and `expandableArea` is 'wholeRow'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("row", { name: "cell1 cell2" }).focus(); + await user.keyboard("{Enter}"); + jest.advanceTimersByTime(300); + + expect( + await screen.findByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + await screen.findByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + jest.useRealTimers(); + }); + + it("should expand the sub rows when the user presses the Space key on the focused parent row and `expandableArea` is 'wholeRow'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("row", { name: "cell1 cell2" }).focus(); + await user.keyboard(" "); + jest.advanceTimersByTime(300); + + expect( + await screen.findByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + await screen.findByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + jest.useRealTimers(); + }); + + it("should not expand the sub rows when the user presses a key other than Enter/Space on the focused parent row and `expandableArea` is 'wholeRow'", async () => { + const user = userEvent.setup(); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("row", { name: "cell1 cell2" }).focus(); + await user.keyboard("{ArrowRight}"); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + }); + + it("should display the sub rows when the `expanded` prop is true", () => { + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + + expect( + screen.getByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + screen.getByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + }); + + it("should collapse the sub rows when the user clicks on the parent row and they are `expanded`", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + await user.click(screen.getByRole("row", { name: "cell1 cell2" })); + jest.advanceTimersByTime(300); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + jest.useRealTimers(); + }); + + it("should collapse the sub rows when the user pressed Enter key on the focused parent row and they are `expanded`", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("row", { name: "cell1 cell2" }).focus(); + await user.keyboard("{Enter}"); + jest.advanceTimersByTime(300); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + jest.useRealTimers(); + }); + + it("should collapse the sub rows when the user pressed Space key on the focused parent row and they are `expanded`", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("row", { name: "cell1 cell2" }).focus(); + await user.keyboard("{Enter}"); + jest.advanceTimersByTime(300); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + jest.useRealTimers(); + }); + + it("should not collapse the sub rows when the user pressed key other than Enter/Space on the focused parent row and they are `expanded`", async () => { + const user = userEvent.setup(); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("row", { name: "cell1 cell2" }).focus(); + await user.keyboard("{ArrowRight}"); + + expect( + screen.getByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + screen.getByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + }); + + it("should apply the expected cursor styling to the first cell of the parent row and `expandableArea` is 'firstColumn'", () => { + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + + expect(screen.getByRole("cell", { name: "cell1" })).toHaveStyle( + "cursor: pointer" + ); + expect(screen.getByRole("cell", { name: "cell2" })).not.toHaveStyle( + "cursor: pointer" + ); + }); + + it("should expand the sub rows when the user clicks on the first cell of the parent row and `expandableArea` is 'firstColumn'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + await user.click(screen.getByRole("cell", { name: "cell1" })); + jest.advanceTimersByTime(300); + + expect( + await screen.findByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + await screen.findByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + jest.useRealTimers(); + }); + + it("should expand the sub rows when the user presses the Enter key on the focused first cell of the parent row and `expandableArea` is 'firstColumn'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("cell", { name: "cell1" }).focus(); + await user.keyboard("{Enter}"); + jest.advanceTimersByTime(300); + + expect( + await screen.findByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + await screen.findByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + jest.useRealTimers(); + }); + + it("should expand the sub rows when the user presses the Space key on the focused first cell of the parent row and `expandableArea` is 'firstColumn'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("cell", { name: "cell1" }).focus(); + await user.keyboard(" "); + jest.advanceTimersByTime(300); + + expect( + await screen.findByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + await screen.findByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + jest.useRealTimers(); + }); + + it("should not expand the sub rows when the user clicks on the second cell of the parent row when they are `expanded` and `expandableArea` is 'firstColumn'", async () => { + const user = userEvent.setup(); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + await user.click(screen.getByRole("cell", { name: "cell2" })); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + }); + + it("should not expand the sub rows when the user presses key other than Enter/Space on the focused first cell of the parent row when they are `expanded` and `expandableArea` is 'firstColumn'", async () => { + const user = userEvent.setup(); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("cell", { name: "cell2" }).focus(); + await user.keyboard("{ArrowRight}"); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + }); + + it("should collapse the sub rows when the user clicks on the first cell of the parent row when they are `expanded` and `expandableArea` is 'firstColumn'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + await user.click(screen.getByRole("cell", { name: "cell1" })); + jest.advanceTimersByTime(300); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + jest.useRealTimers(); + }); + + it("should collapse the sub rows when the user presses the Enter key on the focused first cell of the parent row when they are `expanded` and `expandableArea` is 'firstColumn'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("cell", { name: "cell1" }).focus(); + await user.keyboard("{Enter}"); + jest.advanceTimersByTime(300); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + jest.useRealTimers(); + }); + + it("should collapse the sub rows when the user presses the Space key on the focused first cell of the parent row when they are `expanded` and `expandableArea` is 'firstColumn'", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("cell", { name: "cell1" }).focus(); + await user.keyboard(" "); + jest.advanceTimersByTime(300); + + expect( + screen.queryByRole("row", { name: "sub1cell1 sub1cell2" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1 sub2cell2" }) + ).not.toBeInTheDocument(); + jest.useRealTimers(); + }); + + it("should not collapse the sub rows when the user clicks on the second cell of the parent row when they are `expanded` and `expandableArea` is 'firstColumn'", async () => { + const user = userEvent.setup(); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + await user.click(screen.getByRole("cell", { name: "cell2" })); + + expect( + screen.getByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + screen.getByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + }); + + it("should not collapse the sub rows when the user presses key other than Enter/Space on the focused first cell of the parent row when they are `expanded` and `expandableArea` is 'firstColumn'", async () => { + const user = userEvent.setup(); + render( + + + + sub1cell1 + sub1cell2 + , + + sub2cell1 + sub2cell2 + , + ]} + > + cell1 + cell2 + + +
+ ); + screen.getByRole("cell", { name: "cell1" }).focus(); + await user.keyboard("{ArrowRight}"); + + expect( + screen.getByRole("row", { name: "sub1cell1 sub1cell2" }) + ).toBeVisible(); + expect( + screen.getByRole("row", { name: "sub2cell1 sub2cell2" }) + ).toBeVisible(); + }); + + it("should support expanding and collapsing sub rows when the user clicks on the parent row and draggable body is used", async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + render( + + + + sub1cell1 + + } + id={0} + > + cell1 + + + sub2cell1 + + } + id={1} + > + cell2 + + + sub3cell1 + + } + id={2} + > + cell3 + + +
+ ); + const row1 = screen.getByRole("row", { name: "cell1" }); + const row2 = screen.getByRole("row", { name: "cell2" }); + await user.click(row1); + jest.advanceTimersByTime(300); + await user.click(row2); + jest.advanceTimersByTime(300); + + expect(await screen.findByRole("row", { name: "sub1cell1" })).toBeVisible(); + expect(await screen.findByRole("row", { name: "sub2cell1" })).toBeVisible(); + + await user.click(row1); + jest.advanceTimersByTime(300); + await user.click(row2); + jest.advanceTimersByTime(300); + + expect( + screen.queryByRole("row", { name: "sub1cell1" }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("row", { name: "sub2cell1" }) + ).not.toBeInTheDocument(); + jest.useRealTimers(); + }); + + // needed for coverage + test("should set the cursor to pointer when the row is expandable via the firstColumn and all children are checkbox cells", () => { + render( + + + + {}} /> + {}} /> + , + + {}} /> + {}} /> + , + ]} + > + {}} data-role="cell1" /> + {}} /> + + +
+ ); + + expect(screen.getByTestId("cell1")).toHaveStyle("cursor: pointer"); + }); + + it("should apply the expected padding on the sub row's first cell's content when the table `size` is 'compact'", () => { + render( + "foo" }} + > + + + + + sub1cell1 + sub1cell2 + + + } + > + cell1 + cell2 + + +
+
+ ); + const subRowCell1 = screen.getByRole("cell", { name: "sub1cell1" }); + const content1 = within(subRowCell1).getByTestId("flat-table-cell-content"); + const subRowCell2 = screen.getByRole("cell", { name: "sub1cell2" }); + const content2 = within(subRowCell2).getByTestId("flat-table-cell-content"); + + expect(content1).toHaveStyle("padding-left: 32px"); + expect(content2).not.toHaveStyle("padding-left: 32px"); + }); +}); + +describe("with a ref", () => { + it("the ref should be forwarded", () => { + const mockRef = { current: null }; + + render( + + + + cell1 + cell2 + cell3 + cell4 + + +
+ ); + + expect(mockRef.current).toBe( + screen.getByRole("row", { name: "cell1 cell2 cell3 cell4" }) + ); + }); + + it("the input callback ref should be called with the DOM element", () => { + const mockRef = jest.fn(); + + render( + + + + cell1 + cell2 + cell3 + cell4 + + +
+ ); + + expect(mockRef).toHaveBeenCalledWith( + screen.getByRole("row", { name: "cell1 cell2 cell3 cell4" }) + ); + }); +}); + +// for coverage +test("should render the expected background color styles when `bgColor` prop is passed", () => { + render( + + + + cell1 + {}} data-role="cell2" /> + cell3 + + +
+ ); + const cell1 = screen.getByRole("cell", { name: "cell1" }); + const cell2 = screen.getByTestId("cell2"); + const cell3 = screen.getByRole("columnheader", { name: "cell3" }); + + expect(cell1).toHaveStyle("background-color: red"); + expect(cell2).toHaveStyle("background-color: red"); + expect(cell3).toHaveStyle("background-color: red"); +}); + +// for coverage +test("should render the expected border bottom color styles when `horizontalBorderColor` prop is passed", () => { + render( + + + + cell1 + {}} data-role="cell2" /> + cell3 + + +
+ ); + const cell1 = screen.getByRole("cell", { name: "cell1" }); + const cell2 = screen.getByTestId("cell2"); + const cell3 = screen.getByRole("columnheader", { name: "cell3" }); + + expect(cell1).toHaveStyle("border-bottom-color: red"); + expect(cell2).toHaveStyle("border-bottom-color: red"); + expect(cell3).toHaveStyle("border-bottom-color: red"); +}); + +// for coverage +test("should render the expected border bottom color styles when `horizontalBorderSize` prop is 'medium'", () => { + render( + + + + cell1 + + +
+ ); + const row = screen.getByRole("row", { name: "cell1" }); + + expect(row).toHaveStyleRule( + "border-bottom", + "2px solid var(--colorsUtilityMajor100)", + { modifier: `${StyledFlatTableCell}` } + ); +}); + +// for coverage +test("should apply the expected border styling when row is dragged and table is in sidebar", async () => { + render( + + + + + Row one + + + Row two + + + Row three + + +
+
+ ); + const elementToDrag = screen.getByRole("row", { name: "Row one" }); + + fireEvent.dragStart(elementToDrag); + fireEvent.dragEnter(elementToDrag); + fireEvent.dragOver(elementToDrag); + + await waitFor(() => { + expect(elementToDrag).toHaveStyleRule( + "border", + "var(--colorsUtilityMajor300) 2px solid" + ); + }); +}); diff --git a/src/components/flat-table/flat-table-test.stories.tsx b/src/components/flat-table/flat-table-test.stories.tsx index b440f1c5bc..d7960a4c03 100644 --- a/src/components/flat-table/flat-table-test.stories.tsx +++ b/src/components/flat-table/flat-table-test.stories.tsx @@ -36,7 +36,8 @@ export default { "FlatTableSizeFocus", "FlatTableInsideDrawer", "FlatRowHeaderWithNoPaddingAndButtons", - "MinimalDesign", + "FlatTableThemesWithAlternateHeaderBackground", + "FlatTableThemesWithStickyHead", ], parameters: { info: { disable: true }, @@ -790,3 +791,82 @@ export const FlatRowHeaderWithNoPaddingAndButtons = () => { ); }; + +const themes: Array = [ + "dark", + "light", + "transparent-base", + "transparent-white", +]; + +export const FlatTableThemesWithAlternateHeaderBackground = () => ( + <> + {themes.map((ftTheme, index) => ( + + + + + Name + Location + + + + + John Doe + London + + + + + ))} + +); +FlatTableThemesWithAlternateHeaderBackground.parameters = { + chromatic: { disableSnapshot: false }, + themeProvider: { chromatic: { theme: "sage" } }, +}; + +export const FlatTableThemesWithStickyHead = () => ( + <> + {themes.map((ftTheme, index) => ( + + + + + Name + Location + + + + + John Doe + London + + + John Doe + London + + + John Doe + London + + + + + ))} + +); +FlatTableThemesWithStickyHead.parameters = { + chromatic: { disableSnapshot: false }, + themeProvider: { chromatic: { theme: "sage" } }, +}; diff --git a/src/components/flat-table/flat-table.component.tsx b/src/components/flat-table/flat-table.component.tsx index 7b64c532fd..4a51231394 100644 --- a/src/components/flat-table/flat-table.component.tsx +++ b/src/components/flat-table/flat-table.component.tsx @@ -11,8 +11,11 @@ import { import DrawerSidebarContext from "../drawer/__internal__/drawer-sidebar.context"; import Events from "../../__internal__/utils/helpers/events/events"; import FlatTableContext from "./__internal__/flat-table.context"; +import { TagProps } from "../../__internal__/utils/helpers/tags"; -export interface FlatTableProps extends MarginProps { +export interface FlatTableProps + extends MarginProps, + Omit { /** The HTML id of the element that contains a description of this table. */ ariaDescribedby?: string; /** A string to render as the table's caption */ @@ -188,6 +191,7 @@ export const FlatTable = ({ // it may be that an element within the row currently has focus const index = findParentIndexOfFocusedChild(focusableElementsArray); + /* istanbul ignore else */ if (index !== -1 && index < focusableElementsArray.length) { (focusableElementsArray[index + 1] as HTMLElement)?.focus(); } @@ -228,7 +232,6 @@ export const FlatTable = ({ return ( {footer && ( - + {footer} )} diff --git a/src/components/flat-table/flat-table.pw.tsx b/src/components/flat-table/flat-table.pw.tsx index 536d5e7f16..4d84f39823 100644 --- a/src/components/flat-table/flat-table.pw.tsx +++ b/src/components/flat-table/flat-table.pw.tsx @@ -33,6 +33,7 @@ import { FlatTableCheckboxComponent, FlatTableFirstColumnHasRowspan, FlatTableLastColumnHasRowspan, + FlatTableWithStickyColumn, } from "./components.test-pw"; import Icon from "../icon"; import { getDataElementByValue } from "../../../playwright/components"; @@ -666,6 +667,38 @@ test.describe("Prop tests", () => { ).toBeInViewport(); }); + test("should render with sticky columns when FlatTableRowHeader is used", async ({ + mount, + page, + }) => { + await mount(); + + const bodyCell1 = flatTableBodyRowByPosition(page, 0).locator("td").nth(0); + const bodyStickyCell = flatTableBodyRowByPosition(page, 0) + .locator("th") + .nth(0); + const input = flatTable(page).locator("input"); + + await expect( + flatTableHeaderRowByPosition(page, 0).locator("th").nth(0) + ).toHaveCSS("position", "sticky"); + await expect( + flatTableHeaderRowByPosition(page, 0).locator("th").nth(1) + ).toHaveCSS("position", "sticky"); + await expect(bodyCell1).toHaveCSS("position", "sticky"); + await expect(bodyStickyCell).toHaveCSS("position", "sticky"); + + await expect(bodyCell1).toBeInViewport(); + await expect(bodyStickyCell).toBeInViewport(); + await expect(input).not.toBeInViewport(); + + await input.focus(); + + await expect(bodyCell1).toBeInViewport(); + await expect(bodyStickyCell).toBeInViewport(); + await expect(input).toBeInViewport(); + }); + test(`should render with colSpan set to make cells span 4 columns`, async ({ mount, page, diff --git a/src/components/flat-table/flat-table.spec.tsx b/src/components/flat-table/flat-table.spec.tsx deleted file mode 100644 index 51df045aa5..0000000000 --- a/src/components/flat-table/flat-table.spec.tsx +++ /dev/null @@ -1,1385 +0,0 @@ -import React from "react"; -import { ReactWrapper, mount } from "enzyme"; - -import { - render as rtlRender, - screen, - fireEvent, - waitFor, -} from "@testing-library/react"; - -import FlatTable, { FlatTableProps } from "./flat-table.component"; -import FlatTableHead from "./flat-table-head/flat-table-head.component"; -import FlatTableBody from "./flat-table-body/flat-table-body.component"; -import FlatTableRow from "./flat-table-row/flat-table-row.component"; -import FlatTableHeader from "./flat-table-header/flat-table-header.component"; -import FlatTableCell from "./flat-table-cell/flat-table-cell.component"; -import FlatTableCheckbox from "./flat-table-checkbox/flat-table-checkbox.component"; -import FlatTableRowHeader from "./flat-table-row-header/flat-table-row-header.component"; -import { - assertStyleMatch, - testStyledSystemMargin, -} from "../../__spec_helper__/__internal__/test-utils"; -import StyledFlatTableHeader from "./flat-table-header/flat-table-header.style"; -import StyledFlatTableHead from "./flat-table-head/flat-table-head.style"; -import { StyledFlatTableRowHeader } from "./flat-table-row-header/flat-table-row-header.style"; -import StyledFlatTableCheckbox from "./flat-table-checkbox/flat-table-checkbox.style"; -import { - StyledFlatTable, - StyledFlatTableWrapper, - StyledFlatTableFooter, - StyledTableContainer, -} from "./flat-table.style"; -import DrawerSidebarContext from "../drawer/__internal__/drawer-sidebar.context"; -import { StyledFlatTableCell } from "./flat-table-cell/flat-table-cell.style"; -import StyledFlatTableRow from "./flat-table-row/flat-table-row.style"; -import cellSizes from "./cell-sizes.style"; -import { FLAT_TABLE_SIZES } from "./flat-table.config"; -import Pager from "../pager/pager.component"; -import Logger from "../../__internal__/utils/logger"; -import CarbonProvider from "../carbon-provider"; - -// mock Logger.deprecate so that no console warnings occur while running the tests -const loggerSpy = jest.spyOn(Logger, "deprecate"); - -const RenderComponent = (props: Partial = {}) => ( - - - - row header - header1 - header2 - header3 - - - - - row header - cell1 - cell2 - cell3 - - - row header - cell1 - - - -); - -function renderFlatTable(props = {}) { - return mount(); -} - -function renderFlatTableWithDiv(props = {}) { - return mount( -
- - - - row header - header1 - header2 - header3 - - - - - row header - cell1 - cell2 - cell3 - - - row header - cell1 - - - -
- ); -} - -describe("FlatTable", () => { - beforeAll(() => { - loggerSpy.mockImplementation(() => {}); - }); - - afterAll(() => { - loggerSpy.mockRestore(); - }); - - it("ariaDescribedby prop should have been propagated to the table", () => { - const customId = "foo"; - const wrapper = renderFlatTable({ ariaDescribedby: customId }); - - expect(wrapper.find(StyledFlatTable).prop("aria-describedby")).toBe( - customId - ); - }); - - describe("when a data prop is added", () => { - it("should be added to the root element", () => { - const wrapper = renderFlatTable({ "data-role": "test" }); - - expect(wrapper.find(StyledFlatTableWrapper).props()["data-role"]).toEqual( - "test" - ); - }); - }); - - describe('when rendered with proper table data and "hasStickyHead" prop set to true', () => { - let wrapper: ReactWrapper; - - beforeEach(() => { - wrapper = renderFlatTable({ hasStickyHead: true }); - }); - - it("should have the overflow-y css property set to to auto", () => { - expect(wrapper.find(StyledFlatTableWrapper)).toHaveStyleRule( - "overflow-y", - "auto" - ); - }); - - it("should set position sticky on all th inside the table head", () => { - assertStyleMatch( - { - position: "sticky", - top: "0", - left: "0", - zIndex: "1005", - }, - wrapper.find(StyledFlatTableWrapper), - { modifier: `${StyledFlatTableHead}` } - ); - }); - - it('then all Headers should have proper styling if `colorTheme="dark"`', () => { - wrapper = renderFlatTable({ colorTheme: "dark" }); - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor400)", - borderRight: "1px solid var(--colorsUtilityMajor300)", - color: "var(--colorsUtilityYang100)", - borderBottomColor: "var(--colorsUtilityMajor300)", - }, - - wrapper.find(StyledFlatTableWrapper), - { modifier: `${StyledFlatTableHeader}` } - ); - }); - - it('then all Headers should have proper styling if `colorTheme="light"`', () => { - wrapper = renderFlatTable({ colorTheme: "light" }); - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor100)", - borderRight: "1px solid var(--colorsUtilityMajor150)", - borderBottomColor: "var(--colorsUtilityMajor150)", - }, - - wrapper.find(StyledFlatTableWrapper), - { modifier: `${StyledFlatTableHeader}` } - ); - }); - - it('then all Headers should have proper styling if `colorTheme="transparent-base"`', () => { - wrapper = renderFlatTable({ colorTheme: "transparent-base" }); - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor025)", - borderBottomColor: "var(--colorsUtilityMajor100)", - }, - - wrapper.find(StyledFlatTableWrapper), - { modifier: `${StyledFlatTableHeader}` } - ); - }); - - it('then all Headers should have proper styling if `colorTheme="transparent-white"`', () => { - wrapper = renderFlatTable({ colorTheme: "transparent-white" }); - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityYang100)", - borderBottomColor: "var(--colorsUtilityMajor100)", - }, - - wrapper.find(StyledFlatTableWrapper), - { modifier: `${StyledFlatTableHeader}` } - ); - }); - }); - - describe("when FlatTable is a child of Sidebar", () => { - let wrapper: ReactWrapper; - beforeEach(() => { - wrapper = mount( - - - - - foo - - - - - ); - }); - - it.each([ - ["StyledFlatTableHeader", StyledFlatTableHeader], - ["StyledFlatTableRowHeader", StyledFlatTableRowHeader], - ["StyledFlatTableCheckbox", StyledFlatTableCheckbox], - ])("should override the styles for %s", (id, el) => { - const modifierString = - id === "StyledFlatTableHeader" ? el : `${StyledFlatTableHead} ${el}`; - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor040)", - borderRight: "2px solid var(--colorsUtilityMajor040)", - color: "var(--colorsUtilityYin090)", - }, - wrapper.find(StyledFlatTableWrapper), - { modifier: `${modifierString}` } - ); - }); - }); - - describe.each(["dark", "light", "transparent-base", "transparent-base"])( - "when isZebra prop is set to true and colorTheme is %s", - (colorTheme) => { - it("should apply hover styling to any FlatTableCheckbox not rendered as a th element", () => { - const wrapper = renderFlatTable({ isZebra: true, colorTheme }); - - assertStyleMatch( - { - backgroundColor: "var(--colorsUtilityMajor025)", - }, - wrapper.find(StyledFlatTable), - { - modifier: `${StyledFlatTableRow}:hover ${StyledFlatTableCheckbox}:not(th)`, - } - ); - }); - } - ); - - describe("when the caption prop is set", () => { - it("then that caption should be rendered in the table", () => { - const captionText = "foo"; - const wrapper = renderFlatTable({ caption: captionText }); - - expect(wrapper.find("caption").exists()).toBe(true); - expect(wrapper.find("caption").text()).toBe(captionText); - }); - }); - - describe.each(FLAT_TABLE_SIZES)("when the size prop is set to %s", (size) => { - const { fontSize, paddingSize } = cellSizes[size]; - const expectedStyles = { - fontSize, - paddingLeft: paddingSize, - paddingRight: paddingSize, - }; - - it("then expected styles should be applied to table cells underlying div", () => { - const wrapper = renderFlatTable({ size }); - - assertStyleMatch(expectedStyles, wrapper.find(StyledFlatTable), { - modifier: `${StyledFlatTableCell} > div`, - }); - }); - - it("then expected styles should be applied to table headers underlying div", () => { - const wrapper = renderFlatTable({ size }); - - assertStyleMatch(expectedStyles, wrapper.find(StyledFlatTable), { - modifier: `${StyledFlatTableHeader} > div`, - }); - }); - - it("then expected styles should be applied to row headers underlying div", () => { - const wrapper = renderFlatTable({ size }); - - assertStyleMatch(expectedStyles, wrapper.find(StyledFlatTable), { - modifier: `${StyledFlatTableRowHeader} > div`, - }); - }); - - it("then the Table Rows should have expected height", () => { - const wrapper = renderFlatTable({ size }); - - assertStyleMatch( - { - height: cellSizes[size].height, - }, - wrapper.find(StyledFlatTable), - { - modifier: `${StyledFlatTableRow}`, - } - ); - }); - }); - - describe("StyledFlatTableWrapper", () => { - let wrapper; - const Footer = () =>
foo
; - it("applies correct styles when a div is larger than the FlatTable", () => { - wrapper = renderFlatTableWithDiv({ footer: