diff --git a/packages/react-core/src/components/TreeView/TreeViewListItem.tsx b/packages/react-core/src/components/TreeView/TreeViewListItem.tsx index fcbf541bf61..53c3775fe8f 100644 --- a/packages/react-core/src/components/TreeView/TreeViewListItem.tsx +++ b/packages/react-core/src/components/TreeView/TreeViewListItem.tsx @@ -198,7 +198,7 @@ const TreeViewListItemBase: React.FunctionComponent = ({
  • diff --git a/packages/react-core/src/components/TreeView/__tests__/TreeView.test.tsx b/packages/react-core/src/components/TreeView/__tests__/TreeView.test.tsx index 07f197ab765..cfdb202370f 100644 --- a/packages/react-core/src/components/TreeView/__tests__/TreeView.test.tsx +++ b/packages/react-core/src/components/TreeView/__tests__/TreeView.test.tsx @@ -1,242 +1,316 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { TreeView } from '../TreeView'; -import { Button } from '@patternfly/react-core'; -import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon'; -import FolderOpenIcon from '@patternfly/react-icons/dist/esm/icons/folder-open-icon'; -import { TreeViewSearch } from '../TreeViewSearch'; - -const options = [ - { - name: 'ApplicationLauncher', - id: 'AppLaunch', - children: [ - { - name: 'Application 1', - id: 'App1', - children: [ - { name: 'Settings', id: 'App1Settings' }, - { name: 'Current', id: 'App1Current' } - ] - }, - { - name: 'Application 2', - id: 'App2', - children: [ - { name: 'Settings', id: 'App2Settings' }, - { - name: 'Loader', - id: 'App2Loader', - children: [ - { name: 'Loading App 1', id: 'LoadApp1' }, - { name: 'Loading App 2', id: 'LoadApp2' }, - { name: 'Loading App 3', id: 'LoadApp3' } - ] - } - ] - } - ], - defaultExpanded: true - }, - { - name: 'Cost Management', - id: 'Cost', - children: [ - { - name: 'Application 3', - id: 'App3', - children: [ - { name: 'Settings', id: 'App3Settings' }, - { name: 'Current', id: 'App3Current' } - ] - } - ] - }, - { - name: 'Sources', - id: 'Sources', - children: [{ name: 'Application 4', id: 'App4', children: [{ name: 'Settings', id: 'App4Settings' }] }] - }, - { - name: 'Really really really long folder name that overflows the container it is in', - id: 'Long', - children: [{ name: 'Application 5', id: 'App5' }] - } -]; - -const flagOptions = [ - { - name: 'ApplicationLauncher', - id: 'AppLaunch', - hasCheckbox: true, - icon: , - expandedIcon: , - children: [ - { - name: 'Application 1', - id: 'App1', - children: [ - { name: 'Settings', id: 'App1Settings' }, - { name: 'Current', id: 'App1Current' } - ] - }, - { - name: 'Application 2', - id: 'App2', - hasBadge: true, - children: [ - { name: 'Settings', id: 'App2Settings', hasCheckbox: true }, - { - name: 'Loader', - id: 'App2Loader', - children: [ - { name: 'Loading App 1', id: 'LoadApp1' }, - { name: 'Loading App 2', id: 'LoadApp2' }, - { name: 'Loading App 3', id: 'LoadApp3' } - ] - } - ] - } - ], - defaultExpanded: true - }, - { - name: 'Cost Management', - id: 'Cost', - hasBadge: true, - action: ( - - ), - children: [ - { - name: 'Application 3', - id: 'App3', - children: [ - { name: 'Settings', id: 'App3Settings' }, - { name: 'Current', id: 'App3Current' } - ] - } - ] - }, - { - name: 'Sources', - id: 'Sources', - children: [{ name: 'Application 4', id: 'App4', children: [{ name: 'Settings', id: 'App4Settings' }] }] - }, - { - name: 'Really really really long folder name that overflows the container it is in', - id: 'Long', - children: [{ name: 'Application 5', id: 'App5' }] - } -]; - -const active = [ - { - name: 'Application 1', - id: 'App1', - children: [ - { name: 'Settings', id: 'App1Settings' }, - { name: 'Current', id: 'App1Current' } - ] - } -]; - -describe('tree view', () => { - test('renders basic successfully', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); - }); - test('calls onExpand and onCollapse appropriately', () => { - const onExpand = jest.fn(); - const onCollapse = jest.fn(); - render(); - expect(onExpand).not.toHaveBeenCalled(); - expect(onCollapse).not.toHaveBeenCalled(); - expect(screen.queryByText('Application 3')).toBeNull(); - expect(screen.getByText('Cost Management')).toBeInTheDocument(); - fireEvent( - screen.getByText('Cost Management'), - new MouseEvent('click', { - bubbles: true, - cancelable: true - }) - ); - expect(onExpand).toHaveBeenCalled(); - expect(onCollapse).not.toHaveBeenCalled(); - expect(screen.getByText('Application 3')).toBeInTheDocument(); - fireEvent( - screen.getByText('Cost Management'), - new MouseEvent('click', { - bubbles: true, - cancelable: true - }) - ); - expect(onCollapse).toHaveBeenCalled(); - expect(screen.queryByText('Application 3')).toBeNull(); - }); +jest.mock('../TreeViewList', () => ({ + TreeViewList: ({ children, isNested, toolbar }) => ( +
    +

    {`TreeViewList isNested: ${isNested}`}

    +

    {`TreeViewList toolbar: ${toolbar}`}

    +
    {children}
    +
    + ) +})); +jest.mock('../TreeViewListItem', () => ({ + TreeViewListItem: ({ + action, + activeItems, + badgeProps, + checkProps, + children, + compareItems, + customBadgeContent, + defaultExpanded, + expandedIcon, + hasBadge, + hasCheckbox, + icon, + id, + isCompact, + isExpanded, + isSelectable, + itemData, + name, + onCheck, + onSelect, + onExpand, + onCollapse, + parentItem, + title, + useMemo + }) => ( +
    +

    {`TreeViewListItem action: ${action}`}

    +
    {activeItems && activeItems[0].name}
    +

    {`TreeViewListItem badgeProps: ${badgeProps?.id}`}

    +

    {`TreeViewListItem checkProps: ${checkProps?.checked}`}

    +

    {`TreeViewListItem customBadgeContent: ${customBadgeContent}`}

    +

    {`TreeViewListItem defaultExpanded: ${defaultExpanded}`}

    +

    {`TreeViewListItem expandedIcon: ${expandedIcon}`}

    +

    {`TreeViewListItem hasBadge: ${hasBadge}`}

    +

    {`TreeViewListItem hasCheckbox: ${hasCheckbox}`}

    +

    {`TreeViewListItem icon: ${icon}`}

    +

    {`TreeViewListItem id: ${id}`}

    +

    {`TreeViewListItem isCompact: ${isCompact}`}

    +

    {`TreeViewListItem isExpanded: ${isExpanded}`}

    +

    {`TreeViewListItem isSelectable: ${isSelectable}`}

    +

    {`TreeViewListItem itemData: ${itemData.name}`}

    +

    {`TreeViewListItem name: ${name}`}

    +

    {`TreeViewListItem parentItem: ${parentItem?.name}`}

    +

    {`TreeViewListItem title: ${title}`}

    +

    {`TreeViewListItem useMemo: ${useMemo}`}

    + + + + + +
    {children}
    +
    + ) +})); +jest.mock('../TreeViewRoot', () => ({ + TreeViewRoot: ({ children, hasCheckboxes, hasGuides, hasSelectableNodes, variant, className }) => ( +
    +

    {`TreeViewRoot hasCheckboxes: ${hasCheckboxes}`}

    +

    {`TreeViewRoot hasGuides: ${hasGuides}`}

    +

    {`TreeViewRoot hasSelectableNodes: ${hasSelectableNodes}`}

    +

    {`TreeViewRoot variant: ${variant}`}

    +

    {`TreeViewRoot className: ${className}`}

    +
    {children}
    +
    + ) +})); - test('renders active successfully', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); - }); +const basicData = { + name: 'Basic data name' +}; - test('renders search successfully', () => { - const { asFragment } = render( - - ); - expect(asFragment()).toMatchSnapshot(); - }); +test('Passes hasSelectableNodes to TreeViewRoot', () => { + render(); + + expect(screen.getByText('TreeViewRoot hasSelectableNodes: true')).toBeVisible(); +}); +test('Passes hasCheckboxes to TreeViewRoot', () => { + render(); + + expect(screen.getByText('TreeViewRoot hasCheckboxes: true')).toBeVisible(); +}); +test('Passes hasGuides to TreeViewRoot', () => { + render(); + + expect(screen.getByText('TreeViewRoot hasGuides: true')).toBeVisible(); +}); +test('Passes variant to TreeViewRoot', () => { + render(); + + expect(screen.getByText('TreeViewRoot variant: compact')).toBeVisible(); +}); +test('Passes className to TreeViewRoot', () => { + render(); + + expect(screen.getByText('TreeViewRoot className: test-class')).toBeVisible(); +}); +test('Passes data as children TreeViewRoot', () => { + render(); + + expect(screen.getByTestId('TreeViewRoot-children')).toContainHTML('TreeViewListItem name: Basic data name'); +}); +test('Does not render TreeViewRoot when parentItem is passed', () => { + render(); + + expect(screen.queryByTestId('TreeViewRoot-mock')).not.toBeInTheDocument(); +}); + +test('Passes isNested to TreeViewList', () => { + render(); + + expect(screen.getByText('TreeViewList isNested: true')).toBeVisible(); +}); +test('Passes toolbar to TreeViewList', () => { + render(); + + expect(screen.getByText('TreeViewList toolbar: Toolbar content')).toBeVisible(); +}); +test('Passes data as children TreeViewList', () => { + render(); - test('renders toolbar successfully', () => { - const { asFragment } = render( - test} /> - ); - expect(asFragment()).toMatchSnapshot(); + expect(screen.getByTestId('TreeViewList-children')).toContainHTML('TreeViewListItem name: Basic data name'); +}); + +test('Passes data action to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem action: Item action')).toBeVisible(); +}); +test('Passes activeItems to TreeViewListItem', () => { + render(); + + expect(screen.getByTestId('TreeViewListItem-activeItems')).toHaveTextContent('Active item name'); +}); +test('Passes data badgeProps to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem badgeProps: test-id')).toBeVisible(); +}); +test('Passes data checkProps to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem checkProps: true')).toBeVisible(); +}); +test('Passes data customBadgeContent to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem customBadgeContent: Custom badge')).toBeVisible(); +}); +test('Passes data defaultExpanded to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem defaultExpanded: true')).toBeVisible(); +}); +test('Passes defaultAllExpanded to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem defaultExpanded: true')).toBeVisible(); +}); +test('Passes data expandedIcon to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem expandedIcon: Expanded icon')).toBeVisible(); +}); +test('Passes expandedIcon to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem expandedIcon: Expanded icon')).toBeVisible(); +}); +test('Passes data hasBadge to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem hasBadge: true')).toBeVisible(); +}); +test('Passes hasBadges to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem hasBadge: true')).toBeVisible(); +}); +test('Passes data hasCheckbox to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem hasCheckbox: true')).toBeVisible(); +}); +test('Passes hasCheckboxes to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem hasCheckbox: true')).toBeVisible(); +}); +test('Passes data icon to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem icon: Icon content')).toBeVisible(); +}); +test('Passes icon to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem icon: Icon content')).toBeVisible(); +}); +test('Passes data id to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem id: test-id')).toBeVisible(); +}); + +['default', 'compact', 'compactNoBackground'].forEach((variant) => { + test(`Passes isCompact to TreeViewListItem when variant=${variant}`, () => { + render(); + + expect(screen.getByText(`TreeViewListItem isCompact: ${variant === 'default' ? 'false' : 'true'}`)).toBeVisible(); }); +}); + +test('Passes allExpanded to TreeViewListItem isExpanded prop', () => { + render(); + + expect(screen.getByText('TreeViewListItem isExpanded: true')).toBeVisible(); +}); +test('Passes hasSelectableNodes to TreeViewListItem isSelectable prop', () => { + render(); + + expect(screen.getByText('TreeViewListItem isSelectable: true')).toBeVisible(); +}); +test('Passes data to TreeViewListItem itemData prop', () => { + render(); + + expect(screen.getByText('TreeViewListItem itemData: itemData name')).toBeVisible(); +}); +test('Passes data.name to TreeViewListItem name prop', () => { + render(); - test('renders checkboxes successfully', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); + expect(screen.getByText('TreeViewListItem name: Basic data name')).toBeVisible(); +}); +test('Passes parentItem to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem parentItem: Parent name')).toBeVisible(); +}); +test('Passes data.title to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem title: Basic data title')).toBeVisible(); +}); +test('Passes useMemo to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem useMemo: true')).toBeVisible(); +}); +test('Passes data.children to TreeViewListItem', () => { + render(); + + expect(screen.getByText('TreeViewListItem name: Child 1')).toBeVisible(); +}); + +describe('Passes callback props to TreeViewListItem', () => { + const user = userEvent.setup(); + const callbackMock = jest.fn(); + + test('Passes compareItems', async () => { + render(); + + await user.click(screen.getByRole('button', { name: 'compareItems clicker' })); + + expect(callbackMock).toHaveBeenCalledTimes(1); }); + test('Passes onCheck', async () => { + render(); + + await user.click(screen.getByRole('button', { name: 'onCheck clicker' })); - test('renders icons successfully', () => { - const { asFragment } = render( - } - expandedIcon={} - /> - ); - expect(asFragment()).toMatchSnapshot(); + expect(callbackMock).toHaveBeenCalledTimes(1); }); + test('Passes onSelect', async () => { + render(); - test('renders badges successfully', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); + await user.click(screen.getByRole('button', { name: 'onSelect clicker' })); + + expect(callbackMock).toHaveBeenCalledTimes(1); }); + test('Passes onExpand', async () => { + render(); + + await user.click(screen.getByRole('button', { name: 'onExpand clicker' })); - test('renders individual flag options successfully', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); + expect(callbackMock).toHaveBeenCalledTimes(1); }); -}); + test('Passes onCollapse', async () => { + render(); -test('renders guides successfully', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); -}); + await user.click(screen.getByRole('button', { name: 'onCollapse clicker' })); -test('renders compact successfully', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); + expect(callbackMock).toHaveBeenCalledTimes(1); + }); }); -test('renders compact no background successfully', () => { - const { asFragment } = render(); +test('Matches snapshot', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/react-core/src/components/TreeView/__tests__/TreeViewList.test.tsx b/packages/react-core/src/components/TreeView/__tests__/TreeViewList.test.tsx new file mode 100644 index 00000000000..496d8cc6b08 --- /dev/null +++ b/packages/react-core/src/components/TreeView/__tests__/TreeViewList.test.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { TreeViewList } from '../TreeViewList'; +import styles from '@patternfly/react-styles/css/components/TreeView/tree-view'; + +test(`Renders with class ${styles.treeView}__list by default`, () => { + render(Content); + + expect(screen.getByRole('tree')).toHaveClass(`${styles.treeView}__list`, { exact: true }); +}); + +test(`Renders with role="tree" by default`, () => { + render(Content); + + expect(screen.getByRole('tree')).toHaveTextContent('Content'); +}); + +test(`Renders with role="group" when isNested is true`, () => { + render(Content); + + expect(screen.getByRole('group')).toHaveTextContent('Content'); +}); + +test(`Spreads additional props`, () => { + render(Content); + + expect(screen.getByRole('tree')).toHaveAttribute('id', 'test-id'); +}); + +test(`Renders toolbar content when toolbar prop is passed`, () => { + render(Content); + + expect(screen.getByText('Toolbar content')).toBeInTheDocument(); +}); + +test(`Does not render toolbar content when toolbar prop is not passed`, () => { + render(Content); + + expect(screen.queryByRole('separator')).not.toBeInTheDocument(); +}); + +test('Matches snapshot by default', () => { + const { asFragment } = render(Content); + + expect(asFragment()).toMatchSnapshot(); +}); + +test('Matches snapshot when toolbar is passed', () => { + const { asFragment } = render(Toolbar content}>Content); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/TreeView/__tests__/TreeViewListItem.test.tsx b/packages/react-core/src/components/TreeView/__tests__/TreeViewListItem.test.tsx new file mode 100644 index 00000000000..2451c90c041 --- /dev/null +++ b/packages/react-core/src/components/TreeView/__tests__/TreeViewListItem.test.tsx @@ -0,0 +1,708 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { TreeViewListItem } from '../TreeViewListItem'; +import { TreeView } from '../TreeView'; +import userEvent from '@testing-library/user-event'; +import styles from '@patternfly/react-styles/css/components/TreeView/tree-view'; + +const requiredProps = { + name: 'Item name', + title: 'Item title' +}; + +test(`Renders without children`, () => { + render( +
    + +
    + ); + + expect(screen.getByTestId('container').firstChild).toBeVisible(); +}); + +test(`Does not render children by default`, () => { + render(Content); + + expect(screen.queryByText('Content')).not.toBeInTheDocument(); +}); + +test(`Renders children if defaultExpanded is true`, () => { + render( + + Content + + ); + + expect(screen.getByText('Content')).toBeVisible(); +}); + +test(`Renders children if isExpanded is true`, () => { + render( + + Content + + ); + + expect(screen.getByText('Content')).toBeVisible(); +}); + +test(`Renders children when toggle is clicked`, async () => { + const user = userEvent.setup(); + render(Content); + + await user.click(screen.getByRole('button')); + + expect(screen.getByText('Content')).toBeVisible(); +}); + +test(`Renders with class ${styles.treeViewListItem} and aria-expanded=false by default`, () => { + render(); + + expect(screen.getByRole('treeitem')).toHaveClass(styles.treeViewListItem, { exact: true }); + expect(screen.getByRole('treeitem')).toHaveAttribute('aria-expanded', 'false'); +}); + +test(`Renders with class ${styles.modifiers.expanded} and aria-expanded=true when defaultExpanded is true`, () => { + render(); + + expect(screen.getByRole('treeitem')).toHaveClass(styles.modifiers.expanded); + expect(screen.getByRole('treeitem')).toHaveAttribute('aria-expanded', 'true'); +}); + +test(`Renders with class ${styles.modifiers.expanded} and aria-expanded=true when isExpanded is true`, () => { + render(); + + expect(screen.getByRole('treeitem')).toHaveClass(styles.modifiers.expanded); + expect(screen.getByRole('treeitem')).toHaveAttribute('aria-expanded', 'true'); +}); + +test('Renders with id when passed', () => { + render(); + + expect(screen.getByRole('treeitem')).toHaveAttribute('id', 'test-id'); +}); + +test('Does not render expandable toggle if children are not passed', () => { + render(); + + expect(screen.queryByText(requiredProps.name)?.previousSibling).not.toBeInTheDocument(); +}); + +test(`Renders expandable toggle with class ${styles.treeViewNodeToggle} if children are passed`, () => { + render(Content); + + expect(screen.getByText(requiredProps.name).previousElementSibling).toHaveClass(styles.treeViewNodeToggle); +}); + +test(`Renders expandable toggle without aria-labelledby as span by default`, () => { + render(Content); + const toggle = screen.getByText(requiredProps.name).previousElementSibling; + + expect(toggle?.tagName).toBe('SPAN'); + expect(toggle).not.toHaveAccessibleName(); +}); + +test(`Renders expandable toggle as button with aria-labelledby when hasCheckbox is passed`, () => { + render( + + Content + + ); + const toggle = screen.getByRole('button'); + + expect(toggle).toHaveClass(styles.treeViewNodeToggle); + expect(toggle).toHaveAccessibleName(requiredProps.name); +}); + +test(`Renders expandable toggle as button with aria-labelledby when isSelectable is passed`, () => { + render( + + Content + + ); + const toggle = screen.getByText(requiredProps.name).previousElementSibling; + + expect(toggle?.tagName).toBe('BUTTON'); + expect(toggle).toHaveAccessibleName(requiredProps.name); +}); + +test(`Renders name prop with class ${styles.treeViewNodeText}`, () => { + render(); + + expect(screen.getByText(requiredProps.name)).toHaveClass(styles.treeViewNodeText); +}); + +test(`Renders name prop in span by default`, () => { + render(); + + expect(screen.getByText(requiredProps.name).tagName).toBe('SPAN'); +}); + +test(`Renders name prop in button when isSelectable is passed`, () => { + render(); + + expect(screen.getByText(requiredProps.name).tagName).toBe('BUTTON'); +}); + +test('Does not render title prop by default', () => { + render(); + + expect(screen.queryByText(requiredProps.title)).not.toBeInTheDocument(); +}); + +test(`Renders title prop with class ${styles.treeViewNodeTitle} when isCompact is passed`, () => { + render(); + + expect(screen.getByText(requiredProps.title)).toHaveClass(styles.treeViewNodeTitle); +}); + +test(`Does not render name in ${styles.treeViewNodeContent} wrapper by default`, () => { + render(); + + expect(screen.getByText(requiredProps.name).parentElement).not.toHaveClass(styles.treeViewNodeContent); +}); + +test(`Renders name and title in ${styles.treeViewNodeContent} wrapper when isCompact is passed`, () => { + render(); + + expect(screen.getByText(requiredProps.name).parentElement).toHaveClass(styles.treeViewNodeContent); + expect(screen.getByText(requiredProps.title).parentElement).toHaveClass(styles.treeViewNodeContent); +}); + +test('Does not render checkbox by default', () => { + render(); + + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); +}); + +test('Renders checkbox when hasCheckbox is passed', () => { + render(); + + expect(screen.getByRole('checkbox')).toBeInTheDocument(); +}); + +test('Passes checkProps to checkbox', () => { + render(); + + // Want to check that checkProps.checked as well as additional spread props are passed + expect(screen.getByRole('checkbox')).toBeChecked(); + expect(screen.getByRole('checkbox')).toHaveClass('test-class'); +}); + +test('Does not render icon by default', () => { + render(Content); + + // + expect(screen.getByText(requiredProps.name).previousElementSibling).not.toHaveClass(styles.treeViewNodeIcon); +}); + +test(`Renders icon with class ${styles.treeViewNodeIcon} when prop is passed`, () => { + render( + + Content + + ); + + expect(screen.getByText('Icon content')).toHaveClass(styles.treeViewNodeIcon); + // Check that previous test isn't a false positive + expect(screen.getByText(requiredProps.name).previousElementSibling).toHaveClass(styles.treeViewNodeIcon); +}); + +test('Does not render expandedIcon if list item is not expanded', () => { + render( + + Content + + ); + + expect(screen.queryByText('Expanded icon content')).not.toBeInTheDocument(); +}); + +test('Renders expandedIcon instead of icon if list item is expanded', () => { + render( + + Content + + ); + + expect(screen.getByText('Expanded icon content')).toBeInTheDocument(); + expect(screen.queryByText('Icon content')).not.toBeInTheDocument(); +}); + +test('Does not render badge by default', () => { + render(); + + expect(screen.queryByText(requiredProps.name)?.nextElementSibling).not.toBeInTheDocument(); +}); + +test('Does not render badge if hasBadge is passed but children and customBadgeContent are not passed', () => { + render(); + + expect(screen.queryByText(requiredProps.name)?.nextElementSibling).not.toBeInTheDocument(); +}); + +test('Renders badge with count by default when hasBadge and children are passed', () => { + render( + + + + ); + + expect(screen.getByText(requiredProps.name).nextElementSibling).toHaveTextContent('2'); +}); + +test('Renders badge with customBadgeContent when hasBadge and children are passed', () => { + render( + + + + ); + + expect(screen.getByText(requiredProps.name).nextElementSibling).toHaveTextContent('Custom badge'); +}); + +test('Renders badge with customBadgeContent when hasBadge is passed and children are not passed', () => { + render(); + + expect(screen.getByText(requiredProps.name).nextElementSibling).toHaveTextContent('Custom badge'); +}); + +test(`Renders badge with class ${styles.treeViewNodeCount} when hasBadge and children passed`, () => { + render( + + + + ); + + expect(screen.getByText('2').parentElement).toHaveClass(styles.treeViewNodeCount); +}); + +test(`Renders badge with class ${styles.treeViewNodeCount} when hasBadge is passed and children are not passed`, () => { + render(); + + expect(screen.getByText('Custom badge').parentElement).toHaveClass(styles.treeViewNodeCount); +}); + +test('Passes badgeProps when hasBadge and children are passed', () => { + render( + + + + ); + + expect(screen.getByText('2')).toHaveClass('test-class'); +}); + +test('Passes badgeProps when hasBadge and customBadgeContent are passed', () => { + render( + + ); + + expect(screen.getByText('Badge content')).toHaveClass('test-class'); +}); + +test(`Renders ${styles.treeViewNode} element as button by default`, () => { + render(); + + expect(screen.getByRole('button')).toHaveClass(styles.treeViewNode); +}); + +test(`Renders ${styles.treeViewNode} element as label when hasCheckbox is passed`, () => { + render(); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode?.tagName).toBe('LABEL'); +}); + +test(`Renders ${styles.treeViewNode} element as div when isSelectable is passed`, () => { + render(); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode?.tagName).toBe('DIV'); +}); + +test(`Does not render ${styles.treeViewNode} element with for or id attributes by default`, () => { + render(); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).not.toHaveAttribute('for'); + expect(treeViewNode).not.toHaveAttribute('id'); +}); + +test(`Renders ${styles.treeViewNode} element with for and id attributes when hasCheckbox is passed`, () => { + render(); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).toHaveAttribute('for'); + expect(treeViewNode).toHaveAttribute('id'); +}); + +test(`Renders ${styles.treeViewNode} element with id attribute when isSelectable and children are passed`, () => { + render( + + Content + + ); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).toHaveAttribute('id'); +}); + +test(`Does not render ${styles.treeViewNode} element with additional classes by default`, () => { + render(); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).toHaveClass(styles.treeViewNode, { exact: true }); +}); + +test(`Renders ${styles.treeViewNode} element with ${styles.modifiers.selectable} class when hasCheckbox and children are passed`, () => { + render( + + Content + + ); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).toHaveClass(styles.modifiers.selectable); +}); + +test(`Renders ${styles.treeViewNode} element with ${styles.modifiers.selectable} class when isSelectable and children are passed`, () => { + render( + + Content + + ); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).toHaveClass(styles.modifiers.selectable); +}); + +test(`Does not render ${styles.treeViewNode} element with ${styles.modifiers.selectable} if children are not passed`, () => { + render(); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).not.toHaveClass(styles.modifiers.selectable); +}); + +test(`Renders ${styles.treeViewNode} element with ${styles.modifiers.current} class when isSelectable and activeItems are passed`, () => { + render( + true} isSelectable activeItems={[{ name: 'Active item' }]} {...requiredProps}> + Content + + ); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).toHaveClass(styles.modifiers.current); +}); + +test(`Renders ${styles.treeViewNode} element with ${styles.modifiers.current} class when children are not passed`, () => { + render( true} activeItems={[{ name: 'Active item' }]} {...requiredProps} />); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).toHaveClass(styles.modifiers.current); +}); + +test(`Does not render ${styles.treeViewNode} element with ${styles.modifiers.current} class when children are passed`, () => { + render( + true} activeItems={[{ name: 'Active item' }]} {...requiredProps}> + Content + + ); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + + expect(treeViewNode).not.toHaveClass(styles.modifiers.current); +}); + +describe('compareItems callback', () => { + const compareItemsMock = jest.fn(); + const activeItems = [{ name: 'Active item' }]; + const itemData = { name: 'Item data' }; + + test('Not called by default', () => { + render(Content); + + expect(compareItemsMock).not.toHaveBeenCalled(); + }); + + test('Called when isSelectable and activeItems are passed', () => { + render( + + Content + + ); + + expect(compareItemsMock).toHaveBeenCalledTimes(1); + expect(compareItemsMock).toHaveBeenCalledWith( + expect.objectContaining(activeItems[0]), + expect.objectContaining(itemData) + ); + }); + + test('Not called when isSelectable is passed but activeItems is not passed', () => { + render( + + Content + + ); + + expect(compareItemsMock).not.toHaveBeenCalled(); + }); + + test('Called when children and activeItems are passed', () => { + render( + + ); + + expect(compareItemsMock).toHaveBeenCalledTimes(1); + expect(compareItemsMock).toHaveBeenCalledWith( + expect.objectContaining(activeItems[0]), + expect.objectContaining(itemData) + ); + }); +}); + +test('Does not call onCheck by default', async () => { + const user = userEvent.setup(); + const onCheckMock = jest.fn(); + + render(); + + await user.click(screen.getByRole('checkbox')); + + expect(onCheckMock).not.toHaveBeenCalled(); +}); + +test('Calls onCheck callback when checkbox is clicked', async () => { + const user = userEvent.setup(); + const onCheckMock = jest.fn(); + + render(); + + await user.click(screen.getByRole('checkbox')); + + expect(onCheckMock).toHaveBeenCalledTimes(1); + expect(onCheckMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test('Does not call onSelect by default', async () => { + const user = userEvent.setup(); + const onSelectMock = jest.fn(); + + render(); + + await user.click(screen.getByRole('button')); + + expect(onSelectMock).not.toHaveBeenCalled(); +}); + +test(`Calls onSelect when ${styles.treeViewNode} is clicked`, async () => { + const user = userEvent.setup(); + const onSelectMock = jest.fn(); + + render(); + + await user.click(screen.getByRole('button')); + + expect(onSelectMock).toHaveBeenCalledTimes(1); + expect(onSelectMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test('Does not call onSelect when hasCheckbox is passed', async () => { + const user = userEvent.setup(); + const onSelectMock = jest.fn(); + + render(); + + const treeViewNode = screen.getByRole('treeitem').querySelector(`.${styles.treeViewNode}`); + await user.click(treeViewNode as Element); + + expect(onSelectMock).not.toHaveBeenCalled(); +}); + +test('Does not call onExpand by default', async () => { + const user = userEvent.setup(); + const onExpandMock = jest.fn(); + + render(Content); + + await user.click(screen.getByRole('button')); + + expect(onExpandMock).not.toHaveBeenCalled(); +}); + +test(`Calls onExpand when ${styles.treeViewNode} is collapsed and clicked`, async () => { + const user = userEvent.setup(); + const onExpandMock = jest.fn(); + + render( + + Content + + ); + + await user.click(screen.getByRole('button')); + + expect(onExpandMock).toHaveBeenCalledTimes(1); + expect(onExpandMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test(`Calls onExpand when ${styles.treeViewNodeToggle} is clicked and isSelectable is passed`, async () => { + const user = userEvent.setup(); + const onExpandMock = jest.fn(); + + render( + + Content + + ); + + const toggle = screen.getByText(requiredProps.name).previousElementSibling; + await user.click(toggle as Element); + + expect(onExpandMock).toHaveBeenCalledTimes(1); + expect(onExpandMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test(`Calls onExpand when ${styles.treeViewNodeToggle} is clicked and hasCheckbox is passed`, async () => { + const user = userEvent.setup(); + const onExpandMock = jest.fn(); + + render( + + Content + + ); + + const toggle = screen.getByText(requiredProps.name).previousElementSibling?.previousElementSibling; + await user.click(toggle as Element); + + expect(onExpandMock).toHaveBeenCalledTimes(1); + expect(onExpandMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test('Does not call onCollapse by default', async () => { + const user = userEvent.setup(); + const onCollapseMock = jest.fn(); + + render( + + Content + + ); + + await user.click(screen.getByRole('button')); + + expect(onCollapseMock).not.toHaveBeenCalled(); +}); + +test(`Calls onCollapse when ${styles.treeViewNode} is expanded and clicked`, async () => { + const user = userEvent.setup(); + const onCollapseMock = jest.fn(); + + render( + + Content + + ); + + await user.click(screen.getByRole('button')); + + expect(onCollapseMock).toHaveBeenCalledTimes(1); + expect(onCollapseMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test(`Calls onCollapse when ${styles.treeViewNodeToggle} is clicked and isSelectable is passed`, async () => { + const user = userEvent.setup(); + const onCollapseMock = jest.fn(); + + render( + + Content + + ); + + const toggle = screen.getByText(requiredProps.name).previousElementSibling; + await user.click(toggle as Element); + + expect(onCollapseMock).toHaveBeenCalledTimes(1); + expect(onCollapseMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test(`Calls onCollapse when ${styles.treeViewNodeToggle} is clicked and hasCheckbox is passed`, async () => { + const user = userEvent.setup(); + const onCollapseMock = jest.fn(); + + render( + + Content + + ); + + const toggle = screen.getByText(requiredProps.name).previousElementSibling?.previousElementSibling; + await user.click(toggle as Element); + + expect(onCollapseMock).toHaveBeenCalledTimes(1); + expect(onCollapseMock).toHaveBeenCalledWith(expect.any(Object), undefined, undefined); +}); + +test(`Does not render ${styles.treeViewAction} element by default`, () => { + render(); + + expect(screen.queryByRole('treeitem')?.querySelector(`.${styles.treeViewAction}`)).not.toBeInTheDocument(); +}); + +test(`Renders action with ${styles.treeViewAction} class when action is passed`, () => { + render(); + + expect(screen.getByText('Action content')).toHaveClass(styles.treeViewAction); +}); + +test('Matches snapshot without children', () => { + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); +}); + +test('Matches snapshot with children', () => { + const { asFragment } = render(Content); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/TreeView/__tests__/TreeViewRoot.test.tsx b/packages/react-core/src/components/TreeView/__tests__/TreeViewRoot.test.tsx new file mode 100644 index 00000000000..097bf3d77fa --- /dev/null +++ b/packages/react-core/src/components/TreeView/__tests__/TreeViewRoot.test.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { TreeViewRoot } from '../TreeViewRoot'; +import styles from '@patternfly/react-styles/css/components/TreeView/tree-view'; + +test('Renders children', () => { + render( + +
    Content
    +
    + ); + + expect(screen.getByText('Content')).toBeVisible(); +}); + +test(`Renders with class ${styles.treeView} by default`, () => { + render( + +
    Content
    +
    + ); + + expect(screen.getByText('Content').parentElement).toHaveClass(styles.treeView, { exact: true }); +}); + +test(`Renders with custom class when className is passed`, () => { + render( + +
    Content
    +
    + ); + + expect(screen.getByText('Content').parentElement).toHaveClass('test-class'); +}); + +test(`Renders with class ${styles.modifiers.guides} when hasGuides is passed`, () => { + render( + +
    Content
    +
    + ); + + expect(screen.getByText('Content').parentElement).toHaveClass(styles.modifiers.guides); +}); + +test(`Does not render with additional classes when variant="default"`, () => { + render( + +
    Content
    +
    + ); + + expect(screen.getByText('Content').parentElement).toHaveClass(styles.treeView, { exact: true }); +}); + +test(`Renders with class ${styles.modifiers.compact} when variant="compact"`, () => { + render( + +
    Content
    +
    + ); + + expect(screen.getByText('Content').parentElement).toHaveClass(styles.modifiers.compact); +}); + +test(`Renders with classes ${styles.modifiers.compact} and ${styles.modifiers.noBackground} when variant="compactNoBackground"`, () => { + render( + +
    Content
    +
    + ); + + expect(screen.getByText('Content').parentElement).toHaveClass( + styles.modifiers.compact, + styles.modifiers.noBackground + ); +}); + +test('Matches snapshot', () => { + const { asFragment } = render( + +
    Content
    +
    + ); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/TreeView/__tests__/TreeViewSearch.test.tsx b/packages/react-core/src/components/TreeView/__tests__/TreeViewSearch.test.tsx new file mode 100644 index 00000000000..049a4d49a4c --- /dev/null +++ b/packages/react-core/src/components/TreeView/__tests__/TreeViewSearch.test.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { TreeViewSearch } from '../TreeViewSearch'; +import userEvent from '@testing-library/user-event'; +import styles from '@patternfly/react-styles/css/components/TreeView/tree-view'; + +test(`Renders with ${styles.treeViewSearch} by default`, () => { + render(); + + expect(screen.getByRole('searchbox').parentElement?.parentElement).toHaveClass(styles.treeViewSearch, { + exact: true + }); +}); + +test(`Renders with custom class when className is passed`, () => { + render(); + + expect(screen.getByRole('searchbox').parentElement?.parentElement).toHaveClass('test-class'); +}); + +test(`Renders with id when passed`, () => { + render(); + + expect(screen.getByRole('searchbox')).toHaveAttribute('id', 'test-id'); +}); + +test(`Does not render with id when it is not passed`, () => { + render(); + + expect(screen.getByRole('searchbox')).not.toHaveAttribute('id'); +}); + +test(`Renders with name when passed`, () => { + render(); + + expect(screen.getByRole('searchbox')).toHaveAttribute('name', 'testName'); +}); + +test(`Does not render with name when it is not passed`, () => { + render(); + + expect(screen.getByRole('searchbox')).not.toHaveAttribute('name'); +}); + +test(`Renders with aria-label when passed`, () => { + render(); + + expect(screen.getByRole('searchbox')).toHaveAccessibleName('test label'); +}); + +test(`Does not render with aria-label when it is not passed`, () => { + render(); + + expect(screen.getByRole('searchbox')).not.toHaveAccessibleName(); +}); + +test(`Spreads additional props`, () => { + render(); + + expect(screen.getByRole('searchbox')).toHaveAttribute('data-testprop', 'test-value'); +}); + +test(`Calls onSearch when input is typed into`, async () => { + const user = userEvent.setup(); + const onSearchMock = jest.fn(); + render(); + + await user.type(screen.getByRole('searchbox'), 'a'); + + expect(onSearchMock).toHaveBeenCalledTimes(1); +}); + +test(`Does not call onSearch when input is not typed into`, async () => { + const user = userEvent.setup(); + const onSearchMock = jest.fn(); + + render( + <> + + + + ); + + await user.type(screen.getByRole('textbox'), 'a'); + + expect(onSearchMock).not.toHaveBeenCalled(); +}); + +test('Matches snapshot', () => { + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeView.test.tsx.snap b/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeView.test.tsx.snap index b6de1961c85..960322b8685 100644 --- a/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeView.test.tsx.snap +++ b/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeView.test.tsx.snap @@ -1,3426 +1,122 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders compact no background successfully 1`] = ` +exports[`Matches snapshot 1`] = `
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`renders compact successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`renders guides successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`tree view renders active successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`tree view renders badges successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`tree view renders basic successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`tree view renders checkboxes successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`tree view renders icons successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`tree view renders individual flag options successfully 1`] = ` - -
    -
      -
    • -
      - -
      -
        -
      • -
        - -
        -
      • -
      • -
        - -
        -
      • -
      -
    • -
    • -
      - -
      - -
      -
      -
    • -
    • -
      - -
      -
    • -
    • -
      - -
      -
    • -
    -
    -
    -`; - -exports[`tree view renders search successfully 1`] = ` - -
  • + +`; + +exports[`Matches snapshot without children 1`] = ` + + + +`; diff --git a/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeViewRoot.test.tsx.snap b/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeViewRoot.test.tsx.snap new file mode 100644 index 00000000000..a1ddfaa4834 --- /dev/null +++ b/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeViewRoot.test.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches snapshot 1`] = ` + +
    +
    + Content +
    +
    +
    +`; diff --git a/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeViewSearch.test.tsx.snap b/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeViewSearch.test.tsx.snap new file mode 100644 index 00000000000..7ba29f5f608 --- /dev/null +++ b/packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeViewSearch.test.tsx.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches snapshot 1`] = ` + + + +`;