diff --git a/packages/react/src/components/StructuredList/StructuredList-test.js b/packages/react/src/components/StructuredList/StructuredList-test.js index 510347f55049..bb0e159fee99 100644 --- a/packages/react/src/components/StructuredList/StructuredList-test.js +++ b/packages/react/src/components/StructuredList/StructuredList-test.js @@ -1,205 +1,355 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2022, 2022 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; import { StructuredListWrapper, StructuredListHead, - StructuredListInput, StructuredListBody, StructuredListRow, + StructuredListInput, StructuredListCell, -} from './StructuredList'; -import { mount, shallow } from 'enzyme'; +} from '@carbon/react'; +import { CheckmarkFilled } from '@carbon/react/icons'; const prefix = 'cds'; - -describe('StructuredListWrapper', () => { - describe('Renders as expected', () => { - const wrapper = shallow( - hi - ); - +const dataTestId = 'structured-list-test-id'; +const customHeadClass = 'structured-list-header-custom-class'; +const customRowClass = 'structured-list-row-custom-class'; +const customBodyClass = 'structured-list-body-custom-class'; +const customInputClass = 'structured-list-input-custom-class'; +const customCellClass = 'structured-list-cell-custom-class'; +const inputNameValue = 'list-radio-input'; +const onKeyDownHandlerFn = jest.fn(); +const onKeyDownBodyHandlerFn = jest.fn(); + +const renderComponent = ({ ...rest } = {}) => { + const { bodyProps, bodyCellProps, headProps, wrapperProps } = rest; + return render( + + + + ColumnA + ColumnB + ColumnC + + + + + + Row 1 + + Row 1, Col 2 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui + magna, finibus id tortor sed, aliquet bibendum augue. Aenean posuere + sem vel euismod dignissim. Nulla ut cursus dolor. Pellentesque + vulputate nisl a porttitor interdum. + + + + Row 2 + Row 2 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui + magna, finibus id tortor sed, aliquet bibendum augue. Aenean posuere + sem vel euismod dignissim. Nulla ut cursus dolor. Pellentesque + vulputate nisl a porttitor interdum. + + + + + ); +}; + +const structuredListBodyRowGenerator = (numRows, rest) => { + return Array.apply(null, Array(numRows)).map((n, i) => ( + + Row {i} + Row {i}, Col 2 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui magna, + finibus id tortor sed, aliquet bibendum augue. Aenean posuere sem vel + euismod dignissim. Nulla ut cursus dolor. Pellentesque vulputate nisl a + porttitor interdum. + + + + + select an option + + + + )); +}; + +const renderSelectionVariant = ({ ...rest } = {}) => { + const { inputProps, wrapperProps } = rest; + return render( + + + + ColumnA + ColumnB + ColumnC + {''} + + + + {structuredListBodyRowGenerator(4, inputProps)} + + + ); +}; + +describe('StructuredList', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('StructuredListWrapper', () => { + it('should spread extra props onto outermost element', () => { + renderComponent({ wrapperProps: { 'data-testid': dataTestId } }); + expect(screen.getByLabelText('Structured list section')).toHaveAttribute( + 'data-testid', + dataTestId + ); + }); it('should have the expected classes', () => { - expect( - wrapper.find('div').hasClass(`${prefix}--structured-list`) - ).toEqual(true); + renderComponent(); + expect(screen.getByRole('table')).toHaveClass( + `${prefix}--structured-list` + ); }); - - it('Should add extra classes that are passed via className', () => { - expect(wrapper.find('div').hasClass('extra-class')).toEqual(true); + it('should add extra classes that are passed via classname', () => { + renderComponent({ wrapperProps: { className: 'extra-class' } }); + expect(screen.getByRole('table')).toHaveClass('extra-class'); }); - - it('By default, selection prop is false', () => { - expect(wrapper.hasClass(`${prefix}--structured-list--selection`)).toEqual( - false + it('should default selection prop as false', () => { + renderComponent(); + expect(screen.getByRole('table')).not.toHaveClass( + `${prefix}--structured-list--selection` ); }); - - it('Should add the modifier class for selection when selection prop is true', () => { - wrapper.setProps({ selection: true }); - expect( - wrapper.find('div').hasClass(`${prefix}--structured-list--selection`) - ).toEqual(true); + it('should add the modifier class for selection when selection prop is true', () => { + renderComponent({ wrapperProps: { selection: true } }); + expect(screen.getByRole('table')).toHaveClass( + `${prefix}--structured-list--selection` + ); }); - - it('Should add the modifier class for condensed when isCondensed prop is true', () => { - wrapper.setProps({ isCondensed: true }); - expect( - wrapper.find('div').hasClass(`${prefix}--structured-list--condensed`) - ).toEqual(true); + it('should add the modifier class for condensed when isCondensed prop is true', () => { + renderComponent({ wrapperProps: { isCondensed: true } }); + expect(screen.getByRole('table')).toHaveClass( + `${prefix}--structured-list--condensed` + ); }); - - it('Should add the modifier class for flush when isFlush prop is true', () => { - wrapper.setProps({ isFlush: true }); - expect( - wrapper.find('div').hasClass(`${prefix}--structured-list--flush`) - ).toEqual(true); + it('should add the modifier class for flush when isFlush prop is true', () => { + renderComponent({ wrapperProps: { isFlush: true } }); + expect(screen.getByRole('table')).toHaveClass( + `${prefix}--structured-list--flush` + ); + }); + it('should allow a custom aria label to be passed in', () => { + const testAriaLabel = 'custom-test-aria-label'; + renderComponent({ wrapperProps: { ariaLabel: testAriaLabel } }); + expect(screen.getByLabelText(testAriaLabel)).toBeInTheDocument(); + }); + it('should check that children are rendered', () => { + renderComponent(); + expect(screen.getByText('ColumnA')).toBeVisible(); }); }); -}); - -describe('StructuredListHead', () => { - describe('Renders as expected', () => { - const wrapper = shallow( - hi - ); + describe('StructuredListHeader', () => { it('should have the expected classes', () => { - expect(wrapper.hasClass(`${prefix}--structured-list-thead`)).toEqual( - true - ); + renderComponent(); + const rowGroups = screen.getAllByRole('rowgroup'); + const headerRowGroup = [...rowGroups].filter((rowgroup) => + rowgroup.classList.contains(`${prefix}--structured-list-thead`) + )[0]; + expect(headerRowGroup).toHaveClass(`${prefix}--structured-list-thead`); }); - - it('Should add extra classes that are passed via className', () => { - expect(wrapper.hasClass('extra-class')).toEqual(true); + it('should add extra classes that are passed via className', () => { + renderComponent(); + const rowGroups = screen.getAllByRole('rowgroup'); + const headerRowGroup = [...rowGroups].filter((rowgroup) => + rowgroup.classList.contains(`${prefix}--structured-list-thead`) + )[0]; + expect(headerRowGroup).toHaveClass(customHeadClass); }); - - it('Should accept other props from ...other', () => { - const wrapperProps = shallow( - hi - ); - expect(wrapperProps.props().title).toEqual('title'); + it('should check that children are rendered', () => { + renderComponent(); + expect(screen.getByText('ColumnA')).toBeVisible(); + }); + it('should accept rest props', () => { + const customHeadTitle = 'custom-header-title'; + renderComponent({ headProps: { title: customHeadTitle } }); + expect(screen.getByTitle(customHeadTitle)).toBeInTheDocument(); }); }); -}); - -describe('StructuredListInput', () => { - describe('Renders as expected', () => { - const wrapper = shallow(); - it('should have the expected classes', () => { - expect(wrapper.hasClass(`${prefix}--structured-list-input`)).toEqual( - true - ); + describe('StructuredListRow', () => { + it('should check that children are rendered', () => { + renderComponent(); + expect(screen.getByText('ColumnA')).toBeVisible(); }); - - it('Should add extra classes that are passed via className', () => { - expect(wrapper.hasClass('extra-class')).toEqual(true); + it('should add extra class that are passed via className', () => { + renderComponent(); + const rows = screen.getAllByRole('row'); + const rowWithCustomClass = [...rows].filter((row) => + row.classList.contains(customRowClass) + )[0]; + expect(rowWithCustomClass).toHaveClass(customRowClass); }); - - it('Should accept other props from ...other', () => { - const wrapperProps = shallow(); - expect(wrapperProps.props().title).toEqual('title'); + it('should check that a row is specified as a header row', () => { + renderComponent(); + const rows = screen.getAllByRole('row'); + const headerRow = [...rows].filter((row) => + row.classList.contains(`${prefix}--structured-list-row--header-row`) + )[0]; + expect(headerRow).toHaveClass( + `${prefix}--structured-list-row--header-row` + ); }); - - it('Should render unique id with multiple inputs when no id prop is given', () => { - const wrapper1 = mount(); - const wrapper2 = mount(); - expect(wrapper1.find('[id]')).not.toEqual(wrapper2.find('[id]')); + it('should add an onKeyDown handler', () => { + const { tab, keyboard } = userEvent; + renderSelectionVariant(); + tab(); + keyboard('[ArrowDown]'); + expect(onKeyDownHandlerFn).toHaveBeenCalledTimes(1); + keyboard('[ArrowDown]'); + expect(onKeyDownHandlerFn).toHaveBeenCalledTimes(2); + keyboard('[ArrowDown]'); + expect(onKeyDownHandlerFn).toHaveBeenCalledTimes(3); }); }); -}); -describe('StructuredListRow', () => { - describe('Renders as expected', () => { - const wrapper = shallow(); - - it('should have the expected classes', () => { - expect(wrapper.hasClass(`${prefix}--structured-list-row`)).toEqual(true); + describe('StructuredListBody', () => { + it('should check that children are rendered', () => { + renderComponent(); + expect(screen.getByText('Row 1')).toBeVisible(); }); - - it('Should add extra classes that are passed via className', () => { - expect(wrapper.hasClass('extra-class')).toEqual(true); + it('should add extra classes that are passed via className', () => { + renderComponent(); + const rowGroups = screen.getAllByRole('rowgroup'); + const listBody = [...rowGroups].filter((rowgroup) => + rowgroup.classList.contains(`${prefix}--structured-list-tbody`) + )[0]; + expect(listBody).toHaveClass(customBodyClass); }); - - it('should use correct class when head prop is true', () => { - wrapper.setProps({ head: true }); - - expect( - wrapper.hasClass(`${prefix}--structured-list-row--header-row`) - ).toEqual(true); + it('should have the expected classes', () => { + renderComponent(); + const rowGroups = screen.getAllByRole('rowgroup'); + const bodyRowGroup = [...rowGroups].filter((rowgroup) => + rowgroup.classList.contains(`${prefix}--structured-list-tbody`) + )[0]; + expect(bodyRowGroup).toHaveClass(`${prefix}--structured-list-tbody`); }); - - it('Should accept other props from ...other', () => { - const wrapperProps = shallow( - hi - ); - expect(wrapperProps.props().title).toEqual('title'); + it('should add an onKeyDown handler', () => { + const { tab, keyboard } = userEvent; + renderSelectionVariant(); + tab(); + keyboard('[ArrowDown]'); + expect(onKeyDownBodyHandlerFn).toHaveBeenCalledTimes(1); + keyboard('[ArrowDown]'); + expect(onKeyDownBodyHandlerFn).toHaveBeenCalledTimes(2); + keyboard('[ArrowDown]'); + expect(onKeyDownBodyHandlerFn).toHaveBeenCalledTimes(3); + }); + it('should accept rest props', () => { + const dataTestId = 'data-testid'; + renderComponent({ bodyProps: { 'data-testid': dataTestId } }); + expect(screen.getByTestId(dataTestId)).toBeInTheDocument(); }); }); -}); - -describe('StructuredListBody', () => { - describe('Renders as expected', () => { - const wrapper = shallow( - hi - ); + describe('StructuredListInput', () => { it('should have the expected classes', () => { - expect(wrapper.hasClass(`${prefix}--structured-list-tbody`)).toEqual( - true - ); + renderSelectionVariant(); + const inputElement = screen.getByTitle('row-0'); + expect(inputElement).toHaveClass(`${prefix}--structured-list-input`); }); - - it('Should add extra classes that are passed via className', () => { - expect(wrapper.hasClass('extra-class')).toEqual(true); + it('should render the input with custom title attribute and add additional classes via className', () => { + renderSelectionVariant(); + const inputElement = screen.getByTitle('row-0'); + expect(inputElement).toHaveClass(customInputClass); }); - - it('Should accept other props from ...other', () => { - const wrapperProps = shallow( - hi + it('should accept an id', () => { + renderSelectionVariant(); + const inputElement = screen.getByTitle('row-0'); + expect(inputElement).toHaveAttribute('id', 'row-0'); + }); + it('should add a custom name attribute to the input element', () => { + renderSelectionVariant(); + const inputElement = screen.getByTitle('row-0'); + expect(inputElement).toHaveAttribute('name', 'list-radio-input'); + }); + it('should render unique id with multiple inputs when no id prop is given', () => { + const { container } = renderSelectionVariant(); + const inputElements = container.querySelectorAll( + `[name=${inputNameValue}]` ); - expect(wrapperProps.props().title).toEqual('title'); + const inputIds = Array.from(inputElements).map((input) => { + return input.id; + }); + const containsDuplicates = (array) => { + if (array.length !== new Set(array).size) { + return true; + } + return false; + }; + expect(containsDuplicates(inputIds)).toBeFalsy(); + }); + it('should accept rest props', () => { + const testAriaLabel = 'test-aria-label'; + renderSelectionVariant({ inputProps: { 'aria-label': testAriaLabel } }); + const allInputs = screen.getAllByLabelText(testAriaLabel); + allInputs.forEach((input) => { + expect(input.getAttribute('aria-label')).toEqual(testAriaLabel); + }); }); }); -}); -describe('StructuredListCell', () => { - describe('Renders as expected', () => { - const wrapper = shallow( - hi - ); - - it('Should add extra classes that are passed via className', () => { - expect(wrapper.hasClass('extra-class')).toEqual(true); + describe('StructuredListCell', () => { + it('should add extra classes that are passed via className', () => { + renderComponent({ bodyCellProps: { className: customCellClass } }); + const bodyCells = screen.getAllByRole('cell'); + expect(bodyCells[0]).toHaveClass(customCellClass); }); - it('should have the expected classes', () => { - expect(wrapper.hasClass(`${prefix}--structured-list-td`)).toEqual(true); + renderComponent(); + const bodyCells = screen.getAllByRole('cell'); + expect(bodyCells[0]).toHaveClass(`${prefix}--structured-list-td`); }); - it('should use correct class when head prop is true', () => { - wrapper.setProps({ head: true }); - expect(wrapper.hasClass(`${prefix}--structured-list-th`)).toEqual(true); + renderComponent(); + const bodyCells = screen.getAllByRole('columnheader'); + expect(bodyCells[0]).toHaveClass(`${prefix}--structured-list-th`); }); - it('should use correct class when noWrap prop is true', () => { - wrapper.setProps({ noWrap: true }); - expect( - wrapper.hasClass(`${prefix}--structured-list-content--nowrap`) - ).toEqual(true); - }); - - it('Should accept other props from ...other', () => { - const wrapperProps = shallow( - hi + renderComponent(); + const bodyCells = screen.getAllByRole('cell'); + expect(bodyCells[0]).toHaveClass( + `${prefix}--structured-list-content--nowrap` ); - expect(wrapperProps.props().title).toEqual('title'); + }); + it('should accept rest props', () => { + const dataTestId = 'data-testid'; + renderComponent({ bodyCellProps: { 'data-testid': dataTestId } }); + expect(screen.getByTestId(dataTestId)).toBeInTheDocument(); }); }); });