diff --git a/packages/datagateway-common/package.json b/packages/datagateway-common/package.json index b49984454..6cc72c49e 100644 --- a/packages/datagateway-common/package.json +++ b/packages/datagateway-common/package.json @@ -49,7 +49,6 @@ "@testing-library/react": "12.1.3", "@testing-library/react-hooks": "8.0.1", "@testing-library/user-event": "14.4.1", - "@types/enzyme": "3.10.10", "@types/jest": "29.4.0", "@types/node": "18.11.9", "@types/react": "17.0.39", @@ -57,12 +56,9 @@ "@types/react-virtualized": "9.21.10", "@typescript-eslint/eslint-plugin": "5.49.0", "@typescript-eslint/parser": "5.49.0", - "@wojtekmaj/enzyme-adapter-react-17": "0.6.6", "babel-eslint": "10.1.0", - "enzyme": "3.11.0", - "enzyme-to-json": "3.6.1", "eslint": "8.32.0", - "eslint-config-prettier": "8.6.0", + "eslint-config-prettier": "8.5.0", "eslint-config-react-app": "7.0.0", "eslint-plugin-cypress": "2.12.1", "eslint-plugin-prettier": "4.2.1", @@ -106,9 +102,6 @@ ] }, "jest": { - "snapshotSerializers": [ - "enzyme-to-json/serializer" - ], "collectCoverageFrom": [ "src/**/*.{tsx,ts,js,jsx}", "!src/index.tsx", diff --git a/packages/datagateway-common/src/api/index.tsx b/packages/datagateway-common/src/api/index.tsx index 3106db94a..31f39e82f 100644 --- a/packages/datagateway-common/src/api/index.tsx +++ b/packages/datagateway-common/src/api/index.tsx @@ -369,6 +369,8 @@ export const useUpdateQueryParam = ( const functionToUse = updateMethod === 'push' ? push : replace; return React.useCallback( (param: FiltersType | SortType | number | null) => { + // need to use window.location.search and not useLocation to ensure we have the most + // up to date version of search when useUpdateQueryParam is called const query = parseSearchToQuery(window.location.search); if (type === 'filters') { diff --git a/packages/datagateway-common/src/arrowtooltip.component.test.tsx b/packages/datagateway-common/src/arrowtooltip.component.test.tsx index bad075897..fa56a82c4 100644 --- a/packages/datagateway-common/src/arrowtooltip.component.test.tsx +++ b/packages/datagateway-common/src/arrowtooltip.component.test.tsx @@ -1,25 +1,49 @@ import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; import { ArrowTooltip } from '.'; -import { Tooltip } from '@mui/material'; import { getTooltipText } from './arrowtooltip.component'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; -describe('ArrowTooltip component', () => { - const createWrapper = ( - disableHoverListener?: boolean, - open?: boolean - ): ReactWrapper => { - return mount( - -
- - ); - }; +jest.mock('resize-observer-polyfill', () => ({ + __esModule: true, + default: (() => { + // a simple ResizeObserver mock implemented with constructor function + // because jest.mock doesn't allow access to ResizeObserver at this point + // so extending it is impossible. + // + // this is needed because the ResizeObserver polyfill WON'T WORK in jest env. + + function MockResizeObserver(callback): void { + this.callback = callback; + + this.observe = (target: HTMLElement) => { + this.callback( + [ + { + target: { + scrollWidth: 100, + }, + borderBoxSize: [ + { + inlineSize: Number(target.style.width.replace('px', '')), + }, + ], + }, + ], + this + ); + }; + + this.disconnect = () => { + // disconnected + }; + } + return MockResizeObserver; + })(), +})); + +describe('ArrowTooltip component', () => { describe('getTooltipText', () => { it('returns empty string for anything null-ish', () => { expect(getTooltipText(undefined)).toBe(''); @@ -58,25 +82,85 @@ describe('ArrowTooltip component', () => { }); }); - // Note that disableHoverListener has the opposite value to isTooltipVisible + it('is enabled when the target of the tooltip is overflowing', async () => { + const user = userEvent.setup(); - it('tooltip disabled when tooltipElement null', () => { - // Mock return of createRef to be null - const spyCreateRef = jest - .spyOn(React, 'createRef') - .mockReturnValueOnce(null); + render( + +

+ Some really long text that will for sure overflow +

+
+ ); - const wrapper = createWrapper(); - expect(wrapper.find(Tooltip).props().disableHoverListener).toEqual(true); + await user.hover( + screen.getByText('Some really long text that will for sure overflow') + ); - spyCreateRef.mockRestore(); + expect(await screen.findByText('tooltip content')).toBeInTheDocument(); }); - it('can override disableHoverListener', () => { - let wrapper = createWrapper(true); - expect(wrapper.find(Tooltip).props().disableHoverListener).toEqual(true); + describe('is disabled', () => { + it('when the target of the tooltip is not overflowing', async () => { + const user = userEvent.setup(); + + render( + +

+ Some really long text that will for sure overflow +

+
+ ); - wrapper = createWrapper(false); - expect(wrapper.find(Tooltip).props().disableHoverListener).toEqual(false); + await user.hover( + screen.getByText('Some really long text that will for sure overflow') + ); + + // tooltip doesn't immediately appear in the DOM after it is triggered + // since queryByText immediately queries the dom after the hover event happens, + // we need to make sure that `queryByText` returns null because + // the tooltip **won't ever** appear, not because the tooltip hasn't appeared yet when queryByText queries the dom + // + // waiting for 2 seconds should be enough + + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 2000); + }); + + expect(screen.queryByText('tooltip content')).toBeNull(); + }); + + it('when it is disabled explicitly', async () => { + const user = userEvent.setup(); + + render( + +

+ Some really long text that will for sure overflow +

+
+ ); + + await user.hover( + screen.getByText('Some really long text that will for sure overflow') + ); + + // tooltip doesn't immediately appear in the DOM after it is triggered + // since queryByText immediately queries the dom after the hover event happens, + // we need to make sure that `queryByText` returns null because + // the tooltip **won't ever** appear, not because the tooltip hasn't appeared yet when queryByText queries the dom + // + // waiting for 2 seconds should be enough + + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 2000); + }); + + expect(screen.queryByText('tooltip content')).toBeNull(); + }); }); }); diff --git a/packages/datagateway-common/src/arrowtooltip.component.tsx b/packages/datagateway-common/src/arrowtooltip.component.tsx index 34a2aea26..63b23ecf8 100644 --- a/packages/datagateway-common/src/arrowtooltip.component.tsx +++ b/packages/datagateway-common/src/arrowtooltip.component.tsx @@ -50,26 +50,20 @@ const ArrowTooltip = ( const tooltipResizeObserver = React.useRef( new ResizeObserver((entries) => { - const tooltipElement = entries[0].target; + const tooltipTargetElement = entries[0].target; // Check that the element has been rendered and set the viewable // as false before checking to see the element has exceeded maximum width. - if ( - tooltipElement !== null && - entries.length > 0 && - entries[0].borderBoxSize.length > 0 - ) { + if (tooltipTargetElement && entries[0].borderBoxSize.length > 0) { // Width of the tooltip contents including padding and borders // This is rounded as window.innerWidth and tooltip.scrollWidth are always integer - const borderBoxWidth = Math.round( + const currentTargetWidth = Math.round( entries[0].borderBoxSize[0].inlineSize ); + const minWidthToFitContentOfTarget = tooltipTargetElement.scrollWidth; + const isContentOfTargetOverflowing = + minWidthToFitContentOfTarget > currentTargetWidth; - // have tooltip appear only when visible text width is smaller than full text width. - if (tooltipElement && borderBoxWidth < tooltipElement.scrollWidth) { - setTooltipVisible(true); - } else { - setTooltipVisible(false); - } + setTooltipVisible(isContentOfTargetOverflowing); } }) ); @@ -79,16 +73,14 @@ const ArrowTooltip = ( const tooltipRef = React.useCallback((container: HTMLDivElement) => { if (container !== null) { tooltipResizeObserver.current.observe(container); - } - // When element is unmounted we know container is null so time to clean up - else { - if (tooltipResizeObserver.current) - tooltipResizeObserver.current.disconnect(); + } else if (tooltipResizeObserver.current) { + // When element is unmounted we know container is null so time to clean up + tooltipResizeObserver.current.disconnect(); } }, []); let shouldDisableHoverListener = !isTooltipVisible; - //Allow disableHoverListener to be overidden + // Allow disableHoverListener to be overridden if (disableHoverListener !== undefined) shouldDisableHoverListener = disableHoverListener; diff --git a/packages/datagateway-common/src/card/__snapshots__/cardView.component.test.tsx.snap b/packages/datagateway-common/src/card/__snapshots__/cardView.component.test.tsx.snap index 9a2d0bde0..d1b3f8198 100644 --- a/packages/datagateway-common/src/card/__snapshots__/cardView.component.test.tsx.snap +++ b/packages/datagateway-common/src/card/__snapshots__/cardView.component.test.tsx.snap @@ -1,248 +1,416 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Card View renders correctly 1`] = ` - - +
- - - - - - - - + +
+
+ +
- - - - +
- - Sort By - - - - - - - - - - - + Sort By + +
+
+ +
+
+ + + +
- - - - - - - +
+
+
+
+
+
+ Test 1 +
+
+
+
+
+

+ entity_card.no_description +

+
+
+
+
+
+
+
+
+
+
+ +
  • +
    +
    +
    +
    +
    +
    + Test 2 +
    +
    +
    +
    +
    +

    + entity_card.no_description +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
  • +
  • +
    +
    +
    +
    +
    +
    + Test 3 +
    +
    +
    +
    +
    +

    + entity_card.no_description +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
  • + +
    +
    +
    + +
    + + `; diff --git a/packages/datagateway-common/src/card/__snapshots__/entityCard.component.test.tsx.snap b/packages/datagateway-common/src/card/__snapshots__/entityCard.component.test.tsx.snap index ad30178ca..601da0238 100644 --- a/packages/datagateway-common/src/card/__snapshots__/entityCard.component.test.tsx.snap +++ b/packages/datagateway-common/src/card/__snapshots__/entityCard.component.test.tsx.snap @@ -1,100 +1,60 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Card renders correctly 1`] = ` - - +
    -
    - - Title - - - - +
    - - entity_card.no_description - - - +
    +
    +

    + entity_card.no_description +

    +
    +
    +
    +
    +
    - -
    +
    +
    -
    -
    + + `; diff --git a/packages/datagateway-common/src/card/advancedFilter.component.test.tsx b/packages/datagateway-common/src/card/advancedFilter.component.test.tsx index 51e5da8d7..4d6cd86e6 100644 --- a/packages/datagateway-common/src/card/advancedFilter.component.test.tsx +++ b/packages/datagateway-common/src/card/advancedFilter.component.test.tsx @@ -1,23 +1,20 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import SubjectIcon from '@mui/icons-material/Subject'; -import FingerprintIcon from '@mui/icons-material/Fingerprint'; -import PublicIcon from '@mui/icons-material/Public'; -import ConfirmationNumberIcon from '@mui/icons-material/ConfirmationNumber'; -import AssessmentIcon from '@mui/icons-material/Assessment'; -import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; -import ExploreIcon from '@mui/icons-material/Explore'; -import SaveIcon from '@mui/icons-material/Save'; -import DescriptionIcon from '@mui/icons-material/Description'; -import LinkIcon from '@mui/icons-material/Link'; -import PersonIcon from '@mui/icons-material/Person'; +import * as React from 'react'; import AdvancedFilter, { UnmemoisedAdvancedFilter, } from './advancedFilter.component'; +import { render, screen } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; describe('AdvancedFilter', () => { - it('shows title correctly', () => { - const wrapper = shallow( + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + + it('shows title correctly', async () => { + render( { ); // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - expect(wrapper.find('[children="Test"]').exists()).toBe(true); + expect(await screen.findByText('Test')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="advanced-filters-link"]').text() - ).toEqual('advanced_filters.hide'); + await screen.findByRole('button', { name: 'advanced_filters.hide' }) + ).toBeInTheDocument(); }); - it('shows title correctly when no label provided', () => { - const wrapper = shallow( + it('shows title correctly when no label provided', async () => { + render( { ); // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - expect(wrapper.find('[children="TEST"]').exists()).toBe(true); + expect(await screen.findByText('TEST')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="advanced-filters-link"]').text() - ).toEqual('advanced_filters.hide'); + await screen.findByRole('button', { name: 'advanced_filters.hide' }) + ).toBeInTheDocument(); }); - it('shows description correctly', () => { - const wrapper = shallow( + it('shows description correctly', async () => { + render( { ); // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - expect(wrapper.find('[children="Desc"]').exists()).toBe(true); + expect(await screen.findByText('Desc')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="advanced-filters-link"]').text() - ).toEqual('advanced_filters.hide'); + await screen.findByRole('button', { name: 'advanced_filters.hide' }) + ).toBeInTheDocument(); }); - it('shows description correctly when no label provided', () => { - const wrapper = shallow( + it('shows description correctly when no label provided', async () => { + render( { ); // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - expect(wrapper.find('[children="DESC"]').exists()).toBe(true); + expect(await screen.findByText('DESC')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="advanced-filters-link"]').text() - ).toEqual('advanced_filters.hide'); + await screen.findByRole('button', { name: 'advanced_filters.hide' }) + ).toBeInTheDocument(); }); - it('shows information correctly', () => { - const wrapper = shallow( + it('shows information correctly', async () => { + render( { ); // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - expect(wrapper.find('[children="Info"]').exists()).toBe(true); + expect(await screen.findByText('Info')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="advanced-filters-link"]').text() - ).toEqual('advanced_filters.hide'); + await screen.findByRole('button', { name: 'advanced_filters.hide' }) + ).toBeInTheDocument(); }); - it('shows information correctly when label not provided', () => { - const wrapper = shallow( + it('shows information correctly when label not provided', async () => { + render( { ); // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - expect(wrapper.find('[children="INFO"]').exists()).toBe(true); + expect(await screen.findByText('INFO')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="advanced-filters-link"]').text() - ).toEqual('advanced_filters.hide'); + await screen.findByRole('button', { name: 'advanced_filters.hide' }) + ).toBeInTheDocument(); }); - it('SubjectIcon displays correctly', () => { - const wrapper = shallow( + it('SubjectIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(SubjectIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('SubjectIcon')).toBeInTheDocument(); }); - it('FingerprintIcon displays correctly', () => { - const wrapper = shallow( + it('FingerprintIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(FingerprintIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('FingerprintIcon')).toBeInTheDocument(); }); - it('PublicIcon displays correctly', () => { - const wrapper = shallow( + it('PublicIcon displays correctly', async () => { + render( { }} /> ); + + // Click on the link to show the filters. // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(PublicIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('PublicIcon')).toBeInTheDocument(); }); - it('ConfirmationNumberIcon displays correctly', () => { - const wrapper = shallow( + it('ConfirmationNumberIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(ConfirmationNumberIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect( + await screen.findByTestId('ConfirmationNumberIcon') + ).toBeInTheDocument(); }); - it('AssessmentIcon displays correctly', () => { - const wrapper = shallow( + it('AssessmentIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(AssessmentIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('AssessmentIcon')).toBeInTheDocument(); }); - it('CalendarTodayIcon displays correctly', () => { - const wrapper = shallow( + it('CalendarTodayIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(CalendarTodayIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('CalendarTodayIcon')).toBeInTheDocument(); }); - it('ExploreIcon displays correctly', () => { - const wrapper = shallow( + it('ExploreIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(ExploreIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('ExploreIcon')).toBeInTheDocument(); }); - it('SaveIcon displays correctly', () => { - const wrapper = shallow( + it('SaveIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(SaveIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('SaveIcon')).toBeInTheDocument(); }); - it('DescriptionIcon displays correctly', () => { - const wrapper = shallow( + it('DescriptionIcon displays correctly', async () => { + render( { }} /> ); + + // Click on the link to show the filters. // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(DescriptionIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('DescriptionIcon')).toBeInTheDocument(); }); - it('LinkIcon displays correctly', () => { - const wrapper = shallow( + it('LinkIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(LinkIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('LinkIcon')).toBeInTheDocument(); }); - it('LinkIcon displays correctly', () => { - const wrapper = shallow( + it('LinkIcon displays correctly', async () => { + render( { }} /> ); + // Click on the link to show the filters. - wrapper - .find('[data-testid="advanced-filters-link"]') - .first() - .simulate('click'); - wrapper.update(); - expect(wrapper.exists(PersonIcon)).toBeTruthy(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + expect(await screen.findByTestId('PersonIcon')).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-common/src/card/cardView.component.test.tsx b/packages/datagateway-common/src/card/cardView.component.test.tsx index 374d3e555..1a5674810 100644 --- a/packages/datagateway-common/src/card/cardView.component.test.tsx +++ b/packages/datagateway-common/src/card/cardView.component.test.tsx @@ -1,24 +1,15 @@ -import { - Chip, - Accordion, - ListItemText, - Select, - Typography, -} from '@mui/material'; -import { Pagination } from '@mui/material'; -import { mount, shallow, ReactWrapper } from 'enzyme'; -import React from 'react'; +import * as React from 'react'; import axios from 'axios'; -import { default as CardView, CardViewProps } from './cardView.component'; -import { AdvancedFilter, TextColumnFilter } from '..'; -import { Entity, Investigation } from '../app.types'; +import CardView, { type CardViewProps } from './cardView.component'; +import { TextColumnFilter } from '..'; +import type { Entity, Investigation } from '../app.types'; +import { render, screen, waitFor, within } from '@testing-library/react'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; describe('Card View', () => { let props: CardViewProps; - - const createWrapper = (props: CardViewProps): ReactWrapper => { - return mount(); - }; + let user: UserEvent; const onFilter = jest.fn(); const onPageChange = jest.fn(); @@ -26,28 +17,29 @@ describe('Card View', () => { const onResultsChange = jest.fn(); beforeEach(() => { + user = userEvent.setup(); const data: Investigation[] = [ { id: 1, title: 'Test 1', - name: 'Test 1', - visitId: '1', + name: 'Test name 1', + visitId: 'visit id 1', type: { id: 1, name: '1' }, facility: { id: 1, name: '1' }, }, { id: 2, title: 'Test 2', - name: 'Test 2', - visitId: '2', + name: 'Test name 2', + visitId: 'visit id 2', type: { id: 1, name: '1' }, facility: { id: 1, name: '1' }, }, { id: 3, title: 'Test 3', - name: 'Test 3', - visitId: '3', + name: 'Test name 3', + visitId: 'visit id 3', type: { id: 1, name: '1' }, facility: { id: 1, name: '1' }, }, @@ -82,130 +74,11 @@ describe('Card View', () => { }); it('renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('applying custom filter on panel and on card', () => { - let updatedProps = { - ...props, - customFilters: [ - { - label: 'Type ID', - dataKey: 'type.id', - filterItems: [ - { - name: '1', - count: '1', - }, - { - name: '2', - count: '1', - }, - ], - }, - ], - }; - const wrapper = createWrapper(updatedProps); - expect( - wrapper.find('[data-testid="card"]').at(0).find(Chip).text() - ).toEqual('1'); - - // Open custom filters - const typePanel = wrapper.find(Accordion).first(); - typePanel.find('div').at(1).simulate('click'); - expect(typePanel.find(Chip).first().text()).toEqual('1'); - expect(typePanel.find(Chip).last().text()).toEqual('2'); - - // Apply custom filters - typePanel.find(Chip).first().find('div').simulate('click'); - expect(onPageChange).toHaveBeenNthCalledWith(1, 1); - expect(onFilter).toHaveBeenNthCalledWith(1, 'type.id', ['1']); - - // Mock result of actions - updatedProps = { - ...updatedProps, - page: 1, - filters: { 'type.id': ['1'] }, - }; - - // Mock console.error() when updating the filter panels. We use Accordions - // with dynamic default values, which works, but would log an error. - jest.spyOn(console, 'error').mockImplementationOnce(jest.fn()); - wrapper.setProps(updatedProps); - - // Apply second filter - typePanel.find(Chip).last().find('div').simulate('click'); - expect(onPageChange).toHaveBeenNthCalledWith(2, 1); - - expect(onFilter).toHaveBeenNthCalledWith(2, 'type.id', ['1', '2']); - - // Mock result of actions - updatedProps = { - ...updatedProps, - filters: { 'type.id': ['1', '2'] }, - }; - wrapper.setProps(updatedProps); - - // Remove filter - expect(wrapper.find(Chip).at(2).text()).toEqual('Type ID - 1'); - wrapper.find(Chip).at(2).find('svg').simulate('click'); - expect(onPageChange).toHaveBeenNthCalledWith(3, 1); - expect(onFilter).toHaveBeenNthCalledWith(3, 'type.id', ['2']); - - // Mock result of actions - updatedProps = { - ...updatedProps, - filters: { 'type.id': ['2'] }, - }; - wrapper.setProps(updatedProps); - - // Remove second filter - expect(wrapper.find(Chip).at(2).text()).toEqual('Type ID - 2'); - wrapper.find(Chip).at(2).find('svg').simulate('click'); - expect(onPageChange).toHaveBeenNthCalledWith(4, 1); - expect(onFilter).toHaveBeenNthCalledWith(4, 'type.id', null); - }); - - it('custom filter applied when non-custom filter already in state', () => { - const updatedProps = { - ...props, - customFilters: [ - { - label: 'Type ID', - dataKey: 'type.id', - filterItems: [ - { - name: '1', - count: '1', - }, - { - name: '2', - count: '1', - }, - ], - }, - ], - filters: { 'type.id': { value: 'abc', type: 'include' } }, - }; - const wrapper = createWrapper(updatedProps); - expect( - wrapper.find('[data-testid="card"]').at(0).find(Chip).text() - ).toEqual('1'); - - // Open custom filters - const typePanel = wrapper.find(Accordion).first(); - typePanel.find('div').at(1).simulate('click'); - expect(typePanel.find(Chip).first().text()).toEqual('1'); - expect(typePanel.find(Chip).last().text()).toEqual('2'); - - // Apply custom filters - typePanel.find(Chip).first().find('div').simulate('click'); - expect(onPageChange).toHaveBeenNthCalledWith(1, 1); - expect(onFilter).toHaveBeenNthCalledWith(1, 'type.id', ['1']); - }); - - it('advancedFilter displayed when filter component given', () => { + it('advancedFilter displayed when filter component given', async () => { const textFilter = (label: string, dataKey: string): React.ReactElement => ( { filterComponent: textFilter, }, }; - const wrapper = createWrapper(updatedProps); - expect(wrapper.exists(AdvancedFilter)).toBeTruthy(); + + render(); + + await user.click( + screen.getByRole('button', { name: 'advanced_filters.show' }) + ); + + expect( + screen.getByRole('textbox', { name: 'Filter by Title', hidden: true }) + ).toBeInTheDocument(); }); - it('filter message displayed when loadedData and totalDataCount is 0', () => { + it('filter message displayed when loadedData and totalDataCount is 0', async () => { const updatedProps = { ...props, loadedData: true, totalDataCount: 0, }; - const wrapper = createWrapper(updatedProps); - expect(wrapper.find(Typography).last().text()).toEqual( - 'loading.filter_message' - ); + render(); + expect( + await screen.findByText('loading.filter_message') + ).toBeInTheDocument(); }); - it('buttons display correctly', () => { + it('buttons display correctly', async () => { const updatedProps = { ...props, - buttons: [(entity: Entity) => ], + buttons: [(entity: Entity) => ], }; - const wrapper = createWrapper(updatedProps); - expect(wrapper.find('#test-button').first().text()).toEqual('TEST'); + + render(); + + const cards = screen.getAllByTestId('card'); + + expect( + within(within(cards[0]).getByLabelText('card-buttons')).getByRole( + 'button', + { name: 'Test name 1' } + ) + ).toBeInTheDocument(); + expect( + within(within(cards[1]).getByLabelText('card-buttons')).getByRole( + 'button', + { name: 'Test name 2' } + ) + ).toBeInTheDocument(); + expect( + within(within(cards[2]).getByLabelText('card-buttons')).getByRole( + 'button', + { name: 'Test name 3' } + ) + ).toBeInTheDocument(); }); - it('moreInformation displays correctly', () => { - const moreInformation = (entity: Entity): React.ReactElement =>

    TEST

    ; + it('moreInformation displays correctly', async () => { + const moreInformation = (entity: Entity): React.ReactElement => ( +

    More information for {entity.name}

    + ); const updatedProps = { ...props, moreInformation: moreInformation, }; - const wrapper = createWrapper(updatedProps); - expect(wrapper.exists('[aria-label="card-more-information"]')).toBeTruthy(); + + render(); + + const cards = screen.getAllByTestId('card'); + + await user.click( + within(cards[0]).getByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + within( + within(cards[0]).getByLabelText('card-more-info-details') + ).getByText('More information for Test name 1') + ).toBeInTheDocument(); + + await user.click( + within(cards[1]).getByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + within( + within(cards[1]).getByLabelText('card-more-info-details') + ).getByText('More information for Test name 2') + ).toBeInTheDocument(); + + await user.click( + within(cards[2]).getByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + within( + within(cards[2]).getByLabelText('card-more-info-details') + ).getByText('More information for Test name 3') + ).toBeInTheDocument(); }); - it('title.content displays correctly', () => { + it('title.content displays correctly', async () => { const content = (entity: Entity): React.ReactElement => ( -

    TEST

    +

    Custom {entity.title}

    ); const updatedProps = { ...props, title: { dataKey: 'title', content: content }, }; - const wrapper = createWrapper(updatedProps); - expect(wrapper.find('#test-title-content').at(0).text()).toEqual('TEST'); + + render(); + + const cards = screen.getAllByTestId('card'); + + expect( + within(within(cards[0]).getByLabelText('card-title')).getByText( + 'Custom Test 1' + ) + ).toBeInTheDocument(); + expect( + within(within(cards[1]).getByLabelText('card-title')).getByText( + 'Custom Test 2' + ) + ).toBeInTheDocument(); + expect( + within(within(cards[2]).getByLabelText('card-title')).getByText( + 'Custom Test 3' + ) + ).toBeInTheDocument(); }); - it('sort applied correctly', () => { + it('sort applied correctly', async () => { let updatedProps = { ...props, page: 1 }; - const wrapper = createWrapper(props); - const button = wrapper.find(ListItemText).first(); - const clickableButton = button.find('div'); - expect(button.text()).toEqual('title'); + const { rerender } = render(); // Click to sort ascending - clickableButton.simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by TITLE' }) + ); expect(onSort).toHaveBeenNthCalledWith(1, 'title', 'asc', 'push'); + updatedProps = { ...updatedProps, sort: { title: 'asc' }, }; - wrapper.setProps(updatedProps); + rerender(); - // Click to sort descending - clickableButton.simulate('click'); + await user.click( + await screen.findByRole('button', { + name: 'Sort by TITLE, current direction ascending', + }) + ); expect(onSort).toHaveBeenNthCalledWith(2, 'title', 'desc', 'push'); + updatedProps = { ...updatedProps, sort: { title: 'desc' }, }; - wrapper.setProps(updatedProps); + rerender(); - // Click to clear sorting - clickableButton.simulate('click'); + await user.click( + await screen.findByRole('button', { + name: 'Sort by TITLE, current direction descending', + }) + ); expect(onSort).toHaveBeenNthCalledWith(3, 'title', null, 'push'); }); @@ -312,59 +270,59 @@ describe('Card View', () => { }, ], }; - const wrapper = createWrapper(updatedProps); - wrapper.update(); + render(); expect(onSort).toHaveBeenCalledWith('title', 'asc', 'replace'); expect(onSort).toHaveBeenCalledWith('name', 'desc', 'replace'); expect(onSort).toHaveBeenCalledWith('test', 'asc', 'replace'); }); - it('can sort by description with label', () => { + it('can sort by description with label', async () => { const updatedProps = { ...props, page: 1, title: { dataKey: 'title', disableSort: true }, description: { dataKey: 'name', label: 'Name' }, }; - const wrapper = createWrapper(updatedProps); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('Name'); + render(); // Click to sort ascending - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by NAME' }) + ); expect(onSort).toHaveBeenCalledWith('name', 'asc', 'push'); }); - it('can sort by description without label', () => { + it('can sort by description without label', async () => { const updatedProps = { ...props, page: 1, title: { dataKey: 'title', disableSort: true }, description: { dataKey: 'name' }, }; - const wrapper = createWrapper(updatedProps); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('name'); + render(); // Click to sort ascending - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by NAME' }) + ); expect(onSort).toHaveBeenCalledWith('name', 'asc', 'push'); }); - it('page changed when sort applied', () => { + it('page changed when sort applied', async () => { const updatedProps = { ...props, page: 2 }; - const wrapper = createWrapper(updatedProps); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('title'); + render(); // Click to sort ascending - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by TITLE' }) + ); + expect(onSort).toHaveBeenCalledWith('title', 'asc', 'push'); expect(onPageChange).toHaveBeenCalledWith(1); }); - it('information displays and sorts correctly', () => { + it('information displays and sorts correctly', async () => { const updatedProps = { ...props, title: { dataKey: 'title', disableSort: true }, @@ -374,33 +332,55 @@ describe('Card View', () => { { dataKey: 'name', label: 'Name', - content: (entity: Entity) => 'Content', + content: (entity: Entity) => entity.name, }, ], page: 1, }; - const wrapper = createWrapper(updatedProps); - expect( - wrapper.find('[data-testid="card-info-visitId"]').first().text() - ).toEqual('visitId:'); + render(); + + const cards = await screen.findAllByTestId('card'); + + expect(within(cards[0]).getByText('visitId:')).toBeInTheDocument(); + expect(within(cards[0]).getByText('visit id 1')).toBeInTheDocument(); + expect(within(cards[0]).getByText('Name:')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="card-info-data-visitId"]').first().text() - ).toEqual('1'); + within(cards[0]).getByLabelText('card-description') + ).toHaveTextContent('Test name 1'); + // information content is wrapped with ArrowTooltip, which automatically gives its child + // an aria-label that is the same as the content of the tooltip + expect(within(cards[0]).getByLabelText('Test name 1')).toHaveTextContent( + 'Test name 1' + ); + + expect(within(cards[1]).getByText('visitId:')).toBeInTheDocument(); + expect(within(cards[1]).getByText('visit id 2')).toBeInTheDocument(); + expect(within(cards[1]).getByText('Name:')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="card-info-Name"]').first().text() - ).toEqual('Name:'); + within(cards[1]).getByLabelText('card-description') + ).toHaveTextContent('Test name 2'); + expect(within(cards[1]).getByLabelText('Test name 2')).toHaveTextContent( + 'Test name 2' + ); + + expect(within(cards[2]).getByText('visitId:')).toBeInTheDocument(); + expect(within(cards[2]).getByText('visit id 3')).toBeInTheDocument(); + expect(within(cards[2]).getByText('Name:')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="card-info-data-Name"]').first().text() - ).toEqual('Content'); + within(cards[2]).getByLabelText('card-description') + ).toHaveTextContent('Test name 3'); + expect(within(cards[2]).getByLabelText('Test name 3')).toHaveTextContent( + 'Test name 3' + ); // Click to sort ascending - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('visitId'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by VISITID' }) + ); expect(onSort).toHaveBeenCalledWith('visitId', 'asc', 'push'); }); - it('information displays with content that has no tooltip', () => { + it('information displays with content that has no tooltip', async () => { const updatedProps = { ...props, title: { dataKey: 'title', disableSort: true }, @@ -409,22 +389,34 @@ describe('Card View', () => { { dataKey: 'name', label: 'Name', - content: (entity: Entity) => 'Content', + content: (entity: Entity) => entity.name, noTooltip: true, }, ], }; - const wrapper = createWrapper(updatedProps); + render(); + + const cards = await screen.findAllByTestId('card'); + + expect(within(cards[0]).getByText('Name:')).toBeInTheDocument(); + expect( + within(cards[0]).getByLabelText('card-description') + ).toHaveTextContent('Test name 1'); + expect( + within(cards[0]).getByTestId(`card-info-data-Name`) + ).toHaveTextContent('Test name 1'); + + expect(within(cards[1]).getByText('Name:')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="card-info-Name"]').first().text() - ).toEqual('Name:'); + within(cards[1]).getByLabelText('card-description') + ).toHaveTextContent('Test name 2'); expect( - wrapper.find('[data-testid="card-info-data-Name"]').first().text() - ).toEqual('Content'); + within(cards[1]).getByTestId(`card-info-data-Name`) + ).toHaveTextContent('Test name 2'); }); - it('cannot sort when fields are disabled', () => { + it('cannot sort when fields are disabled', async () => { const updatedProps = { ...props, page: 1, @@ -432,21 +424,39 @@ describe('Card View', () => { description: { dataKey: 'name', disableSort: true }, information: [{ dataKey: 'visitId', disableSort: true }], }; - const wrapper = createWrapper(updatedProps); - expect(wrapper.exists(ListItemText)).toBeFalsy(); + render(); + await waitFor(() => { + expect( + screen.queryByRole('button', { name: 'Sort by TITLE' }) + ).toBeNull(); + expect(screen.queryByRole('button', { name: 'Sort by NAME' })).toBeNull(); + expect( + screen.queryByRole('button', { name: 'Sort by VISITID' }) + ).toBeNull(); + }); }); - it('pagination dispatches onPageChange', () => { + it('pagination dispatches onPageChange', async () => { const updatedProps = { ...props, resultsOptions: [1], results: 1, }; - const wrapper = createWrapper(updatedProps); - const pagination = wrapper.find(Pagination).first(); - pagination.find('button').at(1).simulate('click'); + + render(); + + await user.click( + ( + await screen.findAllByRole('button', { name: 'page 1' }) + )[0] + ); expect(onPageChange).toHaveBeenCalledTimes(0); - pagination.find('button').at(2).simulate('click'); + + await user.click( + ( + await screen.findAllByRole('button', { name: 'Go to page 2' }) + )[1] + ); expect(onPageChange).toHaveBeenNthCalledWith(1, 2); }); @@ -457,7 +467,7 @@ describe('Card View', () => { results: 1, page: 4, }; - createWrapper(updatedProps); + render(); expect(onPageChange).toHaveBeenNthCalledWith(1, 1); }); @@ -469,7 +479,7 @@ describe('Card View', () => { results: 40, page: 1, }; - createWrapper(updatedProps); + render(); expect(onResultsChange).toHaveBeenNthCalledWith(1, 10); }); @@ -481,38 +491,44 @@ describe('Card View', () => { results: 30, page: 1, }; - createWrapper(updatedProps); + render(); expect(onResultsChange).toHaveBeenNthCalledWith(1, 10); }); - it('selector sends pushQuery with results', () => { + it('selector sends pushQuery with results', async () => { const updatedProps = { ...props, resultsOptions: [1, 2, 3], results: 1, page: 2, }; - const wrapper = createWrapper(updatedProps); - wrapper - .find(Select) - .props() - .onChange?.({ target: { value: 2 } }); - expect(onResultsChange).toHaveBeenNthCalledWith(2, 2); + + render(); + + await user.selectOptions( + await screen.findByLabelText('app.max_results'), + '2' + ); + + expect(onResultsChange).toHaveBeenNthCalledWith(2, '2'); }); - it('selector sends pushQuery with results and page', () => { + it('selector sends pushQuery with results and page', async () => { const updatedProps = { ...props, resultsOptions: [1, 2, 3], results: 1, page: 2, }; - const wrapper = createWrapper(updatedProps); - wrapper - .find(Select) - .props() - .onChange?.({ target: { value: 3 } }); - expect(onResultsChange).toHaveBeenNthCalledWith(2, 3); + + render(); + + await user.selectOptions( + await screen.findByLabelText('app.max_results'), + '3' + ); + + expect(onResultsChange).toHaveBeenNthCalledWith(2, '3'); expect(onPageChange).toHaveBeenNthCalledWith(1, 1); }); }); diff --git a/packages/datagateway-common/src/card/cardView.component.tsx b/packages/datagateway-common/src/card/cardView.component.tsx index af4c4b4f3..fcbb48d44 100644 --- a/packages/datagateway-common/src/card/cardView.component.tsx +++ b/packages/datagateway-common/src/card/cardView.component.tsx @@ -104,6 +104,8 @@ export interface CardViewProps { image?: EntityImageDetails; paginationPosition?: CVPaginationPosition; + + 'data-testid'?: string; } interface CVFilterInfo { @@ -172,6 +174,7 @@ const CardView = (props: CardViewProps): React.ReactElement => { onFilter, onSort, onResultsChange, + 'data-testid': testId, } = props; // Get card information. @@ -453,7 +456,7 @@ const CardView = (props: CardViewProps): React.ReactElement => { const hasFilteredResults = loadedData && (filterUpdate || totalDataCount > 0); return ( - + { {/* Card data */} {/* Selected filters array */} - {selectedFilters.length > 0 && (filterUpdate || totalDataCount > 0) && ( - - {selectedFilters.map((filter, filterIndex) => ( -
  • - {filter.items.map((item, itemIndex) => ( - { - changeFilter(filter.filterKey, item, true); - setFilterUpdate(true); - }} - /> - ))} -
  • - ))} -
    - )} + {selectedFilters.length > 0 && + (filterUpdate || totalDataCount > 0) && ( + + {selectedFilters.map((filter, filterIndex) => ( +
  • + {filter.items.map((item, itemIndex) => ( + { + changeFilter(filter.filterKey, item, true); + setFilterUpdate(true); + }} + /> + ))} +
  • + ))} +
    + )} {/* List of cards */} {hasFilteredResults ? ( diff --git a/packages/datagateway-common/src/card/entityCard.component.test.tsx b/packages/datagateway-common/src/card/entityCard.component.test.tsx index 8fd2339f2..7c54ab803 100644 --- a/packages/datagateway-common/src/card/entityCard.component.test.tsx +++ b/packages/datagateway-common/src/card/entityCard.component.test.tsx @@ -1,10 +1,13 @@ -import { Link } from '@mui/material'; -import { mount, shallow } from 'enzyme'; -import React from 'react'; -import { Investigation } from '../app.types'; +import * as React from 'react'; +import type { Investigation } from '../app.types'; import EntityCard from './entityCard.component'; +import { render, screen, waitFor } from '@testing-library/react'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; describe('Card', () => { + let user: UserEvent; + const entity: Investigation = { id: 1, title: 'Title', @@ -13,29 +16,34 @@ describe('Card', () => { visitId: '2', }; - it('renders correctly', () => { - const wrapper = shallow( + beforeEach(() => { + user = userEvent.setup(); + }); + + it('renders correctly', async () => { + const { asFragment } = render( ); - expect(wrapper.find('[aria-label="card-title"]').text()).toEqual('Title'); - expect(wrapper).toMatchSnapshot(); + expect(await screen.findByText('Title')).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders with an image', () => { - const wrapper = shallow( + it('renders with an image', async () => { + render( ); - const cardImage = wrapper.find('[aria-label="card-image"]'); - expect(cardImage.prop('image')).toEqual('test-url'); - expect(cardImage.prop('title')).toEqual('Card Image'); + + const cardImage = await screen.findByRole('img', { name: 'card-image' }); + expect(cardImage).toHaveAttribute('src', 'test-url'); + expect(cardImage).toHaveAttribute('title', 'Card Image'); }); - it('renders custom title content', () => { - const wrapper = shallow( + it('renders custom title content', async () => { + render( { }} /> ); - expect(wrapper.find('[aria-label="card-title"]').text()).toEqual( - 'Test Title' - ); - expect(wrapper.find('ArrowTooltip').prop('title')).toEqual('Test Title'); + expect(await screen.findByText('Test Title')).toBeInTheDocument(); }); - it('renders with a description', () => { - const wrapper = shallow( + it('renders with a description', async () => { + render( ); - expect(wrapper.find('[aria-label="card-description"]').text()).toEqual( - 'Test Description' - ); + expect(await screen.findByText('Test Description')).toBeInTheDocument(); }); - it('shows a collapsed description if it is too long', () => { + it('shows a collapsed description if it is too long', async () => { // Mock the value of clientHeight to be greater than defaultCollapsedHeight Object.defineProperty(HTMLElement.prototype, 'clientHeight', { configurable: true, @@ -78,27 +81,26 @@ describe('Card', () => { 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate semper commodo. Vivamus sed sapien a dolor aliquam commodo vulputate at est. Maecenas sed lobortis justo, congue lobortis urna. Quisque in pharetra justo. Maecenas nunc quam, rutrum non nisl sit amet, mattis condimentum massa. Donec ut commodo urna, vel rutrum sapien. Integer fermentum quam quis commodo lobortis. Duis cursus, turpis a feugiat malesuada, dui tellus condimentum lorem, sed sagittis magna quam in arcu. Integer ex velit, cursus ut sagittis sit amet, pulvinar nec dolor. Curabitur sagittis tincidunt arcu id vestibulum. Aliquam auctor, ante eget consectetur accumsan, massa odio ornare sapien, ut porttitor lorem nulla et urna. Nam sapien erat, rutrum pretium dolor vel, maximus mattis velit. In non ex lobortis, sollicitudin nulla eget, aliquam neque.'; modifiedEntity.summary = descText; - const wrapper = mount( + render( ); + expect(await screen.findByText(descText)).toBeInTheDocument(); expect( - wrapper.find('[aria-label="card-description"]').first().text() - ).toEqual(descText); - expect(wrapper.find('[aria-label="card-description-link"]').text()).toEqual( - 'entity_card.show_more' - ); + await screen.findByText('entity_card.show_more') + ).toBeInTheDocument(); - wrapper.find(Link).find('a').simulate('click'); - expect(wrapper.find('[aria-label="card-description-link"]').text()).toEqual( - 'entity_card.show_less' - ); + await user.click(await screen.findByText('entity_card.show_more')); + + expect( + await screen.findByText('entity_card.show_less') + ).toBeInTheDocument(); }); - it('no card-description-link if clientHeight < defaultCollapsedHeight', () => { + it('no card-description-link if clientHeight < defaultCollapsedHeight', async () => { // Mock the value of clientHeight to be greater than defaultCollapsedHeight Object.defineProperty(HTMLElement.prototype, 'clientHeight', { configurable: true, @@ -110,23 +112,19 @@ describe('Card', () => { 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate semper commodo. Vivamus sed sapien a dolor aliquam commodo vulputate at est. Maecenas sed lobortis justo, congue lobortis urna. Quisque in pharetra justo. Maecenas nunc quam, rutrum non nisl sit amet, mattis condimentum massa. Donec ut commodo urna, vel rutrum sapien. Integer fermentum quam quis commodo lobortis. Duis cursus, turpis a feugiat malesuada, dui tellus condimentum lorem, sed sagittis magna quam in arcu. Integer ex velit, cursus ut sagittis sit amet, pulvinar nec dolor. Curabitur sagittis tincidunt arcu id vestibulum. Aliquam auctor, ante eget consectetur accumsan, massa odio ornare sapien, ut porttitor lorem nulla et urna. Nam sapien erat, rutrum pretium dolor vel, maximus mattis velit. In non ex lobortis, sollicitudin nulla eget, aliquam neque.'; modifiedEntity.summary = descText; - const wrapper = mount( + render( ); - expect( - wrapper.find('[aria-label="card-description"]').first().text() - ).toEqual(descText); - expect( - wrapper.find('[aria-label="card-description-link"]').exists() - ).toBeFalsy(); + expect(await screen.findByText(descText)).toBeInTheDocument(); + expect(screen.queryByLabelText('card-description-link')).toBeNull(); }); - it('render with information', () => { - const wrapper = shallow( + it('render with information', async () => { + render( { ]} /> ); - expect(wrapper.exists("[data-testid='card-info-visitId']")).toBe(true); - expect(wrapper.find("[data-testid='card-info-visitId']").text()).toEqual( - 'visitId:' - ); - expect(wrapper.exists("[data-testid='card-info-data-visitId']")).toBe(true); - expect( - wrapper.find("[data-testid='card-info-data-visitId']").find('b').text() - ).toEqual('1'); - expect( - wrapper - .find("[data-testid='card-info-data-visitId']") - .find('ArrowTooltip') - .prop('title') - ).toEqual('1'); + expect(await screen.findByText('ICON -')).toBeInTheDocument(); + expect(await screen.findByText('visitId:')).toBeInTheDocument(); + expect(await screen.findByText('1')).toBeInTheDocument(); }); - it('renders with buttons', () => { - const wrapper = shallow( + it('renders with buttons', async () => { + render( { ]} /> ); - expect(wrapper.exists('[aria-label="card-buttons"]')).toBe(true); - expect(wrapper.find('[aria-label="card-button-1"]').text()).toEqual( - 'Test Button One' - ); - expect(wrapper.find('[aria-label="card-button-2"]').text()).toEqual( - 'Test Button Two' - ); + expect( + await screen.findByRole('button', { name: 'Test Button One' }) + ).toBeInTheDocument(); + expect( + await screen.findByRole('button', { name: 'Test Button Two' }) + ).toBeInTheDocument(); }); - it('renders with more information', () => { - const wrapper = mount( + it('renders with more information', async () => { + render( { /> ); - expect(wrapper.exists('[aria-label="card-more-information"]')).toBe(true); - expect(wrapper.exists('[aria-label="card-more-info-expand"]')).toBe(true); + await waitFor(() => { + expect(screen.queryByText('Test Information')).toBeNull(); + }); - // Click on the expansion panel to view more information area. - wrapper - .find('[aria-label="card-more-info-expand"]') - .first() - .find('div') - .first() - .simulate('click'); + await user.click(await screen.findByLabelText('card-more-info-expand')); - expect( - wrapper.find('[aria-label="card-more-info-details"]').first().text() - ).toEqual('Test Information'); + expect(await screen.findByText('Test Information')).toBeInTheDocument(); }); - it('renders with tags', () => { - const wrapper = shallow( + it('renders with tags', async () => { + render( { /> ); - expect(wrapper.exists('[aria-label="card-tags"]')).toBe(true); - expect(wrapper.find('[aria-label="card-tag-Name"]').prop('label')).toEqual( - 'Name' - ); - expect(wrapper.find('[aria-label="card-tag-2"]').prop('label')).toEqual( - '2' - ); + expect(await screen.findByText('Name')).toBeInTheDocument(); + expect(await screen.findByText('2')).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-common/src/detailsPanels/__snapshots__/datafileDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/__snapshots__/datafileDetailsPanel.component.test.tsx.snap index b6eab93b9..8b1d23734 100644 --- a/packages/datagateway-common/src/detailsPanels/__snapshots__/datafileDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/__snapshots__/datafileDetailsPanel.component.test.tsx.snap @@ -1,53 +1,58 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Datafile details panel component renders correctly 1`] = ` - - +
    - - - Test 1 - - - - - - + + Test 1 + + +
    +
    +
    - datafiles.size - - - - 1 B - - - - - + datafiles.size + +

    + + 1 B + +

    +
    +
    - datafiles.location - - - - /test1 - - - - + + datafiles.location + +

    + + /test1 + +

    +
    + + `; diff --git a/packages/datagateway-common/src/detailsPanels/__snapshots__/datasetDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/__snapshots__/datasetDetailsPanel.component.test.tsx.snap index cc0357493..32948d42d 100644 --- a/packages/datagateway-common/src/detailsPanels/__snapshots__/datasetDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/__snapshots__/datasetDetailsPanel.component.test.tsx.snap @@ -1,36 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Dataset details panel component renders correctly 1`] = ` - - +
    - - - Test 1 - - - - - - + + Test 1 + + +
    +
    +
    - datasets.description - - - - - - + + datasets.description + +

    + +

    +
    + + `; diff --git a/packages/datagateway-common/src/detailsPanels/__snapshots__/investigationDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/__snapshots__/investigationDetailsPanel.component.test.tsx.snap index a7ec869f0..66b6d66e9 100644 --- a/packages/datagateway-common/src/detailsPanels/__snapshots__/investigationDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/__snapshots__/investigationDetailsPanel.component.test.tsx.snap @@ -1,68 +1,74 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Investigation details panel component renders correctly 1`] = ` - - +
    - - - Test 1 - - - - - - + + Test 1 + + +
    +
    +
    - investigations.details.name - - - - Test 1 - - - - - + investigations.details.name + +

    + + Test 1 + +

    +
    +
    - investigations.details.start_date - - - - 2019-06-10 - - - - - + investigations.details.start_date + +

    + + 2019-06-10 + +

    +
    +
    - investigations.details.end_date - - - - 2019-06-11 - - - - + + investigations.details.end_date + +

    + + 2019-06-11 + +

    +
    + + `; diff --git a/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.test.tsx b/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.test.tsx index 562d8d717..84db104da 100644 --- a/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.test.tsx +++ b/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { shallow } from 'enzyme'; import { Datafile } from '../app.types'; import DatafileDetailsPanel from './datafileDetailsPanel.component'; +import { render } from '@testing-library/react'; describe('Datafile details panel component', () => { let rowData: Datafile; @@ -23,12 +23,12 @@ describe('Datafile details panel component', () => { }); it('renders correctly', () => { - const wrapper = shallow( + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.tsx b/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.tsx index ba63e176d..d507f423a 100644 --- a/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.tsx +++ b/packages/datagateway-common/src/detailsPanels/datafileDetailsPanel.component.tsx @@ -30,7 +30,12 @@ const DatafileDetailsPanel = ( }, [detailsPanelResize]); return ( - + {datafileData.name} diff --git a/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.test.tsx b/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.test.tsx index 87de8d928..970692c61 100644 --- a/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.test.tsx +++ b/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import * as React from 'react'; import { Dataset } from '../app.types'; import DatasetDetailsPanel from './datasetDetailsPanel.component'; +import { render } from '@testing-library/react'; describe('Dataset details panel component', () => { let rowData: Dataset; @@ -22,12 +22,12 @@ describe('Dataset details panel component', () => { }); it('renders correctly', () => { - const wrapper = shallow( + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.tsx b/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.tsx index c7acc2c89..3ae431030 100644 --- a/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.tsx +++ b/packages/datagateway-common/src/detailsPanels/datasetDetailsPanel.component.tsx @@ -29,7 +29,12 @@ const DatasetDetailsPanel = ( }, [detailsPanelResize]); return ( - + {datasetData.name} diff --git a/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datafileDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datafileDetailsPanel.component.test.tsx.snap index b48a94b86..626bbd639 100644 --- a/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datafileDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datafileDetailsPanel.component.test.tsx.snap @@ -1,24 +1,63 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Datafile details panel component renders correctly 1`] = ` -Object { - "detailsPanelResize": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "rowData": Object { - "createTime": "2019-06-11", - "id": 1, - "location": "/test/location", - "modTime": "2019-06-10", - "name": "Test 1", - }, -} + +
    +
    +
    +
    + + Test 1 + +
    +
    +
    +
    + + datafiles.details.size + +

    + + Unknown + +

    +
    +
    + + datafiles.details.location + +

    + + /test/location + +

    +
    +
    +
    +
    `; diff --git a/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datasetDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datasetDetailsPanel.component.test.tsx.snap index e89137280..d6aa17285 100644 --- a/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datasetDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/datasetDetailsPanel.component.test.tsx.snap @@ -3,6 +3,7 @@ exports[`Dataset details panel component should render correctly 1`] = `
    @@ -205,6 +206,7 @@ exports[`Dataset details panel component should render correctly 1`] = ` exports[`Dataset details panel component should render type tab when present in the data 1`] = `
    diff --git a/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/visitDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/visitDetailsPanel.component.test.tsx.snap index 5c1d2658b..bcb9b50fd 100644 --- a/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/visitDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/dls/__snapshots__/visitDetailsPanel.component.test.tsx.snap @@ -3,6 +3,7 @@ exports[`Visit details panel component should check if multiple publications result in change of title to plural version 1`] = `
    @@ -233,6 +234,7 @@ exports[`Visit details panel component should check if multiple publications res exports[`Visit details panel component should check if multiple samples result in change of title to plural version 1`] = `
    @@ -463,6 +465,7 @@ exports[`Visit details panel component should check if multiple samples result i exports[`Visit details panel component should gracefully handles InvestigationUsers without Users 1`] = `
    @@ -671,6 +674,7 @@ exports[`Visit details panel component should gracefully handles InvestigationUs exports[`Visit details panel component should render correctly 1`] = `
    @@ -849,6 +853,7 @@ exports[`Visit details panel component should render correctly 1`] = ` exports[`Visit details panel component should render user, sample and publication tabs when present in the data 1`] = `
    diff --git a/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.test.tsx b/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.test.tsx index db7566993..e071e156a 100644 --- a/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.test.tsx +++ b/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.test.tsx @@ -1,9 +1,9 @@ import React from 'react'; import DatafileDetailsPanel from './datafileDetailsPanel.component'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { mount, ReactWrapper } from 'enzyme'; import { Datafile } from '../../app.types'; -import { useDatafileDetails } from '../../api/datafiles'; +import { useDatafileDetails } from '../../api'; +import { render, RenderResult } from '@testing-library/react'; jest.mock('../../api/datafiles'); @@ -11,8 +11,8 @@ describe('Datafile details panel component', () => { let rowData: Datafile; const detailsPanelResize = jest.fn(); - const createWrapper = (): ReactWrapper => { - return mount( + const renderComponent = (): RenderResult => + render( { /> ); - }; beforeEach(() => { rowData = { @@ -41,22 +40,17 @@ describe('Datafile details panel component', () => { }); it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('DatafileDetailsPanel').props()).toMatchSnapshot(); - }); - - it('calls useDatafileDetails hook on load', () => { - createWrapper(); - expect(useDatafileDetails).toHaveBeenCalledWith(rowData.id); + const { asFragment } = renderComponent(); + expect(asFragment()).toMatchSnapshot(); }); it('calls detailsPanelResize on load', () => { - createWrapper(); + renderComponent(); expect(detailsPanelResize).toHaveBeenCalled(); }); it('does not call detailsPanelResize if not provided', () => { - mount( + render( diff --git a/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.tsx b/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.tsx index a648923f1..d2cbb46a4 100644 --- a/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.tsx +++ b/packages/datagateway-common/src/detailsPanels/dls/datafileDetailsPanel.component.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Typography, Grid, Divider, styled } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { useDatafileDetails } from '../../api/datafiles'; +import { useDatafileDetails } from '../../api'; import { Datafile, Entity } from '../../app.types'; import { formatBytes } from '../../table/cellRenderers/cellContentRenderers'; @@ -32,7 +32,11 @@ const DatafileDetailsPanel = ( }, [detailsPanelResize]); return ( -
    +
    diff --git a/packages/datagateway-common/src/detailsPanels/dls/datasetDetailsPanel.component.tsx b/packages/datagateway-common/src/detailsPanels/dls/datasetDetailsPanel.component.tsx index 1561d8116..765fc6405 100644 --- a/packages/datagateway-common/src/detailsPanels/dls/datasetDetailsPanel.component.tsx +++ b/packages/datagateway-common/src/detailsPanels/dls/datasetDetailsPanel.component.tsx @@ -88,7 +88,11 @@ const DatasetDetailsPanel = ( }, [data, selectedTab, changeTab]); return ( -
    +
    +
    { let rowData: Investigation; @@ -47,12 +47,12 @@ describe('Investigation details panel component', () => { }); it('renders correctly', () => { - const wrapper = shallow( + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-common/src/detailsPanels/investigationDetailsPanel.component.tsx b/packages/datagateway-common/src/detailsPanels/investigationDetailsPanel.component.tsx index 74b0dfa08..f47e5f868 100644 --- a/packages/datagateway-common/src/detailsPanels/investigationDetailsPanel.component.tsx +++ b/packages/datagateway-common/src/detailsPanels/investigationDetailsPanel.component.tsx @@ -29,7 +29,12 @@ const InvestigationDetailsPanel = ( }, [detailsPanelResize]); return ( - + {investigationData.title} diff --git a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datafileDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datafileDetailsPanel.component.test.tsx.snap index 3bfc9bd3d..691fb5648 100644 --- a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datafileDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datafileDetailsPanel.component.test.tsx.snap @@ -3,6 +3,7 @@ exports[`Datafile details panel component should render correctly 1`] = `
    @@ -107,6 +108,7 @@ exports[`Datafile details panel component should render correctly 1`] = ` exports[`Datafile details panel component should render parameters tab when present in the data 1`] = `
    diff --git a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datasetDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datasetDetailsPanel.component.test.tsx.snap index edf6cda09..007b136c8 100644 --- a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datasetDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/datasetDetailsPanel.component.test.tsx.snap @@ -3,6 +3,7 @@ exports[`Dataset details panel component should render correctly 1`] = `
    @@ -146,6 +147,7 @@ exports[`Dataset details panel component should render correctly 1`] = ` exports[`Dataset details panel component should render type tab when present in the data 1`] = `
    diff --git a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/instrumentDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/instrumentDetailsPanel.component.test.tsx.snap index 66b64b473..4cc2f1fd4 100644 --- a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/instrumentDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/instrumentDetailsPanel.component.test.tsx.snap @@ -3,6 +3,7 @@ exports[`Instrument details panel component should gracefully handle InstrumentScientists without Users 1`] = `
    @@ -158,6 +159,7 @@ exports[`Instrument details panel component should gracefully handle InstrumentS exports[`Instrument details panel component should render correctly 1`] = `
    @@ -283,6 +285,7 @@ exports[`Instrument details panel component should render correctly 1`] = ` exports[`Instrument details panel component should render users tab when present in the data 1`] = `
    diff --git a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/investigationDetailsPanel.component.test.tsx.snap b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/investigationDetailsPanel.component.test.tsx.snap index 8433f2906..687f63aa9 100644 --- a/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/investigationDetailsPanel.component.test.tsx.snap +++ b/packages/datagateway-common/src/detailsPanels/isis/__snapshots__/investigationDetailsPanel.component.test.tsx.snap @@ -3,6 +3,7 @@ exports[`Investigation details panel component should check if multiple publications result in change of title to plural version 1`] = `
    @@ -247,6 +248,7 @@ exports[`Investigation details panel component should check if multiple publicat exports[`Investigation details panel component should check if multiple samples result in change of title to plural version 1`] = `
    @@ -491,6 +493,7 @@ exports[`Investigation details panel component should check if multiple samples exports[`Investigation details panel component should gracefully handles StudyInvestigations without Studies and InvestigationUsers without Users 1`] = `
    @@ -693,6 +696,7 @@ exports[`Investigation details panel component should gracefully handles StudyIn exports[`Investigation details panel component should render correctly 1`] = `
    @@ -885,6 +889,7 @@ exports[`Investigation details panel component should render correctly 1`] = ` exports[`Investigation details panel component should render user, sample and publication tabs when present in the data 1`] = `
    diff --git a/packages/datagateway-common/src/detailsPanels/isis/datafileDetailsPanel.component.tsx b/packages/datagateway-common/src/detailsPanels/isis/datafileDetailsPanel.component.tsx index a92fe0991..b7d74929c 100644 --- a/packages/datagateway-common/src/detailsPanels/isis/datafileDetailsPanel.component.tsx +++ b/packages/datagateway-common/src/detailsPanels/isis/datafileDetailsPanel.component.tsx @@ -85,7 +85,11 @@ const DatafileDetailsPanel = ( }, [selectedTab, data, changeTab]); return ( -
    +
    +
    +
    +
    { - it('receives and uses the theme options', () => { - // Create a basic theme. - const theme = createTheme({ - palette: { - mode: 'dark', - }, - }); - - const wrapper = shallow( + it('uses default theme before receiving theme event', () => { + render( -
    Test
    + + test +
    ); - // Dispatch the theme options event. + // value taken from https://mui.com/material-ui/customization/default-theme/ + // (default value is #1976d2) + // mui converts the hex value to CSS rgb expression + + expect(screen.getByTestId('box')).toHaveStyle( + 'background-color: rgb(25, 118, 210);' + ); + }); + + it('uses the theme sent through the theme event', () => { document.dispatchEvent( new CustomEvent(MicroFrontendId, { detail: { type: SendThemeOptionsType, payload: { - theme, + theme: createTheme({ + palette: { + primary: { + main: '#123456', + }, + }, + }), }, }, }) ); - // Force update the wrapper as the state will not - // update since the theme options are from a global variable. - wrapper.instance().forceUpdate(); + render( + + + test + + + ); + + expect(screen.getByTestId('box')).toHaveStyle( + 'background-color: rgb(18, 52, 86);' + ); + }); - expect(wrapper.childAt(0).props()).toHaveProperty('theme'); - expect(wrapper.childAt(0).prop('theme')).toEqual(theme); + it('ignores non-theme events', () => { + document.dispatchEvent( + new CustomEvent(MicroFrontendId, { + detail: { + type: SendThemeOptionsType, + payload: { + theme: createTheme({ + palette: { + primary: { + main: '#abcdef', + }, + }, + }), + }, + }, + }) + ); + + document.dispatchEvent( + new CustomEvent(MicroFrontendId, { + detail: { + type: 'ImposterEvent', + payload: { + theme: createTheme(), + yellow: 'sus', + }, + }, + }) + ); + + render( + + + test + + + ); + + // value taken from https://mui.com/material-ui/customization/default-theme/ + // (default value is #1976d2) + // mui converts the hex value to CSS rgb expression + + // expect the dispatched theme to be used. + expect(screen.getByTestId('box')).toHaveStyle( + 'background-color: rgb(171, 205, 239);' + ); }); }); diff --git a/packages/datagateway-common/src/homePage/__snapshots__/homePage.component.test.tsx.snap b/packages/datagateway-common/src/homePage/__snapshots__/homePage.component.test.tsx.snap index b551682fe..eacddbf4e 100644 --- a/packages/datagateway-common/src/homePage/__snapshots__/homePage.component.test.tsx.snap +++ b/packages/datagateway-common/src/homePage/__snapshots__/homePage.component.test.tsx.snap @@ -1,54 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Home page component homepage renders correctly 1`] = ` -
    +
    - - - Data discovery @@ -57,390 +24,233 @@ exports[`Home page component homepage renders correctly 1`] = ` access - - - - +

    for large-scale science facilities - - - +

    +
    +
    -
    - - - - - - - home_page.browse.title - - - home_page.browse.description1 - - - + home_page.browse.title + +

    + home_page.browse.description1 +

    +

    DataGateway focuses on providing data discovery and data access functionality to the data. - - - - +

    - home_page.browse.button - - - - - + + home_page.browse.button + + +
    +
    +
    - +
    +
    +
    - - - - - +
    +
    - - - - - - - home_page.search.title - - - home_page.search.description - - - - home_page.search.button - - - - - - -
    +

    + home_page.search.title +

    +

    + home_page.search.description +

    + +
    +
    +
    +
    - - - - - - home_page.download.title - - - home_page.download.description - - - - home_page.download.button - - - - - - -
    +

    + home_page.download.title +

    +

    + home_page.download.description +

    + +
    +
    +
    +
    - - - home_page.facility.title - - - home_page.facility.description - - - + home_page.facility.title + +

    + home_page.facility.description +

    +
    - home_page.facility.button - - - + + home_page.facility.button + + +
    +
    +
    - - - - -
    +
    +
    +
    +
    +
    `; diff --git a/packages/datagateway-common/src/homePage/homePage.component.test.tsx b/packages/datagateway-common/src/homePage/homePage.component.test.tsx index 07a8256a0..85c040c17 100644 --- a/packages/datagateway-common/src/homePage/homePage.component.test.tsx +++ b/packages/datagateway-common/src/homePage/homePage.component.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { shallow } from 'enzyme'; import HomePage, { HomePageProps } from './homePage.component'; +import { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; describe('Home page component', () => { let props: HomePageProps; @@ -20,7 +21,11 @@ describe('Home page component', () => { }); it('homepage renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { asFragment } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-common/src/hooks/useSticky.test.ts b/packages/datagateway-common/src/hooks/useSticky.test.ts new file mode 100644 index 000000000..23bac3067 --- /dev/null +++ b/packages/datagateway-common/src/hooks/useSticky.test.ts @@ -0,0 +1,86 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useSticky from './useSticky'; +import { fireEvent } from '@testing-library/react'; + +describe('useSticky', () => { + it('returns isSticky true if the bottom of the target element is scrolled past', () => { + const mockGetBoundingClientRect = jest.fn(); + const target = document.createElement('div'); + target.getBoundingClientRect = mockGetBoundingClientRect; + + // if window scrollY is greater than boundingClientRect bottom + // that means the element is scrolled out of view + global.scrollY = 100; + mockGetBoundingClientRect.mockReturnValue({ + bottom: 50, + }); + + const { waitFor, result } = renderHook(() => + useSticky({ current: target }) + ); + + // scroll the window + fireEvent.scroll(window, {}); + + waitFor(() => { + expect(result.current.isSticky).toBe(true); + }); + }); + + it('returns isSticky false if the bottom of the target element is still in view', () => { + const mockGetBoundingClientRect = jest.fn(); + const target = document.createElement('div'); + target.getBoundingClientRect = mockGetBoundingClientRect; + + // if window scrollY is greater than boundingClientRect bottom + // that means the element is scrolled out of view + global.scrollY = 100; + mockGetBoundingClientRect.mockReturnValue({ + bottom: 150, + }); + + const { waitFor, result } = renderHook(() => + useSticky({ current: target }) + ); + + // scroll the window + fireEvent.scroll(window, {}); + + waitFor(() => { + expect(result.current.isSticky).toBe(false); + }); + }); + + it('returns isSticky false upon window scroll if the given react ref points to null', () => { + const { waitFor, result } = renderHook(() => useSticky({ current: null })); + // scroll the window + fireEvent.scroll(window, {}); + waitFor(() => { + expect(result.current.isSticky).toBe(false); + }); + }); + + it('returns isStick false upon window scroll if bottom coordinate of the target element is unavailable', () => { + const mockGetBoundingClientRect = jest.fn(); + const target = document.createElement('div'); + target.getBoundingClientRect = mockGetBoundingClientRect; + + // if window scrollY is greater than boundingClientRect bottom + // that means the element is scrolled out of view + global.scrollY = 100; + mockGetBoundingClientRect.mockReturnValue({ + top: 150, + }); + + const { waitFor, result } = renderHook(() => + useSticky({ current: target }) + ); + + // scroll the window + fireEvent.scroll(window, {}); + + waitFor(() => { + expect(result.current.isSticky).toBe(false); + }); + }); +}); diff --git a/packages/datagateway-common/src/hooks/useSticky.ts b/packages/datagateway-common/src/hooks/useSticky.ts new file mode 100644 index 000000000..aeb12c51f --- /dev/null +++ b/packages/datagateway-common/src/hooks/useSticky.ts @@ -0,0 +1,36 @@ +import React from 'react'; +import debounce from 'lodash.debounce'; + +function useSticky(targetElement: React.RefObject): { + isSticky: boolean; +} { + const [isSticky, setIsSticky] = React.useState(false); + + const handleScroll = React.useCallback((): void => { + if ( + targetElement.current && + targetElement.current.getBoundingClientRect().bottom + ) { + if ( + window.scrollY > targetElement.current.getBoundingClientRect().bottom + ) { + setIsSticky(true); + } else { + setIsSticky(false); + } + } + }, [targetElement]); + + React.useEffect(() => { + // Use lodash.debounce for handleScroll with a wait of 20ms. + window.addEventListener('scroll', debounce(handleScroll, 20)); + + return () => { + window.removeEventListener('scroll', () => handleScroll); + }; + }, [handleScroll]); + + return { isSticky }; +} + +export default useSticky; diff --git a/packages/datagateway-common/src/index.tsx b/packages/datagateway-common/src/index.tsx index aa77e92fa..3bf565712 100644 --- a/packages/datagateway-common/src/index.tsx +++ b/packages/datagateway-common/src/index.tsx @@ -77,6 +77,6 @@ export { default as DLSDatasetDetailsPanel } from './detailsPanels/dls/datasetDe export { default as DLSVisitDetailsPanel } from './detailsPanels/dls/visitDetailsPanel.component'; export { default as InvestigationDetailsPanel } from './detailsPanels/investigationDetailsPanel.component'; export { default as DatasetDetailsPanel } from './detailsPanels/datasetDetailsPanel.component'; -export { default as DatafileDetailsPanel } from './detailsPanels/datasetDetailsPanel.component'; +export { default as DatafileDetailsPanel } from './detailsPanels/datafileDetailsPanel.component'; // ReactDOM.render(, document.getElementById('root')); diff --git a/packages/datagateway-common/src/preloader/__snapshots__/preloader.component.test.tsx.snap b/packages/datagateway-common/src/preloader/__snapshots__/preloader.component.test.tsx.snap index edee2ed52..91c6aa27e 100644 --- a/packages/datagateway-common/src/preloader/__snapshots__/preloader.component.test.tsx.snap +++ b/packages/datagateway-common/src/preloader/__snapshots__/preloader.component.test.tsx.snap @@ -1,180 +1,48 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Preloader component does not render when the site is not loading 1`] = `
    `; +exports[`Preloader component does not render when the site is not loading 1`] = ` + +
    + +`; exports[`Preloader component renders when the site is loading 1`] = ` -
    - +
    - - - - - +
    + + + + + +
    +
    +
    + Loading...
    - - Loading... - - -
    +
    + `; diff --git a/packages/datagateway-common/src/preloader/preloader.component.test.tsx b/packages/datagateway-common/src/preloader/preloader.component.test.tsx index fcad4d6a5..1a234e533 100644 --- a/packages/datagateway-common/src/preloader/preloader.component.test.tsx +++ b/packages/datagateway-common/src/preloader/preloader.component.test.tsx @@ -1,15 +1,15 @@ -import React from 'react'; +import { render } from '@testing-library/react'; +import * as React from 'react'; import Preloader from './preloader.component'; -import { shallow } from 'enzyme'; describe('Preloader component', () => { - it('renders when the site is loading', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders when the site is loading', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('does not render when the site is not loading', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('does not render when the site is not loading', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-common/src/setupTests.tsx b/packages/datagateway-common/src/setupTests.tsx index f56f55044..2ef17f1cc 100644 --- a/packages/datagateway-common/src/setupTests.tsx +++ b/packages/datagateway-common/src/setupTests.tsx @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import '@testing-library/jest-dom'; import React from 'react'; -import Enzyme from 'enzyme'; -import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; import { Action } from 'redux'; import { StateType } from './state/app.types'; import { initialState } from './state/reducers/dgcommon.reducer'; @@ -17,9 +15,6 @@ import { createMemoryHistory, History } from 'history'; jest.setTimeout(15000); -// Unofficial React 17 Enzyme adapter -Enzyme.configure({ adapter: new Adapter() }); - if (typeof window.URL.createObjectURL === 'undefined') { // required as work-around for enzyme/jest environment not implementing window.URL.createObjectURL method Object.defineProperty(window.URL, 'createObjectURL', { diff --git a/packages/datagateway-common/src/sticky.component.test.tsx b/packages/datagateway-common/src/sticky.component.test.tsx index 58af59c88..86df4256a 100644 --- a/packages/datagateway-common/src/sticky.component.test.tsx +++ b/packages/datagateway-common/src/sticky.component.test.tsx @@ -1,118 +1,46 @@ import React from 'react'; -import { mount, shallow, ReactWrapper } from 'enzyme'; -import Sticky, { useSticky } from './sticky.component'; -import { Paper } from '@mui/material'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { render, screen } from '@testing-library/react'; +import Sticky from './sticky.component'; +import useSticky from './hooks/useSticky'; -describe('Sticky component', () => { - const createShallowWrapper = (): ReactWrapper => { - return shallow( - -
    - - ); - }; +jest.mock('./hooks/useSticky', () => ({ + __esModule: true, + default: jest.fn(), +})); - const createWrapper = (): ReactWrapper => { - return mount( +describe('Sticky component', () => { + it('renders children with fixed position and 100% width when sticky', async () => { + (useSticky as jest.MockedFn).mockReturnValue({ + isSticky: true, + }); + render(
    ); - }; - - const currentMock: HTMLDivElement = document.createElement('div'); - const dimensionsMock = { - height: 0, - width: 0, - x: 0, - y: 0, - bottom: 1, - left: 0, - right: 0, - top: 0, - toJSON: jest.fn(), - }; - - it('eventListener added and removed with useEffect', () => { - // Allow cleanUp to be called manually - let cleanUp; - const spyUseEffect = jest - .spyOn(React, 'useEffect') - .mockImplementation((f) => { - cleanUp = f(); - }); - const spyAdd = jest.spyOn(window, 'addEventListener'); - const spyRemove = jest.spyOn(window, 'removeEventListener'); - - createShallowWrapper(); - expect(spyAdd).toHaveBeenCalledTimes(1); - expect(spyRemove).toHaveBeenCalledTimes(0); - - cleanUp(); - expect(spyAdd).toHaveBeenCalledTimes(1); - expect(spyRemove).toHaveBeenCalledTimes(1); - - spyUseEffect.mockRestore(); - spyAdd.mockRestore(); - spyRemove.mockRestore(); - }); - it('handleScroll does nothing when target undefined', () => { - // Allow handleScroll to be called manually - let handleScrollMock; - const spyUseCallback = jest - .spyOn(React, 'useCallback') - .mockImplementation((f) => { - handleScrollMock = f; - return f; - }); + const paper = screen.getByTestId('sticky-paper'); - // Elevation is initially 0 (not stickied) - const wrapper = createWrapper(); - expect(wrapper.find(Paper).props().elevation).toEqual(0); - - // Mock scrolling beyond bottom of getBoundingClientRect - Object.defineProperty(global.window, 'scrollY', { value: 2 }); - handleScrollMock(); - expect(wrapper.find(Paper).props().elevation).toEqual(0); - - spyUseCallback.mockRestore(); + // should be elevated when sticky + expect(paper).toHaveStyle( + 'z-index: 9; position: fixed; width: 100%; top: 0' + ); }); - it('useSticky works correctly', () => { - // Allow handleScroll to be called manually - let handleScrollMock; - const spyUseCallback = jest - .spyOn(React, 'useCallback') - .mockImplementation((f) => { - handleScrollMock = f; - return f; - }); - const spyGetRect = jest - .spyOn(currentMock, 'getBoundingClientRect') - .mockImplementation(() => { - return dimensionsMock; - }); - const { result } = renderHook(() => useSticky({ current: currentMock })); - - Object.defineProperty(global.window, 'scrollY', { value: 0 }); - act(() => { - handleScrollMock(); - }); - - expect(result.current.isSticky).toEqual(false); - - Object.defineProperty(global.window, 'scrollY', { value: 2 }); - act(() => { - handleScrollMock(); + it('renders children normally when not sticky', () => { + (useSticky as jest.MockedFn).mockReturnValue({ + isSticky: false, }); + render( + +
    + + ); - expect(result.current.isSticky).toEqual(true); + const paper = screen.getByTestId('sticky-paper'); - spyUseCallback.mockRestore(); - spyGetRect.mockRestore(); + // should not be elevated + expect(paper).not.toHaveStyle('position: fixed; width: 100%; top: 0'); + expect(paper).toHaveStyle('z-index: 9'); }); - - // Sticky functionality tested in dataview e2e breadcrumbs tests }); diff --git a/packages/datagateway-common/src/sticky.component.tsx b/packages/datagateway-common/src/sticky.component.tsx index f45e15167..edc8179a1 100644 --- a/packages/datagateway-common/src/sticky.component.tsx +++ b/packages/datagateway-common/src/sticky.component.tsx @@ -1,39 +1,7 @@ import React from 'react'; import { keyframes, Paper, styled } from '@mui/material'; -import debounce from 'lodash.debounce'; - -export function useSticky(targetElement: React.RefObject): { - isSticky: boolean; -} { - const [isSticky, setIsSticky] = React.useState(false); - - const handleScroll = React.useCallback((): void => { - if ( - targetElement.current && - targetElement.current.getBoundingClientRect().bottom - ) { - if ( - window.scrollY > targetElement.current.getBoundingClientRect().bottom - ) { - setIsSticky(true); - } else { - setIsSticky(false); - } - } - }, [targetElement]); - - React.useEffect(() => { - // Use lodash.debounce for handleScroll with a wait of 20ms. - window.addEventListener('scroll', debounce(handleScroll, 20)); - - return () => { - window.removeEventListener('scroll', () => handleScroll); - }; - }, [handleScroll]); - - return { isSticky }; -} +import useSticky from './hooks/useSticky'; const moveDown = keyframes` from { @@ -77,6 +45,7 @@ const Sticky = (props: { children: React.ReactNode }): React.ReactElement => { // Wrap navbar components in Paper to allow for when // it is sticky to stand out on the page when scrolling. - - + +
    +

    + Rendered an action using test! +

    +
    +
    `; exports[`Action cell component renders no actions correctly 1`] = ` - + +
    + `; diff --git a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/cellContentRenderers.test.tsx.snap b/packages/datagateway-common/src/table/cellRenderers/__snapshots__/cellContentRenderers.test.tsx.snap deleted file mode 100644 index e4219fc3e..000000000 --- a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/cellContentRenderers.test.tsx.snap +++ /dev/null @@ -1,64 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Cell content renderers datasetLink renders correctly 1`] = ` - - test - -`; - -exports[`Cell content renderers investigationLink renders correctly 1`] = ` - - test - -`; - -exports[`Cell content renderers tableLink renders correctly 1`] = ` - - test text - -`; diff --git a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/dataCell.component.test.tsx.snap b/packages/datagateway-common/src/table/cellRenderers/__snapshots__/dataCell.component.test.tsx.snap index 41551ea48..b9f68e7f7 100644 --- a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/dataCell.component.test.tsx.snap +++ b/packages/datagateway-common/src/table/cellRenderers/__snapshots__/dataCell.component.test.tsx.snap @@ -1,141 +1,72 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Data cell component renders correctly 1`] = ` - - +
    - non nested property - - -
    - + +
    +
    +
    - + `; exports[`Data cell component renders nested cell data correctly 1`] = ` - - +
    - nested property - - -
    - + +
    +
    +
    - + `; exports[`Data cell component renders provided cell data correctly 1`] = ` - - +
    - provided test - - -
    - + +
    +
    +
    - + `; diff --git a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/expandCell.component.test.tsx.snap b/packages/datagateway-common/src/table/cellRenderers/__snapshots__/expandCell.component.test.tsx.snap index 6265c2ecf..ab35deda1 100644 --- a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/expandCell.component.test.tsx.snap +++ b/packages/datagateway-common/src/table/cellRenderers/__snapshots__/expandCell.component.test.tsx.snap @@ -1,36 +1,61 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Expand cell component renders correctly when expanded 1`] = ` - - +
    - - - + +
    + `; exports[`Expand cell component renders correctly when not expanded 1`] = ` - - +
    - - - + +
    + `; diff --git a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/selectCell.component.test.tsx.snap b/packages/datagateway-common/src/table/cellRenderers/__snapshots__/selectCell.component.test.tsx.snap index 47a33262b..15ff48788 100644 --- a/packages/datagateway-common/src/table/cellRenderers/__snapshots__/selectCell.component.test.tsx.snap +++ b/packages/datagateway-common/src/table/cellRenderers/__snapshots__/selectCell.component.test.tsx.snap @@ -1,219 +1,202 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Select cell component renders correctly when checked 1`] = ` - - +
    - - + + + + + - - +
    + `; exports[`Select cell component renders correctly when selectedRows is undefined 1`] = ` - - +
    - - - } - className="tour-dataview-add-to-cart" - disabled={true} - icon={ - + + + + - - +
    + `; exports[`Select cell component renders correctly when selectedRows loading is true 1`] = ` - - +
    - - - } - className="tour-dataview-add-to-cart" - disabled={true} - icon={ - + + + + - - +
    + `; exports[`Select cell component renders correctly when selectedRows parentSelected is true 1`] = ` - - +
    - - + + + + - - +
    + `; exports[`Select cell component renders correctly when unchecked 1`] = ` - - +
    - - - } - className="tour-dataview-add-to-cart" - disabled={false} - icon={ - + + + + + - - +
    + `; diff --git a/packages/datagateway-common/src/table/cellRenderers/actionCell.component.test.tsx b/packages/datagateway-common/src/table/cellRenderers/actionCell.component.test.tsx index 014426cfc..a44943d73 100644 --- a/packages/datagateway-common/src/table/cellRenderers/actionCell.component.test.tsx +++ b/packages/datagateway-common/src/table/cellRenderers/actionCell.component.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react'; +import * as React from 'react'; import ActionCell from './actionCell.component'; import { TableActionProps } from '../table.component'; @@ -13,13 +13,15 @@ describe('Action cell component', () => { className: 'test-class', }; - it('renders no actions correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders no actions correctly', async () => { + const { asFragment } = render( + + ); + expect(asFragment()).toMatchSnapshot(); }); - it('renders an action correctly', () => { - const wrapper = shallow( + it('renders an action correctly', async () => { + const { asFragment } = render( { ]} /> ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-common/src/table/cellRenderers/cellContentRenderers.test.tsx b/packages/datagateway-common/src/table/cellRenderers/cellContentRenderers.test.tsx index 2b4c6c16a..0035a45f9 100644 --- a/packages/datagateway-common/src/table/cellRenderers/cellContentRenderers.test.tsx +++ b/packages/datagateway-common/src/table/cellRenderers/cellContentRenderers.test.tsx @@ -1,15 +1,13 @@ import React from 'react'; -import { shallow } from 'enzyme'; import { MemoryRouter } from 'react-router-dom'; -import { Link } from '@mui/material'; import { - formatBytes, datasetLink, - investigationLink, - tableLink, + filterStudyInfoInvestigations, + formatBytes, formatCountOrSize, getStudyInfoInvestigation, - filterStudyInfoInvestigations, + investigationLink, + tableLink, } from './cellContentRenderers'; import type { DateFilter, @@ -17,6 +15,7 @@ import type { Study, TextFilter, } from '../../app.types'; +import { render, screen } from '@testing-library/react'; describe('Cell content renderers', () => { describe('formatBytes', () => { @@ -345,28 +344,37 @@ describe('Cell content renderers', () => { describe('datasetLink', () => { it('renders correctly', () => { - const wrapper = shallow( + render( {datasetLink('1', 2, 'test', 'card')} ); - expect(wrapper.find(Link)).toMatchSnapshot(); + expect(screen.getByRole('link', { name: 'test' })).toHaveAttribute( + 'href', + '/browse/investigation/1/dataset/2/datafile?view=card' + ); }); }); describe('investigationLink', () => { it('renders correctly', () => { - const wrapper = shallow( + render( {investigationLink(1, 'test', 'card')} ); - expect(wrapper.find(Link)).toMatchSnapshot(); + expect(screen.getByRole('link', { name: 'test' })).toHaveAttribute( + 'href', + '/browse/investigation/1/dataset?view=card' + ); }); }); describe('tableLink', () => { it('renders correctly', () => { - const wrapper = shallow( + render( {tableLink('/test/url', 'test text')} ); - expect(wrapper.find(Link)).toMatchSnapshot(); + expect(screen.getByRole('link', { name: 'test text' })).toHaveAttribute( + 'href', + '/test/url' + ); }); }); }); diff --git a/packages/datagateway-common/src/table/cellRenderers/dataCell.component.test.tsx b/packages/datagateway-common/src/table/cellRenderers/dataCell.component.test.tsx index 64c3c17e3..410f9e2f3 100644 --- a/packages/datagateway-common/src/table/cellRenderers/dataCell.component.test.tsx +++ b/packages/datagateway-common/src/table/cellRenderers/dataCell.component.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react'; +import * as React from 'react'; import DataCell from './dataCell.component'; describe('Data cell component', () => { @@ -10,37 +10,37 @@ describe('Data cell component', () => { rowIndex: 1, rowData: { test: 'non nested property', - nested: { test: 'nested property' }, + nested: { + test: 'nested property', + }, }, }; - it('renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders correctly', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders provided cell data correctly', () => { - const wrapper = shallow( + it('renders provided cell data correctly', async () => { + const { asFragment } = render( {'provided test'}} /> ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders nested cell data correctly', () => { - const wrapper = shallow( + it('renders nested cell data correctly', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('gracefully handles invalid dataKeys', () => { - shallow(); - - shallow(); - - shallow(); + it('gracefully handles invalid dataKeys', async () => { + render(); + render(); + render(); }); }); diff --git a/packages/datagateway-common/src/table/cellRenderers/expandCell.component.test.tsx b/packages/datagateway-common/src/table/cellRenderers/expandCell.component.test.tsx index e7be5932c..860030baa 100644 --- a/packages/datagateway-common/src/table/cellRenderers/expandCell.component.test.tsx +++ b/packages/datagateway-common/src/table/cellRenderers/expandCell.component.test.tsx @@ -1,8 +1,11 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import * as React from 'react'; import ExpandCell from './expandCell.component'; describe('Expand cell component', () => { + let user: UserEvent; const setExpandedIndex = jest.fn(); const expandCellProps = { columnIndex: 1, @@ -14,35 +17,39 @@ describe('Expand cell component', () => { setExpandedIndex, }; + beforeEach(() => { + user = userEvent.setup(); + }); + afterEach(() => { setExpandedIndex.mockClear(); }); - it('renders correctly when expanded', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders correctly when expanded', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('sets the expanded index to -1 when ExpandLess button is pressed', () => { - const wrapper = shallow(); - - wrapper.childAt(0).prop('onClick')(); + it('sets the expanded index to -1 when ExpandLess button is pressed', async () => { + render(); + await user.click( + await screen.findByRole('button', { name: 'Hide details' }) + ); expect(setExpandedIndex).toHaveBeenCalledWith(-1); }); - it('renders correctly when not expanded', () => { - const wrapper = shallow( + it('renders correctly when not expanded', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('sets the expanded index to rowIndex when ExpandMore button is pressed', () => { - const wrapper = shallow( - + it('sets the expanded index to rowIndex when ExpandMore button is pressed', async () => { + render(); + await user.click( + await screen.findByRole('button', { name: 'Show details' }) ); - - wrapper.childAt(0).prop('onClick')(); expect(setExpandedIndex).toHaveBeenCalledWith(expandCellProps.rowIndex); }); }); diff --git a/packages/datagateway-common/src/table/cellRenderers/selectCell.component.test.tsx b/packages/datagateway-common/src/table/cellRenderers/selectCell.component.test.tsx index d2d83d57e..9fd588a49 100644 --- a/packages/datagateway-common/src/table/cellRenderers/selectCell.component.test.tsx +++ b/packages/datagateway-common/src/table/cellRenderers/selectCell.component.test.tsx @@ -1,8 +1,11 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; import React from 'react'; -import { shallow } from 'enzyme'; import SelectCell from './selectCell.component'; describe('Select cell component', () => { + let user: UserEvent; const setLastChecked = jest.fn(); const onCheck = jest.fn(); const onUncheck = jest.fn(); @@ -36,100 +39,99 @@ describe('Select cell component', () => { loading: false, }; + beforeEach(() => { + user = userEvent.setup(); + }); + afterEach(() => { setLastChecked.mockClear(); onCheck.mockClear(); onUncheck.mockClear(); }); - it('renders correctly when unchecked', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders correctly when unchecked', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly when checked', () => { - const wrapper = shallow( + it('renders correctly when checked', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly when selectedRows is undefined', () => { - const wrapper = shallow( + it('renders correctly when selectedRows is undefined', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly when selectedRows loading is true', () => { - const wrapper = shallow( + it('renders correctly when selectedRows loading is true', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); it('renders correctly when selectedRows parentSelected is true', () => { - const wrapper = shallow( + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('calls setLastChecked when checkbox is clicked', () => { - const wrapper = shallow(); - - wrapper.find('.tour-dataview-add-to-cart').prop('onClick')( - new MouseEvent('click') + it('calls setLastChecked when checkbox is clicked', async () => { + render(); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 2' }) ); expect(setLastChecked).toHaveBeenCalledWith(2); }); - it('calls onCheck when the row is unselected and the checkbox is clicked', () => { - const wrapper = shallow(); - - wrapper.find('.tour-dataview-add-to-cart').prop('onClick')( - new MouseEvent('click') + it('calls onCheck when the row is unselected and the checkbox is clicked', async () => { + render(); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 2' }) ); expect(onCheck).toHaveBeenCalledWith([3]); }); - it('calls onUncheck when the row is selected and the checkbox is clicked', () => { - const wrapper = shallow( - - ); - - wrapper.find('.tour-dataview-add-to-cart').prop('onClick')( - new MouseEvent('click') + it('calls onUncheck when the row is selected and the checkbox is clicked', async () => { + render(); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 2' }) ); expect(onUncheck).toHaveBeenCalledWith([3]); }); - - it('calls onCheck when the row is selected via shift-click and the checkbox is clicked', () => { - const wrapper = shallow( - - ); - - wrapper.find('.tour-dataview-add-to-cart').prop('onClick')( - new MouseEvent('click', { shiftKey: true }) + it('calls onCheck when the row is selected via shift-click and the checkbox is clicked', async () => { + render(); + fireEvent.click( + await screen.findByRole('checkbox', { + name: 'select row 2', + }), + { shiftKey: true } ); expect(onCheck).toHaveBeenCalledWith([1, 2, 3]); }); - - it('calls onUncheck when the row is unselected via shift-click and the checkbox is clicked', () => { - const wrapper = shallow( + it('calls onUncheck when the row is unselected via shift-click and the checkbox is clicked', async () => { + render( ); - - wrapper.find('.tour-dataview-add-to-cart').prop('onClick')( - new MouseEvent('click', { shiftKey: true }) + fireEvent.click( + await screen.findByRole('checkbox', { + name: 'select row 2', + }), + { shiftKey: true } ); expect(onUncheck).toHaveBeenCalledWith([1, 2, 3]); }); diff --git a/packages/datagateway-common/src/table/columnFilters/__snapshots__/dateColumnFilter.component.test.tsx.snap b/packages/datagateway-common/src/table/columnFilters/__snapshots__/dateColumnFilter.component.test.tsx.snap index 6cdf309c3..10e416a6e 100644 --- a/packages/datagateway-common/src/table/columnFilters/__snapshots__/dateColumnFilter.component.test.tsx.snap +++ b/packages/datagateway-common/src/table/columnFilters/__snapshots__/dateColumnFilter.component.test.tsx.snap @@ -1,303 +1,199 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Date filter component CustomClearButton renders correctly 1`] = ` - - Clear - -`; - -exports[`Date filter component DatePicker functionality useTextFilter hook returns a function which can generate a working text filter 1`] = ` -
    - + + `; -exports[`Date filter component DateTimePicker functionality useTextFilter hook returns a function which can generate a working text filter 1`] = ` -
    - -
    +
    + `; exports[`Text filter component usePrincipalExperimenterFilter hook returns a function which can generate a working PI filter 1`] = ` -
    - - +
    +
    - Include - - + Include + + + + +
    +
    +
    +
    +
    +
    `; exports[`Text filter component useTextFilter hook returns a function which can generate a working text filter 1`] = ` -
    - - +
    +
    - Include - - + Include + + + + +
    +
    +
    +
    +
    + `; diff --git a/packages/datagateway-common/src/table/columnFilters/dateColumnFilter.component.test.tsx b/packages/datagateway-common/src/table/columnFilters/dateColumnFilter.component.test.tsx index 258483414..6b2d55bb3 100644 --- a/packages/datagateway-common/src/table/columnFilters/dateColumnFilter.component.test.tsx +++ b/packages/datagateway-common/src/table/columnFilters/dateColumnFilter.component.test.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { shallow, mount } from 'enzyme'; +import * as React from 'react'; import DateColumnFilter, { CustomClearButton, datesEqual, @@ -14,6 +13,9 @@ import { cleanupDatePickerWorkaround, } from '../../setupTests'; import { PickersActionBarProps } from '@mui/x-date-pickers/PickersActionBar'; +import { render, screen } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('../../api'); @@ -28,7 +30,7 @@ describe('Date filter component', () => { }); it('renders correctly', () => { - const wrapper = shallow( + const { asFragment } = render( { onChange={jest.fn()} /> ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); describe('datesEqual function', () => { @@ -211,28 +213,35 @@ describe('Date filter component', () => { describe('CustomClearButton', () => { const onClear = jest.fn(); let props: PickersActionBarProps; + let user: UserEvent; beforeEach(() => { + user = userEvent.setup(); props = { onClear: onClear, }; }); it('renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('calls onClear when button clicked', () => { - const wrapper = shallow(); - const button = wrapper.find('[role="button"]'); - button.simulate('click'); + it('calls onClear when button clicked', async () => { + render(); + await user.click(await screen.findByRole('button')); expect(onClear).toHaveBeenCalled(); }); }); describe('DatePicker functionality', () => { - it('calls the onChange method correctly when filling out the date inputs', () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + + it('calls the onChange method correctly when filling out the date inputs', async () => { const onChange = jest.fn(); const baseProps = { @@ -240,53 +249,67 @@ describe('Date filter component', () => { onChange, }; - const wrapper = mount(); + const { rerender } = render(); - const startDateFilterInput = wrapper.find('input').first(); - startDateFilterInput.instance().value = '2019-08-06'; - startDateFilterInput.simulate('change'); + const startDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter from', + }); + await user.type(startDateFilterInput, '2019-08-06'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06', }); - wrapper.setProps({ ...baseProps, value: { startDate: '2019-08-06' } }); - const endDateFilterInput = wrapper.find('input').last(); - endDateFilterInput.instance().value = '2019-08-06'; - endDateFilterInput.simulate('change'); + rerender( + + ); + + const endDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter to', + }); + await user.type(endDateFilterInput, '2019-08-06'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06', endDate: '2019-08-06', }); - wrapper.setProps({ - ...baseProps, - value: { - startDate: '2019-08-06', - endDate: '2019-08-06', - }, - }); - startDateFilterInput.instance().value = ''; - startDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(startDateFilterInput); expect(onChange).toHaveBeenLastCalledWith({ endDate: '2019-08-06', }); - wrapper.setProps({ - ...baseProps, - value: { - endDate: '2019-08-06', - }, - }); - endDateFilterInput.instance().value = ''; - endDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(endDateFilterInput); expect(onChange).toHaveBeenLastCalledWith(null); }); - it('handles invalid date values correctly by not calling onChange, unless there was previously a value there', () => { + it('handles invalid date values correctly by not calling onChange, unless there was previously a value there', async () => { const onChange = jest.fn(); const baseProps = { @@ -294,63 +317,74 @@ describe('Date filter component', () => { onChange, }; - const wrapper = mount(); + const { rerender } = render(); - const startDateFilterInput = wrapper.find('input').first(); - startDateFilterInput.instance().value = '2'; - startDateFilterInput.simulate('change'); + const startDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter from', + }); + await user.type(startDateFilterInput, '2'); expect(onChange).not.toHaveBeenCalled(); - const endDateFilterInput = wrapper.find('input').last(); - endDateFilterInput.instance().value = '201'; - endDateFilterInput.simulate('change'); + const endDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter to', + }); + await user.type(endDateFilterInput, '201'); expect(onChange).not.toHaveBeenCalled(); - startDateFilterInput.instance().value = '2019-08-06'; - startDateFilterInput.simulate('change'); + await user.clear(startDateFilterInput); + await user.type(startDateFilterInput, '2019-08-06'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06', }); - wrapper.setProps({ ...baseProps, value: { startDate: '2019-08-06' } }); - endDateFilterInput.instance().value = '2019-08-07'; - endDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(endDateFilterInput); + await user.type(endDateFilterInput, '2019-08-07'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06', endDate: '2019-08-07', }); - wrapper.setProps({ - ...baseProps, - value: { - startDate: '2019-08-06', - endDate: '2019-08-07', - }, - }); - startDateFilterInput.instance().value = '2'; - startDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(startDateFilterInput); + await user.type(startDateFilterInput, '2'); expect(onChange).toHaveBeenLastCalledWith({ endDate: '2019-08-07', }); - wrapper.setProps({ - ...baseProps, - value: { - endDate: '2019-08-07', - }, - }); - endDateFilterInput.instance().value = '201'; - endDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(endDateFilterInput); + await user.type(endDateFilterInput, '201'); expect(onChange).toHaveBeenLastCalledWith(null); }); - it('displays error for invalid date', () => { + it('displays error for invalid date', async () => { const onChange = jest.fn(); const baseProps = { @@ -362,15 +396,17 @@ describe('Date filter component', () => { }, }; - const wrapper = mount(); + render(); - expect(wrapper.find('p.Mui-error')).toHaveLength(2); - expect(wrapper.find('p.Mui-error').first().text()).toEqual( + const errorMessages = await screen.findAllByText( 'Date format: yyyy-MM-dd.' ); + for (const element of errorMessages) { + expect(element).toBeInTheDocument(); + } }); - it('displays error for invalid date range', () => { + it('displays error for invalid date range', async () => { const onChange = jest.fn(); const baseProps = { @@ -382,15 +418,15 @@ describe('Date filter component', () => { }, }; - const wrapper = mount(); + render(); - expect(wrapper.find('p.Mui-error')).toHaveLength(2); - expect(wrapper.find('p.Mui-error').first().text()).toEqual( - 'Invalid date range' - ); + const errorMessages = await screen.findAllByText('Invalid date range'); + for (const element of errorMessages) { + expect(element).toBeInTheDocument(); + } }); - it('useTextFilter hook returns a function which can generate a working text filter', () => { + it('useDateFilter hook returns a function which can generate a working date filter', async () => { const pushFilter = jest.fn(); (usePushFilter as jest.Mock).mockImplementation(() => pushFilter); @@ -401,32 +437,29 @@ describe('Date filter component', () => { dateFilter = result.current('Start Date', 'startDate'); }); - const shallowWrapper = shallow(dateFilter); - expect(shallowWrapper).toMatchSnapshot(); + const { asFragment } = render(dateFilter); + expect(asFragment()).toMatchSnapshot(); - const mountWrapper = mount(dateFilter); - const startDateFilterInput = mountWrapper.find('input').first(); - startDateFilterInput.instance().value = '2021-08-09'; - startDateFilterInput.simulate('change'); + const startDateFilterInput = await screen.findByRole('textbox', { + name: 'Start Date filter from', + }); + + await user.type(startDateFilterInput, '2021-08-09'); expect(pushFilter).toHaveBeenLastCalledWith('startDate', { startDate: '2021-08-09', }); - - mountWrapper.setProps({ - ...mountWrapper.props(), - value: { startDate: '2021-08-09' }, - }); - startDateFilterInput.instance().value = ''; - startDateFilterInput.simulate('change'); - - expect(pushFilter).toHaveBeenCalledTimes(2); - expect(pushFilter).toHaveBeenLastCalledWith('startDate', null); }); }); describe('DateTimePicker functionality', () => { - it('calls the onChange method correctly when filling out the date-time inputs', () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + + it('calls the onChange method correctly when filling out the date-time inputs', async () => { const onChange = jest.fn(); const baseProps = { @@ -434,56 +467,72 @@ describe('Date filter component', () => { onChange, }; - const wrapper = mount(); + const { rerender } = render( + + ); + + const startDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter from', + }); - const startDateFilterInput = wrapper.find('input').first(); - startDateFilterInput.instance().value = '2019-08-06 00:00:00'; - startDateFilterInput.simulate('change'); + await user.type(startDateFilterInput, '2019-08-06 00:00:00'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06 00:00:00', }); - wrapper.setProps({ - ...baseProps, - value: { startDate: '2019-08-06 00:00:00' }, + rerender( + + ); + + const endDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter to', }); - const endDateFilterInput = wrapper.find('input').last(); - endDateFilterInput.instance().value = '2019-08-06 23:59:00'; - endDateFilterInput.simulate('change'); + + await user.type(endDateFilterInput, '2019-08-06 23:59:00'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06 00:00:00', endDate: '2019-08-06 23:59:00', }); - wrapper.setProps({ - ...baseProps, - value: { - startDate: '2019-08-06 00:00:00', - endDate: '2019-08-06 23:59:00', - }, - }); - startDateFilterInput.instance().value = ''; - startDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(startDateFilterInput); expect(onChange).toHaveBeenLastCalledWith({ endDate: '2019-08-06 23:59:00', }); - wrapper.setProps({ - ...baseProps, - value: { - endDate: '2019-08-06 23:59:00', - }, - }); - endDateFilterInput.instance().value = ''; - endDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(endDateFilterInput); expect(onChange).toHaveBeenLastCalledWith(null); }); - it('handles invalid date values correctly by not calling onChange, unless there was previously a value there', () => { + it('handles invalid date values correctly by not calling onChange, unless there was previously a value there', async () => { const onChange = jest.fn(); const baseProps = { @@ -491,66 +540,84 @@ describe('Date filter component', () => { onChange, }; - const wrapper = mount(); + const { rerender } = render( + + ); + + const startDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter from', + }); - const startDateFilterInput = wrapper.find('input').first(); - startDateFilterInput.instance().value = '2'; - startDateFilterInput.simulate('change'); + await user.type(startDateFilterInput, '2'); expect(onChange).not.toHaveBeenCalled(); - const endDateFilterInput = wrapper.find('input').last(); - endDateFilterInput.instance().value = '201'; - endDateFilterInput.simulate('change'); + const endDateFilterInput = await screen.findByRole('textbox', { + name: 'test filter to', + }); + + await user.type(endDateFilterInput, '201'); expect(onChange).not.toHaveBeenCalled(); - startDateFilterInput.instance().value = '2019-08-06 00:00:00'; - startDateFilterInput.simulate('change'); + await user.clear(startDateFilterInput); + await user.type(startDateFilterInput, '2019-08-06 00:00:00'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06 00:00:00', }); - wrapper.setProps({ - ...baseProps, - value: { startDate: '2019-08-06 00:00:00' }, - }); - endDateFilterInput.instance().value = '2019-08-07 00:00:00'; - endDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(endDateFilterInput); + await user.type(endDateFilterInput, '2019-08-07 00:00:00'); expect(onChange).toHaveBeenLastCalledWith({ startDate: '2019-08-06 00:00:00', endDate: '2019-08-07 00:00:00', }); - wrapper.setProps({ - ...baseProps, - value: { - startDate: '2019-08-06 00:00:00', - endDate: '2019-08-07 00:00:00', - }, - }); - startDateFilterInput.instance().value = '2'; - startDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(startDateFilterInput); + await user.type(startDateFilterInput, '2'); expect(onChange).toHaveBeenLastCalledWith({ endDate: '2019-08-07 00:00:00', }); - wrapper.setProps({ - ...baseProps, - value: { - endDate: '2019-08-07 00:00:00', - }, - }); - endDateFilterInput.instance().value = '201'; - endDateFilterInput.simulate('change'); + rerender( + + ); + + await user.clear(endDateFilterInput); + await user.type(endDateFilterInput, '201'); expect(onChange).toHaveBeenLastCalledWith(null); }); - it('displays error for invalid date', () => { + it('displays error for invalid date', async () => { const onChange = jest.fn(); const baseProps = { @@ -562,15 +629,17 @@ describe('Date filter component', () => { }, }; - const wrapper = mount(); + render(); - expect(wrapper.find('p.Mui-error')).toHaveLength(2); - expect(wrapper.find('p.Mui-error').first().text()).toEqual( + const errorMessages = await screen.findAllByText( 'Date-time format: yyyy-MM-dd HH:mm:ss.' ); + for (const element of errorMessages) { + expect(element).toBeInTheDocument(); + } }); - it('displays error for invalid time', () => { + it('displays error for invalid time', async () => { const onChange = jest.fn(); const baseProps = { @@ -582,15 +651,17 @@ describe('Date filter component', () => { }, }; - const wrapper = mount(); + render(); - expect(wrapper.find('p.Mui-error')).toHaveLength(2); - expect(wrapper.find('p.Mui-error').first().text()).toEqual( + const errorMessages = await screen.findAllByText( 'Date-time format: yyyy-MM-dd HH:mm:ss.' ); + for (const element of errorMessages) { + expect(element).toBeInTheDocument(); + } }); - it('displays error for invalid date-time range', () => { + it('displays error for invalid date-time range', async () => { const onChange = jest.fn(); const baseProps = { @@ -602,48 +673,14 @@ describe('Date filter component', () => { }, }; - const wrapper = mount(); + render(); - expect(wrapper.find('p.Mui-error')).toHaveLength(2); - expect(wrapper.find('p.Mui-error').first().text()).toEqual( + const errorMessages = await screen.findAllByText( 'Invalid date-time range' ); - }); - - // I don't believe this works with date+time due to time values not appearing in URL params - // TODO remove this? - it.skip('useTextFilter hook returns a function which can generate a working text filter', () => { - const pushFilter = jest.fn(); - (usePushFilter as jest.Mock).mockImplementation(() => pushFilter); - - const { result } = renderHook(() => useDateFilter({})); - let dateFilter; - - act(() => { - dateFilter = result.current('Start Date', 'startDate'); - }); - - const shallowWrapper = shallow(dateFilter); - expect(shallowWrapper).toMatchSnapshot(); - - const mountWrapper = mount(dateFilter); - const startDateFilterInput = mountWrapper.find('input').first(); - startDateFilterInput.instance().value = '2021-08-09 00:00:00'; - startDateFilterInput.simulate('change'); - - expect(pushFilter).toHaveBeenLastCalledWith('startDate', { - startDate: '2021-08-09 00:00:00', - }); - - mountWrapper.setProps({ - ...mountWrapper.props(), - value: { startDate: '2021-08-09 00:00:00' }, - }); - startDateFilterInput.instance().value = ''; - startDateFilterInput.simulate('change'); - - expect(pushFilter).toHaveBeenCalledTimes(2); - expect(pushFilter).toHaveBeenLastCalledWith('startDate', null); + for (const element of errorMessages) { + expect(element).toBeInTheDocument(); + } }); }); }); diff --git a/packages/datagateway-common/src/table/columnFilters/textColumnFilter.component.test.tsx b/packages/datagateway-common/src/table/columnFilters/textColumnFilter.component.test.tsx index 7396dba1c..a5108a4a5 100644 --- a/packages/datagateway-common/src/table/columnFilters/textColumnFilter.component.test.tsx +++ b/packages/datagateway-common/src/table/columnFilters/textColumnFilter.component.test.tsx @@ -1,34 +1,41 @@ import React from 'react'; -import { shallow, mount } from 'enzyme'; import TextColumnFilter, { usePrincipalExperimenterFilter, useTextFilter, } from './textColumnFilter.component'; -import { Select } from '@mui/material'; import { act } from 'react-dom/test-utils'; import { usePushFilter, usePushFilters } from '../../api'; +import { render, screen } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('../../api'); jest.useFakeTimers('modern'); const DEBOUNCE_DELAY = 250; describe('Text filter component', () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup({ delay: null }); + }); + it('renders correctly', () => { - const wrapper = shallow( + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('calls the onChange method once when input is typed while include filter type is selected and calls again by debounced function after timeout', () => { + it('calls the onChange method once when input is typed while include filter type is selected and calls again by debounced function after timeout', async () => { const onChange = jest.fn(); - const wrapper = mount( + render( { ); // We simulate a change in the input from 'test' to 'test-again'. - const textFilterInput = wrapper.find('input').first(); + const textFilterInput = await screen.findByRole('textbox', { + name: 'Filter by test', + hidden: true, + }); - textFilterInput.instance().value = 'test-again'; - textFilterInput.simulate('change'); + await user.clear(textFilterInput); + await user.type(textFilterInput, 'test-again'); jest.advanceTimersByTime(DEBOUNCE_DELAY); @@ -51,10 +61,10 @@ describe('Text filter component', () => { }); }); - it('calls the onChange method once when input is typed while exclude filter type is selected and calls again by debounced function after timeout', () => { + it('calls the onChange method once when input is typed while exclude filter type is selected and calls again by debounced function after timeout', async () => { const onChange = jest.fn(); - const wrapper = mount( + render( { ); // We simulate a change in the input from 'test' to 'test-again'. - const textFilterInput = wrapper.find('input').first(); + const textFilterInput = await screen.findByRole('textbox', { + name: 'Filter by test', + hidden: true, + }); - textFilterInput.instance().value = 'test-again'; - textFilterInput.simulate('change'); + await user.clear(textFilterInput); + await user.type(textFilterInput, 'test-again'); jest.advanceTimersByTime(DEBOUNCE_DELAY); @@ -77,10 +90,10 @@ describe('Text filter component', () => { }); }); - it('calls the onChange method once when include filter type is selected while there is input', () => { + it('calls the onChange method once when include filter type is selected while there is input', async () => { const onChange = jest.fn(); - const wrapper = mount( + render( { ); // We simulate a change in the select from 'exclude' to 'include'. - const textFilterSelect = wrapper.find(Select).at(0); - - act(() => { - textFilterSelect.props().onChange({ target: { value: 'include' } }); - }); + await user.click( + await screen.findByRole('button', { + name: 'include or exclude', + hidden: true, + }) + ); + await user.click(await screen.findByText('Include')); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenLastCalledWith({ @@ -102,10 +117,10 @@ describe('Text filter component', () => { }); }); - it('calls the onChange method once when exclude filter type is selected while there is input', () => { + it('calls the onChange method once when exclude filter type is selected while there is input', async () => { const onChange = jest.fn(); - const wrapper = mount( + render( { ); // We simulate a change in the select from 'include' to 'exclude'. - const textFilterSelect = wrapper.find(Select).at(0); - - act(() => { - textFilterSelect.props().onChange({ target: { value: 'exclude' } }); - }); + await user.click( + await screen.findByRole('button', { + name: 'include or exclude', + hidden: true, + }) + ); + await user.click(await screen.findByText('Exclude')); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenLastCalledWith({ @@ -127,10 +144,10 @@ describe('Text filter component', () => { }); }); - it('calls the onChange method once when input is cleared and calls again by debounced function after timeout', () => { + it('calls the onChange method once when input is cleared and calls again by debounced function after timeout', async () => { const onChange = jest.fn(); - const wrapper = mount( + render( { ); // We simulate a change in the input from 'test' to ''. - const textFilterInput = wrapper.find('input').first(); - - textFilterInput.instance().value = ''; - textFilterInput.simulate('change'); + await user.clear( + await screen.findByRole('textbox', { + name: 'Filter by test', + hidden: true, + }) + ); jest.advanceTimersByTime(DEBOUNCE_DELAY); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenLastCalledWith(null); }); - it('does not call the onChange method when filter type is selected while input is empty', () => { + it('does not call the onChange method when filter type is selected while input is empty', async () => { const onChange = jest.fn(); - const wrapper = mount( + render( { ); // We simulate a change in the select from 'exclude' to 'include'. - const textFilterSelect = wrapper.find(Select).at(0); - - act(() => { - textFilterSelect.props().onChange({ target: { value: 'include' } }); - }); + await user.click( + await screen.findByRole('button', { + name: 'include or exclude', + hidden: true, + }) + ); + await user.click(await screen.findByText('Include')); expect(onChange).toHaveBeenCalledTimes(0); }); - it('updates the input value when the value prop changes', () => { + it('updates the input value when the value prop changes', async () => { const baseProps = { label: 'test', onChange: jest.fn(), value: undefined }; - const wrapper = mount(); - - wrapper.setProps({ - ...baseProps, - value: { value: 'changed via props', type: 'include' }, - }); - wrapper.update(); + const { rerender } = render(); - expect(wrapper.find('input#test-filter').prop('value')).toEqual( - 'changed via props' + rerender( + ); - wrapper.setProps({ - ...baseProps, - value: undefined, - }); - wrapper.update(); + expect( + await screen.findByDisplayValue('changed via props') + ).toBeInTheDocument(); - expect(wrapper.find('input#test-filter').prop('value')).toEqual(''); + rerender(); + + expect(await screen.findByDisplayValue('')).toBeInTheDocument(); }); - it('useTextFilter hook returns a function which can generate a working text filter', () => { + it('useTextFilter hook returns a function which can generate a working text filter', async () => { const pushFilter = jest.fn(); (usePushFilter as jest.Mock).mockImplementation(() => pushFilter); @@ -205,15 +223,17 @@ describe('Text filter component', () => { textFilter = result.current('Name', 'name'); }); - const shallowWrapper = shallow(textFilter); - expect(shallowWrapper).toMatchSnapshot(); + const { asFragment } = render(textFilter); + expect(asFragment()).toMatchSnapshot(); - const mountWrapper = mount(textFilter); // We simulate a change in the input to 'test'. - const textFilterInput = mountWrapper.find('input').first(); + const textFilterInput = await screen.findByRole('textbox', { + name: 'Filter by Name', + hidden: true, + }); - textFilterInput.instance().value = 'test'; - textFilterInput.simulate('change'); + await user.clear(textFilterInput); + await user.type(textFilterInput, 'test'); jest.advanceTimersByTime(DEBOUNCE_DELAY); @@ -223,8 +243,7 @@ describe('Text filter component', () => { type: 'include', }); - textFilterInput.instance().value = ''; - textFilterInput.simulate('change'); + await user.clear(textFilterInput); jest.advanceTimersByTime(DEBOUNCE_DELAY); @@ -232,7 +251,7 @@ describe('Text filter component', () => { expect(pushFilter).toHaveBeenLastCalledWith('name', null); }); - it('usePrincipalExperimenterFilter hook returns a function which can generate a working PI filter', () => { + it('usePrincipalExperimenterFilter hook returns a function which can generate a working PI filter', async () => { const pushFilters = jest.fn(); (usePushFilters as jest.Mock).mockImplementation(() => pushFilters); @@ -246,15 +265,17 @@ describe('Text filter component', () => { ); }); - const shallowWrapper = shallow(piFilter); - expect(shallowWrapper).toMatchSnapshot(); + const { asFragment } = render(piFilter); + expect(asFragment()).toMatchSnapshot(); - const mountWrapper = mount(piFilter); // We simulate a change in the input to 'test'. - const textFilterInput = mountWrapper.find('input').first(); + const textFilterInput = await screen.findByRole('textbox', { + name: 'Filter by Principal Investigator', + hidden: true, + }); - textFilterInput.instance().value = 'test'; - textFilterInput.simulate('change'); + await user.clear(textFilterInput); + await user.type(textFilterInput, 'test'); jest.advanceTimersByTime(DEBOUNCE_DELAY); @@ -276,8 +297,7 @@ describe('Text filter component', () => { }, ]); - textFilterInput.instance().value = ''; - textFilterInput.simulate('change'); + await user.clear(textFilterInput); jest.advanceTimersByTime(DEBOUNCE_DELAY); diff --git a/packages/datagateway-common/src/table/headerRenderers/__snapshots__/dataHeader.component.test.tsx.snap b/packages/datagateway-common/src/table/headerRenderers/__snapshots__/dataHeader.component.test.tsx.snap index 95940f92a..8f2700dd8 100644 --- a/packages/datagateway-common/src/table/headerRenderers/__snapshots__/dataHeader.component.test.tsx.snap +++ b/packages/datagateway-common/src/table/headerRenderers/__snapshots__/dataHeader.component.test.tsx.snap @@ -1,373 +1,335 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Data column header component renders correctly with filter but no sort 1`] = ` - +
    - - - - - - - Test - - - - -
    - +
    + Test +
    +
    +
    +

    + Test +

    +
    +
    +
    +
    + + +
    +
    +
    -
    - - +
    +
    `; exports[`Data column header component renders correctly with sort and filter 1`] = ` - +
    - - - - - - - +
    Test - - - - - -
    - +
    +
    +
    + +

    + Test +

    + +
    +
    +
    +
    +
    + + +
    +
    +
    -
    - - +
    + `; exports[`Data column header component renders correctly with sort but no filter 1`] = ` - +
    - - - - - - - +
    Test - - - - -
    - +
    +
    +
    + +

    + Test +

    + +
    +
    +
    +
    -
    - - +
    +
    `; exports[`Data column header component renders correctly without sort or filter 1`] = ` - +
    - - - - - - - Test - - - -
    - +
    + Test +
    +
    +
    +

    + Test +

    +
    +
    +
    -
    - - +
    +
    `; diff --git a/packages/datagateway-common/src/table/headerRenderers/__snapshots__/selectHeader.component.test.tsx.snap b/packages/datagateway-common/src/table/headerRenderers/__snapshots__/selectHeader.component.test.tsx.snap index 4a0ad164c..3158ae8b4 100644 --- a/packages/datagateway-common/src/table/headerRenderers/__snapshots__/selectHeader.component.test.tsx.snap +++ b/packages/datagateway-common/src/table/headerRenderers/__snapshots__/selectHeader.component.test.tsx.snap @@ -1,322 +1,242 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Select column header component renders correctly when checked 1`] = ` - - +
    - - } - disabled={false} - icon={ - - } - indeterminate={false} - indeterminateIcon={ - - } - inputProps={ - Object { - "aria-label": "select all rows", - } - } - onClick={[Function]} - size="small" - sx={ - Object { - "height": 20, - "padding": "inherit", - } - } - /> + + + + + - - +
    + `; exports[`Select column header component renders correctly when indeterminate 1`] = ` - - +
    - - } - disabled={false} - icon={ - - } - indeterminate={true} - indeterminateIcon={ - - } - inputProps={ - Object { - "aria-label": "select all rows", - } - } - onClick={[Function]} - size="small" - sx={ - Object { - "height": 20, - "padding": "inherit", - } - } - /> + + + + + - - +
    + `; exports[`Select column header component renders correctly when loading is true 1`] = ` - - +
    - - } - disabled={true} - icon={ - - } - indeterminateIcon={ - - } - inputProps={ - Object { - "aria-label": "select all rows", - } - } - onClick={[Function]} - size="small" - sx={ - Object { - "height": 20, - "padding": "inherit", - } - } - /> + + + + - - +
    + `; exports[`Select column header component renders correctly when selectedRows is undefined 1`] = ` - - +
    - - } - disabled={true} - icon={ - - } - indeterminateIcon={ - - } - inputProps={ - Object { - "aria-label": "select all rows", - } - } - onClick={[Function]} - size="small" - sx={ - Object { - "height": 20, - "padding": "inherit", - } - } - /> + + + + - - +
    + `; exports[`Select column header component renders correctly when selectedRows parentSelected is true 1`] = ` - - +
    - - - } - className="tour-dataview-add-to-cart" - disabled={true} - icon={ - - } - inputProps={ - Object { - "aria-label": "select row undefined", - } - } - onClick={[Function]} - size="small" - style={ - Object { - "padding": "inherit", - } - } - /> + + + + + - - +
    + `; exports[`Select column header component renders correctly when unchecked 1`] = ` - - +
    - - } - disabled={false} - icon={ - - } - indeterminate={false} - indeterminateIcon={ - - } - inputProps={ - Object { - "aria-label": "select all rows", - } - } - onClick={[Function]} - size="small" - sx={ - Object { - "height": 20, - "padding": "inherit", - } - } - /> + + + + + - - +
    + `; diff --git a/packages/datagateway-common/src/table/headerRenderers/dataHeader.component.test.tsx b/packages/datagateway-common/src/table/headerRenderers/dataHeader.component.test.tsx index 1643f83f6..96dfe5a5f 100644 --- a/packages/datagateway-common/src/table/headerRenderers/dataHeader.component.test.tsx +++ b/packages/datagateway-common/src/table/headerRenderers/dataHeader.component.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import * as React from 'react'; import DataHeader from './dataHeader.component'; import TextColumnFilter from '../columnFilters/textColumnFilter.component'; -import { TableSortLabel } from '@mui/material'; describe('Data column header component', () => { + let user: UserEvent; const onSort = jest.fn(); const resizeColumn = jest.fn(); const dataHeaderProps = { @@ -26,99 +28,94 @@ describe('Data column header component', () => { ); + beforeEach(() => { + user = userEvent.setup(); + }); + afterEach(() => { onSort.mockClear(); resizeColumn.mockClear(); }); - it('renders correctly without sort or filter', () => { - const wrapper = shallow( + it('renders correctly without sort or filter', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly with sort but no filter', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders correctly with sort but no filter', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly with filter but no sort', () => { - const wrapper = shallow( + it('renders correctly with filter but no sort', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly with sort and filter', () => { - const wrapper = shallow( + it('renders correctly with sort and filter', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); describe('calls the onSort method when label is clicked', () => { - it('sets asc order', () => { - const wrapper = shallow(); - - const label = wrapper.find(TableSortLabel); - - label.simulate('click'); + it('sets asc order', async () => { + render(); + await user.click(await screen.findByRole('button', { name: 'Test' })); expect(onSort).toHaveBeenCalledWith('test', 'asc', 'push'); }); - it('sets desc order', () => { - const wrapper = shallow( - + it('sets desc order', async () => { + render( + ); - - const label = wrapper.find(TableSortLabel); - - label.simulate('click'); + await user.click(await screen.findByRole('button', { name: 'Test' })); expect(onSort).toHaveBeenCalledWith('test', 'desc', 'push'); }); - it('sets null order', () => { - const wrapper = shallow( - + it('sets null order', async () => { + render( + ); - - const label = wrapper.find(TableSortLabel); - - label.simulate('click'); + await user.click(await screen.findByRole('button', { name: 'Test' })); expect(onSort).toHaveBeenCalledWith('test', null, 'push'); }); }); describe('calls the onSort method when default sort is specified', () => { it('sets asc order', () => { - const wrapper = mount( - - ); - wrapper.update(); + render(); expect(onSort).toHaveBeenCalledWith('test', 'asc', 'replace'); }); - it('sets desc order', () => { - const wrapper = mount( - - ); - wrapper.update(); - + render(); expect(onSort).toHaveBeenCalledWith('test', 'desc', 'replace'); }); }); - it('calls the resizeColumn method when column resizer is dragged', () => { - const wrapper = shallow(); - - wrapper.find('Draggable').prop('onDrag')(null, { deltaX: 50 }); - - expect(resizeColumn).toHaveBeenCalledWith('test', 50); + it.skip('calls the resizeColumn method when column resizer is dragged', async () => { + // TODO: I think testing-library doesn't support dragging interaction at the moment + // the drag example code here only works with ar eal browser: + // https://testing-library.com/docs/example-drag/ }); }); diff --git a/packages/datagateway-common/src/table/headerRenderers/selectHeader.component.test.tsx b/packages/datagateway-common/src/table/headerRenderers/selectHeader.component.test.tsx index 581c01bcc..495641f12 100644 --- a/packages/datagateway-common/src/table/headerRenderers/selectHeader.component.test.tsx +++ b/packages/datagateway-common/src/table/headerRenderers/selectHeader.component.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import * as React from 'react'; import SelectHeader from './selectHeader.component'; import SelectCell from '../cellRenderers/selectCell.component'; describe('Select column header component', () => { + let user: UserEvent; const setLastChecked = jest.fn(); const onCheck = jest.fn(); const onUncheck = jest.fn(); @@ -17,75 +20,73 @@ describe('Select column header component', () => { loading: false, }; + beforeEach(() => { + user = userEvent.setup(); + }); + afterEach(() => { setLastChecked.mockClear(); onCheck.mockClear(); onUncheck.mockClear(); }); - it('renders correctly when unchecked', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders correctly when unchecked', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly when indeterminate', () => { - const wrapper = shallow( + it('renders correctly when indeterminate', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly when checked', () => { - const wrapper = shallow( + it('renders correctly when checked', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly when selectedRows is undefined', () => { - const wrapper = shallow( + it('renders correctly when selectedRows is undefined', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('renders correctly when loading is true', () => { - const wrapper = shallow( + it('renders correctly when loading is true', async () => { + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); it('renders correctly when selectedRows parentSelected is true', () => { - const wrapper = shallow( + const { asFragment } = render( ); - expect(wrapper).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); - it('calls onCheck when not all rows are selected and the checkbox is clicked', () => { - const wrapper = shallow( - - ); - - wrapper.childAt(0).childAt(0).childAt(0).prop('onClick')(); + it('calls onCheck when not all rows are selected and the checkbox is clicked', async () => { + render(); + await user.click(await screen.findByRole('checkbox')); expect(onCheck).toHaveBeenCalledWith([1, 2, 3]); }); - it('calls onUncheck when all rows are selected and the checkbox is clicked', () => { - const wrapper = shallow( - - ); - - wrapper.childAt(0).childAt(0).childAt(0).prop('onClick')(); + it('calls onUncheck when all rows are selected and the checkbox is clicked', async () => { + render(); + await user.click(await screen.findByRole('checkbox')); expect(onUncheck).toHaveBeenCalledWith([1, 2, 3]); }); }); diff --git a/packages/datagateway-common/src/table/rowRenderers/__snapshots__/detailsPanelRow.component.test.tsx.snap b/packages/datagateway-common/src/table/rowRenderers/__snapshots__/detailsPanelRow.component.test.tsx.snap index 44aa33ac5..0d16e8ae4 100644 --- a/packages/datagateway-common/src/table/rowRenderers/__snapshots__/detailsPanelRow.component.test.tsx.snap +++ b/packages/datagateway-common/src/table/rowRenderers/__snapshots__/detailsPanelRow.component.test.tsx.snap @@ -1,43 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Details panel row component renders correctly 1`] = ` - +
    -
    - +
    +
    + Details panel using test +
    +
    - + `; diff --git a/packages/datagateway-common/src/table/rowRenderers/detailsPanelRow.component.test.tsx b/packages/datagateway-common/src/table/rowRenderers/detailsPanelRow.component.test.tsx index 5617f3142..ff90b6146 100644 --- a/packages/datagateway-common/src/table/rowRenderers/detailsPanelRow.component.test.tsx +++ b/packages/datagateway-common/src/table/rowRenderers/detailsPanelRow.component.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import * as React from 'react'; import DetailsPanelRow from './detailsPanelRow.component'; import { DetailsPanelProps } from '../table.component'; +import { render } from '@testing-library/react'; describe('Details panel row component', () => { const detailsPanelRowProps = { @@ -22,7 +22,9 @@ describe('Details panel row component', () => { }; it('renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { asFragment } = render( + + ); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-common/src/table/table.component.test.tsx b/packages/datagateway-common/src/table/table.component.test.tsx index 08d445406..527561ffe 100644 --- a/packages/datagateway-common/src/table/table.component.test.tsx +++ b/packages/datagateway-common/src/table/table.component.test.tsx @@ -1,13 +1,15 @@ -import React from 'react'; -import { mount } from 'enzyme'; +import * as React from 'react'; import Table, { ColumnType } from './table.component'; import { formatBytes } from './cellRenderers/cellContentRenderers'; import { TableCellProps } from 'react-virtualized'; import TextColumnFilter from './columnFilters/textColumnFilter.component'; -import SelectHeader from './headerRenderers/selectHeader.component'; -import ReactTestUtils from 'react-dom/test-utils'; +import { render, screen, waitFor, within } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; describe('Table component', () => { + let user: UserEvent; + const onSort = jest.fn(); const tableProps = { @@ -49,75 +51,31 @@ describe('Table component', () => { ] as ColumnType[], }; + beforeEach(() => { + user = userEvent.setup(); + }); + afterEach(() => { onSort.mockClear(); }); - it('renders data columns correctly', () => { - const wrapper = mount(); - - expect(wrapper.exists('[aria-colcount=2]')).toBe(true); + it('renders data columns correctly', async () => { + render(
    ); - expect( - wrapper - .find('[role="columnheader"]') - .at(0) - .children() - .find('label') - .first() - .text() - // Check InputLabel is rendering as expected - ).toEqual('Include'); - - expect( - wrapper - .find('[role="columnheader"]') - .at(0) - .children() - .find('div') - .first() - .text() - // Empty Selects (like the one in textColumnFilter) render a zero width space character - // InputLabel for textColumnFilter will render, default to 'Include' - ).toEqual('Test 1Include\u200B'); + // expect "Test 1" and "Test 2" column to exist + const columnHeaders = await screen.findAllByRole('columnheader'); + expect(within(columnHeaders[0]).getByText('Test 1')).toBeInTheDocument(); + expect(within(columnHeaders[1]).getByText('Test 2')).toBeInTheDocument(); - expect( - wrapper - .find('[role="columnheader"]') - .at(1) - .children() - .find('div') - .first() - .text() - ).toEqual('Test 2'); - - expect( - wrapper - .find('[role="row"]') - .find('[role="gridcell"]') - .first() - .find('span') - .text() - ).toEqual('test1'); - - expect( - wrapper - .find('[role="row"]') - .find('[role="gridcell"]') - .last() - .find('span') - .text() - ).toEqual('2 B'); + const rows = await screen.findAllByRole('row'); + const cellsInFirstRow = within(rows[1]).getAllByRole('gridcell'); + expect(within(cellsInFirstRow[0]).getByText('test1')).toBeInTheDocument(); + expect(within(cellsInFirstRow[1]).getByText('2 B')).toBeInTheDocument(); }); - it('calls onSort function when sort label clicked', () => { - const wrapper = mount(
    ); - - wrapper - .find('[role="columnheader"] span[role="button"]') - .first() - .simulate('click'); - + it('calls onSort function when sort label clicked', async () => { + render(
    ); + await user.click(await screen.findByText('Test 1')); expect(onSort).toHaveBeenCalledWith('TEST1', 'asc', 'push'); }); @@ -129,15 +87,14 @@ describe('Table component', () => { { ...tableProps.columns[1], defaultSort: 'desc' }, ], }; - const wrapper = mount(
    ); - wrapper.update(); + render(
    ); expect(onSort).toHaveBeenCalledWith('TEST1', 'asc', 'replace'); expect(onSort).toHaveBeenCalledWith('TEST2', 'desc', 'replace'); }); - it('renders select column correctly, with both allIds defined and undefined', () => { - const wrapper = mount( + it('renders select column correctly', async () => { + render(
    { /> ); - expect(wrapper.exists('[aria-colcount=3]')).toBe(true); - expect(wrapper.exists('[aria-label="select all rows"]')).toBe(true); - expect(wrapper.find(SelectHeader).prop('allIds')).toEqual([1]); - - const wrapperAllIds = mount( -
    - ); - - expect(wrapperAllIds.exists('[aria-colcount=3]')).toBe(true); - expect(wrapperAllIds.exists('[aria-label="select all rows"]')).toBe(true); - expect(wrapperAllIds.find(SelectHeader).prop('allIds')).toEqual([ - 1, 2, 3, 4, - ]); + expect( + await screen.findByRole('checkbox', { name: 'select all rows' }) + ).toBeInTheDocument(); }); - it('resizes data columns correctly when a column is resized', () => { - const wrapper = mount(
    ); - - wrapper.update(); - - expect(wrapper.find('[role="columnheader"]').at(0).prop('style')).toEqual( - expect.objectContaining({ - flex: expect.stringContaining('1 1 512px'), - }) - ); - - ReactTestUtils.act(() => { - wrapper.find('DataHeader').at(0).prop('resizeColumn')( - wrapper.find('DataHeader').at(0).prop('dataKey'), - 50 - ); - }); - - wrapper.update(); - - expect(wrapper.find('[role="columnheader"]').at(0).prop('style')).toEqual( - expect.objectContaining({ - flex: expect.stringContaining('0 0 562px'), - }) - ); + it.skip('resizes data columns correctly when a column is resized', () => { + // TODO: I think testing-library doesn't support dragging interaction at the moment + // the drag example code here only works with ar eal browser: + // https://testing-library.com/docs/example-drag/ }); - it('resizes all data columns correctly when a column is resized and there are expand, select and action columns', () => { - const wrapper = mount( -
    Details panel; - }} - actions={[]} - selectedRows={[]} - onCheck={jest.fn()} - onUncheck={jest.fn()} - /> - ); - - wrapper.update(); - - expect(wrapper.find('[role="columnheader"]').at(2).prop('style')).toEqual( - expect.objectContaining({ - flex: expect.stringContaining('1 1 512px'), - }) - ); - - ReactTestUtils.act(() => { - wrapper.find('DataHeader').at(0).prop('resizeColumn')( - wrapper.find('DataHeader').at(0).prop('dataKey'), - 40 - ); - }); - - wrapper.update(); - - expect(wrapper.find('[role="columnheader"]').at(2).prop('style')).toEqual( - expect.objectContaining({ - flex: expect.stringContaining('0 0 552px'), - }) - ); + it.skip('resizes all data columns correctly when a column is resized and there are expand, select and action columns', () => { + // TODO: I think testing-library doesn't support dragging interaction at the moment + // the drag example code here only works with ar eal browser: + // https://testing-library.com/docs/example-drag/ }); - it('renders details column correctly', () => { - const wrapper = mount( + it('renders details column correctly', async () => { + render(
    { /> ); - expect(wrapper.exists('[aria-colcount=3]')).toBe(true); - expect(wrapper.exists('[aria-label="Show details"]')).toBe(true); + expect( + await screen.findByRole('button', { name: 'Show details' }) + ).toBeInTheDocument(); }); - it('renders detail panel when expand button is clicked and derenders when hide button is clicked', () => { - const wrapper = mount( + it('renders detail panel when expand button is clicked and derenders when hide button is clicked', async () => { + render(
    Details panel; + return
    Details panel
    ; }} /> ); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Show details' }) + ); - expect(wrapper.exists('#details-panel')).toBe(true); + expect(await screen.findByTestId('details-panel')).toBeInTheDocument(); - wrapper.find('[aria-label="Hide details"]').last().simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Hide details' }) + ); - expect(wrapper.exists('#details-panel')).toBe(false); + await waitFor(() => { + expect(screen.queryByRole('details-panel')).toBeNull(); + }); }); - it('renders actions column correctly', () => { - const wrapper = mount( + it('renders actions column correctly', async () => { + render(
    { /> ); - expect(wrapper.exists('[aria-colcount=3]')).toBe(true); + expect(await screen.findByText('Actions')).toBeInTheDocument(); expect( - wrapper.find('[role="columnheader"]').last().children().find('div').text() - ).toEqual('Actions'); - expect(wrapper.find('button').text()).toEqual('I am an action'); + await screen.findByRole('button', { name: 'I am an action' }) + ).toBeInTheDocument(); }); - it('renders correctly when no infinite loading properties are defined', () => { - const wrapper = mount( + it('renders correctly when no infinite loading properties are defined', async () => { + render(
    { /> ); - expect(wrapper.find('InfiniteLoader').prop('rowCount')).toBe( - tableProps.data.length - ); - expect(wrapper.find('InfiniteLoader').prop('loadMoreRows')).toBeInstanceOf( - Function - ); - expect(wrapper.find('InfiniteLoader').prop('loadMoreRows')()).resolves.toBe( - undefined - ); + expect(await screen.findByText('test1')).toBeInTheDocument(); }); it('throws error when only one of loadMoreRows or totalRowCount are defined', () => { @@ -317,13 +202,13 @@ describe('Table component', () => { }); expect(() => - mount(
    ) + render(
    ) ).toThrowError( 'Only one of loadMoreRows and totalRowCount was defined - either define both for infinite loading functionality or neither for no infinite loading' ); expect(() => - mount(
    ) + render(
    ) ).toThrowError( 'Only one of loadMoreRows and totalRowCount was defined - either define both for infinite loading functionality or neither for no infinite loading' ); diff --git a/packages/datagateway-common/src/views/__snapshots__/viewButton.component.test.tsx.snap b/packages/datagateway-common/src/views/__snapshots__/viewButton.component.test.tsx.snap deleted file mode 100644 index 13f01eb09..000000000 --- a/packages/datagateway-common/src/views/__snapshots__/viewButton.component.test.tsx.snap +++ /dev/null @@ -1,36 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Generic view button renders correctly 1`] = ` - -
    - -
    -
    -`; diff --git a/packages/datagateway-common/src/views/__snapshots__/viewCartButton.component.test.tsx.snap b/packages/datagateway-common/src/views/__snapshots__/viewCartButton.component.test.tsx.snap index a8fd6b380..82135f3ae 100644 --- a/packages/datagateway-common/src/views/__snapshots__/viewCartButton.component.test.tsx.snap +++ b/packages/datagateway-common/src/views/__snapshots__/viewCartButton.component.test.tsx.snap @@ -1,21 +1,34 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Generic cart button renders correctly 1`] = ` - - + + `; diff --git a/packages/datagateway-common/src/views/addToCartButton.component.test.tsx b/packages/datagateway-common/src/views/addToCartButton.component.test.tsx index 5b9c8aa33..02b2f7b03 100644 --- a/packages/datagateway-common/src/views/addToCartButton.component.test.tsx +++ b/packages/datagateway-common/src/views/addToCartButton.component.test.tsx @@ -1,37 +1,35 @@ -import React from 'react'; -import { mount } from 'enzyme'; +import * as React from 'react'; import AddToCartButton, { AddToCartButtonProps, } from './addToCartButton.component'; import configureStore from 'redux-mock-store'; import { initialState as dGCommonInitialState } from '../state/reducers/dgcommon.reducer'; import { StateType } from '../state/app.types'; -import { useCart, useAddToCart, useRemoveFromCart } from '../api/cart'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { MemoryRouter } from 'react-router-dom'; -import { ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; - -jest.mock('../api/cart', () => { - const originalModule = jest.requireActual('../api/cart'); - - return { - __esModule: true, - ...originalModule, - useCart: jest.fn(), - useAddToCart: jest.fn(), - useRemoveFromCart: jest.fn(), - }; -}); +import { QueryClient, QueryClientProvider } from 'react-query'; +import axios, { AxiosResponse } from 'axios'; +import { + render, + type RenderResult, + screen, + waitFor, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import { DownloadCartItem } from '../app.types'; describe('Generic add to cart button', () => { const mockStore = configureStore([thunk]); let state: StateType; + let user: UserEvent; + let holder: HTMLElement; + let cartItems: DownloadCartItem[]; - const createWrapper = (props: AddToCartButtonProps): ReactWrapper => { + function renderComponent(props: AddToCartButtonProps): RenderResult { const store = mockStore(state); - return mount( + return render( @@ -40,9 +38,11 @@ describe('Generic add to cart button', () => { ); - }; + } beforeEach(() => { + cartItems = []; + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ dgdataview: {}, //Dont need to fill, since not part of the test @@ -52,129 +52,202 @@ describe('Generic add to cart button', () => { }) ); - (useCart as jest.Mock).mockReturnValue({ - data: [], - isLoading: false, - }); - (useAddToCart as jest.Mock).mockReturnValue({ - mutate: jest.fn(), - isLoading: false, - }); - (useRemoveFromCart as jest.Mock).mockReturnValue({ - mutate: jest.fn(), - isLoading: false, - }); + holder = document.createElement('div'); + holder.setAttribute('id', 'datagateway-dataview'); + document.body.appendChild(holder); + + axios.get = jest + .fn() + .mockImplementation((url: string): Promise> => { + if (/.*\/user\/cart\/.*$/.test(url)) { + return Promise.resolve({ + data: { cartItems }, + }); + } + return Promise.reject(`Endpoint not mocked: ${url}`); + }); + + axios.post = jest + .fn() + .mockImplementation((url: string): Promise> => { + if (/.*\/user\/cart\/.*\/cartItems/.test(url)) { + return Promise.resolve({ + data: [], + }); + } + return Promise.reject(`Endpoint not mocked: ${url}`); + }); }); afterEach(() => { + document.body.removeChild(holder); jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper({ + it('renders correctly', async () => { + renderComponent({ allIds: [1], entityId: 1, entityType: 'investigation', }); - expect(wrapper.find('button').text()).toBe('buttons.add_to_cart'); - expect(wrapper.find('StyledTooltip').prop('title')).toEqual(''); + + expect( + await screen.findByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); }); - it('renders as disabled when cart is loading', () => { - (useCart as jest.Mock).mockReturnValue({ - data: undefined, - isLoading: true, - }); - const wrapper = createWrapper({ + it('renders as disabled when cart is loading', async () => { + axios.get = jest.fn().mockReturnValue( + new Promise((_) => { + // never resolve the promise to pretend the query is loading + }) + ); + + renderComponent({ allIds: [1], entityId: 1, entityType: 'investigation', }); - expect(wrapper.find('button').prop('disabled')).toBe(true); - expect(wrapper.find('StyledTooltip').prop('title')).toEqual( - 'buttons.cart_loading_tooltip' - ); + + const addToCartButton = await screen.findByRole('button', { + name: 'buttons.add_to_cart', + }); + + expect(addToCartButton).toBeDisabled(); + + await user.hover(addToCartButton.parentElement); + + expect( + await screen.findByText('buttons.cart_loading_tooltip') + ).toBeInTheDocument(); }); - it('renders as disabled with tooltip when cart does not load', () => { - (useCart as jest.Mock).mockReturnValue({ - data: undefined, - isLoading: false, + it('renders as disabled with tooltip when cart does not load', async () => { + axios.get = jest.fn().mockRejectedValue({ + message: 'Test error message', }); - const wrapper = createWrapper({ + + renderComponent({ allIds: [1], entityId: 1, entityType: 'investigation', }); - expect(wrapper.find('button').prop('disabled')).toBe(true); - expect(wrapper.find('StyledTooltip').prop('title')).toEqual( - 'buttons.cart_loading_failed_tooltip' - ); - }); - it('renders as disabled with tooltip when parent entity selected', () => { - (useCart as jest.Mock).mockReturnValueOnce({ - data: [ - { - entityId: 1, - entityType: 'investigation', - id: 1, - name: 'test', - parentEntities: [], - }, - ], - isLoading: false, + const addToCartButton = await screen.findByRole('button', { + name: 'buttons.add_to_cart', }); - const wrapper = createWrapper({ + + expect(addToCartButton).toBeDisabled(); + + await user.hover(addToCartButton.parentElement); + + expect( + await screen.findByText('buttons.cart_loading_failed_tooltip') + ).toBeInTheDocument(); + }); + + it('renders as disabled with tooltip when parent entity selected', async () => { + cartItems = [ + { + entityId: 1, + entityType: 'investigation', + id: 1, + name: 'test', + parentEntities: [], + }, + ]; + + renderComponent({ allIds: [1, 2], entityId: 2, entityType: 'dataset', parentId: '1', }); - expect(wrapper.find('button').prop('disabled')).toBe(true); - expect(wrapper.find('StyledTooltip').prop('title')).toEqual( - 'buttons.parent_selected_tooltip' - ); + + const addToCartButton = await screen.findByRole('button', { + name: 'buttons.add_to_cart', + }); + + expect(addToCartButton).toBeDisabled(); + + await user.hover(addToCartButton.parentElement); + + expect( + await screen.findByText('buttons.parent_selected_tooltip') + ).toBeInTheDocument(); }); - it('calls addToCart action on button press with item not in cart', () => { - const entityType = 'investigation'; - const wrapper = createWrapper({ + it('calls addToCart action on button press with item not in cart', async () => { + renderComponent({ allIds: [1], entityId: 1, - entityType, + entityType: 'investigation', }); - wrapper.find('#add-to-cart-btn-investigation-1').last().simulate('click'); + // wait for data to finish loading + await waitFor(() => { + expect( + screen.getByRole('button', { name: 'buttons.add_to_cart' }) + ).not.toBeDisabled(); + }); - expect(useAddToCart).toHaveBeenCalledWith(entityType); + axios.post = jest.fn().mockResolvedValue({ + data: { + cartItems: [ + { + entityType: 'investigation', + entityId: 1, + }, + ], + }, + }); + + user.click( + await screen.findByRole('button', { name: 'buttons.add_to_cart' }) + ); + + expect( + await screen.findByRole('button', { name: 'buttons.remove_from_cart' }) + ).toBeInTheDocument(); }); - it('calls removeFromCart on button press with item already in cart', () => { - (useCart as jest.Mock).mockReturnValueOnce({ - data: [ - { - entityId: 1, - entityType: 'investigation', - id: 1, - name: 'test', - parentEntities: [], - }, - ], - isLoading: false, + it('calls removeFromCart on button press with item already in cart', async () => { + axios.get = jest.fn().mockResolvedValue({ + data: { + cartItems: [ + { + entityId: 1, + entityType: 'investigation', + id: 1, + name: 'test', + parentEntities: [], + }, + ], + }, }); - const entityType = 'investigation'; - const wrapper = createWrapper({ + renderComponent({ allIds: [1], entityId: 1, - entityType, + entityType: 'investigation', }); - wrapper - .find('#remove-from-cart-btn-investigation-1') - .last() - .simulate('click'); - expect(useRemoveFromCart).toHaveBeenCalledWith(entityType); + expect( + await screen.findByRole('button', { name: 'buttons.remove_from_cart' }) + ).toBeInTheDocument(); + + axios.post = jest.fn().mockResolvedValue({ + data: { + cartItems: [], + }, + }); + + await user.click( + screen.getByRole('button', { name: 'buttons.remove_from_cart' }) + ); + + expect( + await screen.findByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-common/src/views/clearFiltersButton.component.test.tsx b/packages/datagateway-common/src/views/clearFiltersButton.component.test.tsx index 51e976732..0f98d8729 100644 --- a/packages/datagateway-common/src/views/clearFiltersButton.component.test.tsx +++ b/packages/datagateway-common/src/views/clearFiltersButton.component.test.tsx @@ -1,38 +1,27 @@ -import React from 'react'; +import * as React from 'react'; import configureStore from 'redux-mock-store'; import { initialState as dGCommonInitialState } from '../state/reducers/dgcommon.reducer'; import { StateType } from '../state/app.types'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { MemoryRouter } from 'react-router-dom'; -import { mount, ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import ClearFiltersButton, { ClearFilterProps, } from './clearFiltersButton.component'; -import { render, RenderResult } from '@testing-library/react'; +import { render, screen, type RenderResult } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; describe('Generic clear filters button', () => { const mockStore = configureStore([thunk]); let state: StateType; let props: ClearFilterProps; + let user: UserEvent; const handleButtonClearFilters = jest.fn(); - const createWrapper = (props: ClearFilterProps): ReactWrapper => { - const store = mockStore(state); - return mount( - - - - - - - - ); - }; - - const createRTLWrapper = (props: ClearFilterProps): RenderResult => { + const renderComponent = (props: ClearFilterProps): RenderResult => { const store = mockStore(state); return render( @@ -47,10 +36,11 @@ describe('Generic clear filters button', () => { beforeEach(() => { props = { - handleButtonClearFilters: handleButtonClearFilters, + handleButtonClearFilters, disabled: false, }; + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ dgdataview: {}, //Dont need to fill, since not part of the test @@ -71,28 +61,28 @@ describe('Generic clear filters button', () => { }); it('renders correctly', () => { - const wrapper = createRTLWrapper(props); - expect(wrapper.asFragment()).toMatchSnapshot(); + const { asFragment } = renderComponent(props); + expect(asFragment()).toMatchSnapshot(); }); - it('calls the handle clear filter button when the button is clicked', () => { - const wrapper = createWrapper(props); + it('calls the handle clear filter button when the button is clicked', async () => { + renderComponent(props); - wrapper - .find('[data-testid="clear-filters-button"]') - .last() - .simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ); expect(handleButtonClearFilters).toHaveBeenCalledTimes(1); }); - it('is disabled when prop disabled is equal to true', () => { - props = { - handleButtonClearFilters: handleButtonClearFilters, + it('is disabled when prop disabled is equal to true', async () => { + renderComponent({ + handleButtonClearFilters, disabled: true, - }; - const wrapper = createWrapper(props); + }); - expect(wrapper.find(ClearFiltersButton).props().disabled).toEqual(true); + expect( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); }); }); diff --git a/packages/datagateway-common/src/views/downloadButton.component.test.tsx b/packages/datagateway-common/src/views/downloadButton.component.test.tsx index 60c2873c4..63bfee915 100644 --- a/packages/datagateway-common/src/views/downloadButton.component.test.tsx +++ b/packages/datagateway-common/src/views/downloadButton.component.test.tsx @@ -1,19 +1,27 @@ -import React from 'react'; -import { mount } from 'enzyme'; +import * as React from 'react'; import DownloadButton, { DownloadButtonProps, } from './downloadButton.component'; import configureStore from 'redux-mock-store'; import { initialState as dGCommonInitialState } from '../state/reducers/dgcommon.reducer'; -import { downloadDatafile } from '../api/datafiles'; -import { downloadDataset } from '../api/datasets'; -import { downloadInvestigation } from '../api/investigations'; +import { + downloadDatafile, + downloadDataset, + downloadInvestigation, +} from '../api'; import { StateType } from '../state/app.types'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { MemoryRouter } from 'react-router-dom'; -import { ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { + render, + type RenderResult, + screen, + waitFor, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('../api/datafiles'); jest.mock('../api/datasets'); @@ -22,10 +30,11 @@ jest.mock('../api/investigations'); describe('Generic download button', () => { const mockStore = configureStore([thunk]); let state: StateType; + let user: UserEvent; - const createWrapper = (props: DownloadButtonProps): ReactWrapper => { + function renderComponent(props: DownloadButtonProps): RenderResult { const store = mockStore(state); - return mount( + return render( @@ -34,9 +43,10 @@ describe('Generic download button', () => { ); - }; + } beforeEach(() => { + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ dgdataview: {}, //Dont need to fill, since not part of the test @@ -55,149 +65,197 @@ describe('Generic download button', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const props: DownloadButtonProps = { - entityType: 'datafile', - entityName: 'test', - entityId: 1, - entitySize: 1, - }; - const textButtonWrapper = createWrapper(props); - expect(textButtonWrapper.find('button').text()).toBe('buttons.download'); + describe('text variant', () => { + it('renders correctly', async () => { + renderComponent({ + entityType: 'datafile', + entityName: 'test', + entityId: 1, + entitySize: 1, + }); - const iconButtonWrapper = createWrapper({ - ...props, - variant: 'icon', + expect( + await screen.findByRole('button', { name: 'buttons.download' }) + ).toBeInTheDocument(); }); - expect(iconButtonWrapper.find('button').text()).toBe(''); - }); - it('calls download investigation on button press for both text and icon buttons', () => { - const props: DownloadButtonProps = { - entityType: 'investigation', - entityName: 'test', - entityId: 1, - entitySize: 1, - }; - let wrapper = createWrapper(props); - - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadInvestigation).toHaveBeenCalledWith( - 'https://www.example.com/ids', - 1, - 'test' - ); + it('calls download investigation on button press', async () => { + renderComponent({ + entityType: 'investigation', + entityName: 'test', + entityId: 1, + entitySize: 1, + }); - jest.clearAllMocks(); + await user.click( + await screen.findByRole('button', { name: 'buttons.download' }) + ); - wrapper = createWrapper({ - ...props, - variant: 'icon', + expect(downloadInvestigation).toHaveBeenCalledWith( + 'https://www.example.com/ids', + 1, + 'test' + ); }); - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadInvestigation).toHaveBeenCalledWith( - 'https://www.example.com/ids', - 1, - 'test' - ); - }); + it('calls download dataset on button press', async () => { + renderComponent({ + entityType: 'dataset', + entityName: 'test', + entityId: 1, + entitySize: 1, + }); - it('calls download dataset on button press for both text and icon buttons', () => { - const props: DownloadButtonProps = { - entityType: 'dataset', - entityName: 'test', - entityId: 1, - entitySize: 1, - }; - let wrapper = createWrapper(props); - - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadDataset).toHaveBeenCalledWith( - 'https://www.example.com/ids', - 1, - 'test' - ); + await user.click( + await screen.findByRole('button', { name: 'buttons.download' }) + ); - jest.clearAllMocks(); + expect(downloadDataset).toHaveBeenCalledWith( + 'https://www.example.com/ids', + 1, + 'test' + ); + }); + + it('calls download datafile on button press', async () => { + renderComponent({ + entityType: 'datafile', + entityName: 'test', + entityId: 1, + entitySize: 1, + }); + + await user.click( + await screen.findByRole('button', { name: 'buttons.download' }) + ); - wrapper = createWrapper({ - ...props, - variant: 'icon', + expect(downloadDatafile).toHaveBeenCalledWith( + 'https://www.example.com/ids', + 1, + 'test' + ); }); - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadDataset).toHaveBeenCalledWith( - 'https://www.example.com/ids', - 1, - 'test' - ); + it('renders a tooltip and disabled button if entity size is zero', async () => { + renderComponent({ + entityType: 'datafile', + entityName: 'test', + entityId: 1, + entitySize: 0, + }); + + const button = await screen.findByRole('button', { + name: 'buttons.download', + }); + + expect(button).toBeDisabled(); + + await user.hover(button.parentElement); + expect( + await screen.findByText('buttons.unable_to_download_tooltip') + ).toBeInTheDocument(); + }); }); - it('calls download datafile on button press for both text and icon buttons', () => { - const props: DownloadButtonProps = { - entityType: 'datafile', - entityName: 'test', - entityId: 1, - entitySize: 1, - }; - let wrapper = createWrapper(props); - - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadDatafile).toHaveBeenCalledWith( - 'https://www.example.com/ids', - 1, - 'test' - ); + describe('icon variant', () => { + it('renders icon variant correctly', async () => { + renderComponent({ + entityType: 'datafile', + entityName: 'test', + entityId: 1, + entitySize: 1, + variant: 'icon', + }); - jest.clearAllMocks(); + expect(await screen.findByTestId('GetAppIcon')).toBeInTheDocument(); + }); + + it('calls download investigation on button press', async () => { + renderComponent({ + entityType: 'investigation', + entityName: 'test', + entityId: 1, + entitySize: 1, + variant: 'icon', + }); + + await user.click(await screen.findByTestId('GetAppIcon')); - wrapper = createWrapper({ - ...props, - variant: 'icon', + expect(downloadInvestigation).toHaveBeenCalledWith( + 'https://www.example.com/ids', + 1, + 'test' + ); }); - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadDatafile).toHaveBeenCalledWith( - 'https://www.example.com/ids', - 1, - 'test' - ); - }); + it('calls download dataset on button press', async () => { + renderComponent({ + entityType: 'dataset', + entityName: 'test', + entityId: 1, + entitySize: 1, + variant: 'icon', + }); - it('renders nothing when entityName is undefined', () => { - const wrapper = createWrapper({ - entityType: 'datafile', - entityName: undefined, - entityId: 1, - entitySize: 1, + await user.click(await screen.findByTestId('GetAppIcon')); + + expect(downloadDataset).toHaveBeenCalledWith( + 'https://www.example.com/ids', + 1, + 'test' + ); }); - expect(wrapper.find(DownloadButton).children().length).toBe(0); - }); + it('calls download datafile on button press', async () => { + renderComponent({ + entityType: 'datafile', + entityName: 'test', + entityId: 1, + entitySize: 1, + variant: 'icon', + }); - it('renders a tooltip and disabled button if entity size is zero', () => { - const props: DownloadButtonProps = { - entityType: 'datafile', - entityName: 'test', - entityId: 1, - entitySize: 0, - }; - let wrapper = createWrapper(props); + await user.click(await screen.findByTestId('GetAppIcon')); + + expect(downloadDatafile).toHaveBeenCalledWith( + 'https://www.example.com/ids', + 1, + 'test' + ); + }); - expect(wrapper.find('#tooltip-1').first().prop('title')).not.toEqual(''); - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadDatafile).not.toHaveBeenCalled(); + it('renders a tooltip and disabled button if entity size is zero', async () => { + renderComponent({ + entityType: 'datafile', + entityName: 'test', + entityId: 1, + entitySize: 0, + variant: 'icon', + }); - jest.clearAllMocks(); + const button = await screen.findByRole('button', { + name: 'buttons.download', + }); - wrapper = createWrapper({ - ...props, - variant: 'icon', + expect(button).toBeDisabled(); + + await user.hover(button.parentElement); + expect( + await screen.findByText('buttons.unable_to_download_tooltip') + ).toBeInTheDocument(); }); + }); - expect(wrapper.find('#tooltip-1').first().prop('title')).not.toEqual(''); - wrapper.find('#download-btn-1').last().simulate('click'); - expect(downloadDatafile).not.toHaveBeenCalled(); + it('renders nothing when entityName is undefined', async () => { + renderComponent({ + entityType: 'datafile', + entityName: undefined, + entityId: 1, + entitySize: 1, + }); + + await waitFor(() => { + expect(screen.queryByRole('button')).toBeNull(); + }); }); }); diff --git a/packages/datagateway-common/src/views/selectionAlert.component.test.tsx b/packages/datagateway-common/src/views/selectionAlert.component.test.tsx index 602c02890..ece6a617a 100644 --- a/packages/datagateway-common/src/views/selectionAlert.component.test.tsx +++ b/packages/datagateway-common/src/views/selectionAlert.component.test.tsx @@ -1,10 +1,11 @@ -import { mount, ReactWrapper } from 'enzyme'; -import React from 'react'; +import * as React from 'react'; import { DownloadCartItem } from '../app.types'; import { NotificationType } from '../state/actions/actions.types'; import SelectionAlert from './selectionAlert.component'; -import { render, RenderResult } from '@testing-library/react'; +import { render, RenderResult, screen, waitFor } from '@testing-library/react'; import { AnyAction } from 'redux'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; describe('SelectionAlert', () => { let events: CustomEvent[] = []; @@ -34,23 +35,9 @@ describe('SelectionAlert', () => { let storageGetItemMock: jest.Mock; let storageSetItemMock: jest.Mock; let storageRemoveItemMock: jest.Mock; + let user: UserEvent; - const createWrapper = ( - selectedItems: DownloadCartItem[], - loggedInAnonymously: boolean - ): ReactWrapper => { - return mount( - undefined} - width={'100px'} - marginSide={'4px'} - loggedInAnonymously={loggedInAnonymously} - /> - ); - }; - - const createRTLWrapper = ( + const renderComponent = ( selectedItems: DownloadCartItem[], loggedInAnonymously: boolean ): RenderResult => { @@ -66,6 +53,7 @@ describe('SelectionAlert', () => { }; beforeEach(() => { + user = userEvent.setup(); events = []; document.dispatchEvent = (e: Event) => { events.push(e as CustomEvent); @@ -86,43 +74,72 @@ describe('SelectionAlert', () => { }); it('renders correctly', () => { - const wrapper = createRTLWrapper([cartItems[0]], true); - expect(wrapper.asFragment()).toMatchSnapshot(); + const { asFragment } = renderComponent([cartItems[0]], true); + expect(asFragment()).toMatchSnapshot(); }); - it('sends a notification to SciGateway if user is not logged in but only once', () => { - const wrapper = createWrapper([cartItems[0]], true); - - wrapper.update(); - - expect(events.length).toBe(1); - expect(events[0].detail).toEqual({ - type: NotificationType, - payload: { - severity: 'warning', - message: 'selec_alert.warning_message_session_token', - }, + it('sends a notification to SciGateway if user is not logged in but only once', async () => { + const { rerender } = renderComponent([cartItems[0]], true); + + await waitFor(() => { + expect(events.length).toBe(1); + expect(events[0].detail).toEqual({ + type: NotificationType, + payload: { + severity: 'warning', + message: 'selec_alert.warning_message_session_token', + }, + }); + + expect(storageSetItemMock).toHaveBeenCalledTimes(1); + expect(storageSetItemMock).toHaveBeenCalledWith( + 'sentExpiredMessage', + '1' + ); }); - expect(storageSetItemMock).toHaveBeenCalledTimes(1); - expect(storageSetItemMock).toHaveBeenCalledWith('sentExpiredMessage', '1'); - storageGetItemMock.mockReturnValueOnce('1'); - //Adding another element - wrapper.setProps({ selectedItems: [cartItems[0], cartItems[1]] }); + rerender( + undefined} + width={'100px'} + marginSide={'4px'} + /> + ); + expect(events.length).toBe(1); storageGetItemMock.mockReturnValueOnce('1'); + rerender( + undefined} + width={'100px'} + marginSide={'4px'} + /> + ); + //Removing all should reset the message - wrapper.setProps({ selectedItems: [] }); expect(events.length).toBe(1); expect(storageRemoveItemMock).toHaveBeenCalledTimes(1); expect(storageRemoveItemMock).toHaveBeenCalledWith('sentExpiredMessage'); - wrapper.setProps({ selectedItems: [cartItems[0]] }); + rerender( + undefined} + width={'100px'} + marginSide={'4px'} + /> + ); + expect(events.length).toBe(2); expect(events[1].detail).toEqual({ type: NotificationType, @@ -136,72 +153,90 @@ describe('SelectionAlert', () => { it('does not send a notification to SciGateway if user is not logged and it has been sent before', () => { storageGetItemMock.mockReturnValue('1'); - const wrapper = createWrapper([], true); + const { rerender } = renderComponent([], true); - wrapper.update(); + rerender( + undefined} + width={'100px'} + marginSide={'4px'} + /> + ); - //Adding the first element should not broadcast a message - wrapper.setProps({ selectedItems: [cartItems[0]] }); expect(events.length).toBe(0); }); - it('renders correctly with more than one item selected', () => { - const wrapper = createWrapper(cartItems, false); - expect( - wrapper.find('[aria-label="selection-alert-text"]').first().text().trim() - ).toEqual('selec_alert.added'); + it('renders correctly with more than one item selected', async () => { + renderComponent(cartItems, false); + expect(await screen.findByText('selec_alert.added')).toBeInTheDocument(); }); - it('renders correctly with one item removed', () => { - const wrapper = createWrapper(cartItems, false); - wrapper.setProps({ selectedItems: [cartItems[0], cartItems[1]] }); + it('renders correctly with one item removed', async () => { + const { rerender } = renderComponent(cartItems, false); - expect( - wrapper.find('[aria-label="selection-alert-text"]').first().text().trim() - ).toEqual('selec_alert.removed'); + rerender( + undefined} + width={'100px'} + marginSide={'4px'} + /> + ); + + expect(await screen.findByText('selec_alert.removed')).toBeInTheDocument(); }); - it('renders correctly with all items removed', () => { - const wrapper = createWrapper(cartItems, false); - wrapper.setProps({ selectedItems: [] }); + it('renders correctly with all items removed', async () => { + const { rerender } = renderComponent(cartItems, false); + + rerender( + undefined} + width={'100px'} + marginSide={'4px'} + /> + ); - expect( - wrapper.find('[aria-label="selection-alert-text"]').first().text().trim() - ).toEqual('selec_alert.removed'); + expect(await screen.findByText('selec_alert.removed')).toBeInTheDocument(); }); - it('does not render when nothing has changed', () => { - const wrapper = createWrapper([], false); - expect(wrapper.find('[aria-label="selection-alert"]').exists()).toBeFalsy(); + it('does not render when nothing has changed', async () => { + renderComponent([], false); + await waitFor(() => { + expect(screen.queryByLabelText('selection-alert')).toBeNull(); + }); }); - it('does not render when closed', () => { - const wrapper = createWrapper(cartItems, false); - wrapper - .find('[aria-label="selection-alert-close"]') - .last() - .simulate('click'); - wrapper.update(); + it('does not render when closed', async () => { + renderComponent(cartItems, false); - expect(wrapper.find('[aria-label="selection-alert"]').exists()).toBeFalsy(); + await user.click(await screen.findByLabelText('selection-alert-close')); + + await waitFor(() => { + expect(screen.queryByLabelText('selection-alert')).toBeNull(); + }); }); - it('clicking link calls navigateToSelection', () => { + it('clicking link calls navigateToSelection', async () => { let navigated = false; const navigate = (): void => { navigated = true; }; - const wrapper = mount( + + render( ); - wrapper - .find('[aria-label="selection-alert-link"]') - .first() - .simulate('click'); + + await user.click(await screen.findByLabelText('selection-alert-link')); expect(navigated).toBeTruthy(); }); diff --git a/packages/datagateway-common/src/views/viewButton.component.test.tsx b/packages/datagateway-common/src/views/viewButton.component.test.tsx index c7fd0f1db..55244bef4 100644 --- a/packages/datagateway-common/src/views/viewButton.component.test.tsx +++ b/packages/datagateway-common/src/views/viewButton.component.test.tsx @@ -1,58 +1,46 @@ -import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import * as React from 'react'; import configureStore from 'redux-mock-store'; import { initialState as dGCommonInitialState } from '../state/reducers/dgcommon.reducer'; -import { StateType } from '../state/app.types'; +import type { StateType } from '../state/app.types'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { MemoryRouter } from 'react-router-dom'; -import { mount, ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; -import ViewButton, { ViewProps } from './viewButton.component'; -import { render, RenderResult } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import ViewButton from './viewButton.component'; describe('Generic view button', () => { - const mockStore = configureStore([thunk]); let state: StateType; - let props: ViewProps; - const handleButtonChange = jest.fn(); - - const createWrapper = (props: ViewProps): ReactWrapper => { - const store = mockStore(state); - return mount( - - - - - - - - ); - }; - - const createRTLWrapper = (props: ViewProps): RenderResult => { - const store = mockStore(state); - return render( - - + function Wrapper({ + children, + }: { + children: React.ReactElement; + }): JSX.Element { + return ( + + - + {children} ); - }; + } beforeEach(() => { - props = { - viewCards: true, - handleButtonChange: handleButtonChange, - disabled: false, - }; - state = JSON.parse( JSON.stringify({ - dgdataview: {}, //Dont need to fill, since not part of the test + dgdataview: {}, + //Dont need to fill, since not part of the test dgcommon: { ...dGCommonInitialState, urls: { @@ -64,52 +52,65 @@ describe('Generic view button', () => { ); }); - afterEach(() => { - jest.clearAllMocks(); - handleButtonChange.mockClear(); + it('displays as view table when card view is enabled', async () => { + render(, { + wrapper: Wrapper, + }); + + const button = screen.getByRole('button', { + name: 'page view app.view_table', + }); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('app.view_table'); + expect(screen.getByTestId('ViewListIcon')).toBeInTheDocument(); }); - it('renders correctly', () => { - const wrapper = createRTLWrapper(props); - expect(wrapper.asFragment()).toMatchSnapshot(); + it('displays as view cards when card view is disabled', () => { + render(, { + wrapper: Wrapper, + }); + + const button = screen.getByRole('button', { + name: 'page view app.view_cards', + }); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('app.view_cards'); + expect(screen.getByTestId('ViewAgendaIcon')).toBeInTheDocument(); }); - it('calls the handle button change when the view button is clicked', () => { - const wrapper = createWrapper(props); + it('calls the handle button change when the view button is clicked', async () => { + const user = userEvent.setup(); + const handleButtonChange = jest.fn(); - wrapper - .find('[aria-label="page view app.view_table"]') - .last() - .simulate('click'); + render(, { + wrapper: Wrapper, + }); + await user.click( + await screen.findByRole('button', { + name: 'page view app.view_table', + }) + ); expect(handleButtonChange).toHaveBeenCalledTimes(1); + }); - wrapper.update(); - - expect( - wrapper.find('[aria-label="page view app.view_cards"]') - ).toBeTruthy(); - - wrapper - .find('[aria-label="page view app.view_table"]') - .last() - .simulate('click'); - - expect(handleButtonChange).toHaveBeenCalledTimes(2); + it('is disabled when prop disabled is equal to true', async () => { + const user = userEvent.setup(); + const handleButtonChange = jest.fn(); - expect( - wrapper.find('[aria-label="page view app.view_cards"]') - ).toBeTruthy(); - }); + render( + , + { + wrapper: Wrapper, + } + ); - it('is disabled when prop disabled is equal to true', () => { - props = { - viewCards: true, - handleButtonChange: handleButtonChange, - disabled: true, - }; - const wrapper = createWrapper(props); + const button = screen.getByRole('button', { + name: 'page view app.view_table', + }); - expect(wrapper.find(ViewButton).props().disabled).toEqual(true); + expect(button).toBeDisabled(); + await expect(user.click(button)).rejects.toThrowError(); + expect(handleButtonChange).not.toBeCalled(); }); }); diff --git a/packages/datagateway-common/src/views/viewCartButton.component.test.tsx b/packages/datagateway-common/src/views/viewCartButton.component.test.tsx index 70a543b34..9eeee6ef4 100644 --- a/packages/datagateway-common/src/views/viewCartButton.component.test.tsx +++ b/packages/datagateway-common/src/views/viewCartButton.component.test.tsx @@ -1,44 +1,53 @@ -import React from 'react'; +import { render, type RenderResult, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import * as React from 'react'; import configureStore from 'redux-mock-store'; import { initialState as dGCommonInitialState } from '../state/reducers/dgcommon.reducer'; import { StateType } from '../state/app.types'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { MemoryRouter } from 'react-router-dom'; -import { mount, ReactWrapper, shallow } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import ViewCartButton, { CartProps } from './viewCartButton.component'; -import { Badge } from '@mui/material'; describe('Generic cart button', () => { const mockStore = configureStore([thunk]); + const navigateToDownload = jest.fn(); + let user: UserEvent; let state: StateType; let props: CartProps; - const navigateToDownload = jest.fn(); - - const createWrapper = (props: CartProps): ReactWrapper => { + function renderComponent(props: CartProps): RenderResult { const store = mockStore(state); - return mount( + return render( - + ); - }; + } beforeEach(() => { + user = userEvent.setup(); props = { cartItems: [], navigateToDownload: navigateToDownload, }; - state = JSON.parse( JSON.stringify({ - dgdataview: {}, //Dont need to fill, since not part of the test + dgdataview: {}, + //Dont need to fill, since not part of the test dgcommon: { ...dGCommonInitialState, urls: { @@ -55,20 +64,18 @@ describe('Generic cart button', () => { navigateToDownload.mockClear(); }); - it('renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + it('renders correctly', async () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('calls the navigate to download plugin when the cart clicked', () => { - const wrapper = createWrapper(props); - - wrapper.find('[aria-label="app.cart_arialabel"]').last().simulate('click'); - + it('calls the navigate to download plugin when the cart clicked', async () => { + renderComponent(props); + await user.click(await screen.findByLabelText('app.cart_arialabel')); expect(navigateToDownload).toHaveBeenCalledTimes(1); }); - it('has cartItems', () => { + it('has cartItems', async () => { props = { cartItems: [ { @@ -88,8 +95,7 @@ describe('Generic cart button', () => { ], navigateToDownload: navigateToDownload, }; - const wrapper = createWrapper(props); - - expect(wrapper.find(Badge).props().badgeContent).toEqual(2); + renderComponent(props); + expect(await screen.findByText('2')).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-dataview/package.json b/packages/datagateway-dataview/package.json index 41baad99e..2159fa1bb 100644 --- a/packages/datagateway-dataview/package.json +++ b/packages/datagateway-dataview/package.json @@ -83,9 +83,6 @@ ] }, "jest": { - "snapshotSerializers": [ - "enzyme-to-json/serializer" - ], "collectCoverageFrom": [ "src/**/*.{tsx,ts,js,jsx}", "!src/index.tsx", @@ -109,21 +106,18 @@ "devDependencies": { "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "12.1.3", + "@testing-library/react-hooks": "8.0.1", "@testing-library/user-event": "14.4.3", - "@types/enzyme": "3.10.10", "@types/react-redux": "7.1.22", "@types/redux-mock-store": "1.0.3", "@typescript-eslint/eslint-plugin": "5.49.0", "@typescript-eslint/parser": "5.49.0", "@welldone-software/why-did-you-render": "7.0.1", - "@wojtekmaj/enzyme-adapter-react-17": "0.6.6", "babel-eslint": "10.1.0", "blob-polyfill": "7.0.20220408", "cross-env": "7.0.3", "cypress": "11.2.0", "cypress-failed-log": "2.10.0", - "enzyme": "3.11.0", - "enzyme-to-json": "3.6.1", "eslint": "8.32.0", "eslint-config-react-app": "7.0.0", "express": "4.18.1", diff --git a/packages/datagateway-dataview/src/App.test.tsx b/packages/datagateway-dataview/src/App.test.tsx index d2f2bd668..2a6e29d19 100644 --- a/packages/datagateway-dataview/src/App.test.tsx +++ b/packages/datagateway-dataview/src/App.test.tsx @@ -1,31 +1,81 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; +import * as React from 'react'; import App from './App'; -import { mount } from 'enzyme'; import log from 'loglevel'; -import { Provider } from 'react-redux'; +import { render, screen, waitFor } from '@testing-library/react'; +import PageContainer from './page/pageContainer.component'; +import { configureApp, settingsLoaded } from './state/actions'; -jest.mock('loglevel'); +jest + .mock('loglevel') + .mock('./page/pageContainer.component') + .mock('./state/actions', () => ({ + ...jest.requireActual('./state/actions'), + configureApp: jest.fn(), + })) + .mock('react', () => ({ + ...jest.requireActual('react'), + // skip React suspense mechanism and show children directly. + Suspense: ({ children }) => children, + })); describe('App', () => { - it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + beforeEach(() => { + jest.restoreAllMocks(); }); - it('catches errors using componentDidCatch and shows fallback UI', () => { - const wrapper = mount(); - const error = new Error('test'); - wrapper.find(Provider).simulateError(error); + it('renders without crashing', async () => { + // pretend app is configured successfully + (configureApp as jest.MockedFn).mockReturnValue( + async (dispatch) => { + dispatch(settingsLoaded()); + } + ); + (PageContainer as jest.Mock).mockImplementation(() =>
    page
    ); - expect(wrapper.exists('.error')).toBe(true); + render(); - expect(log.error).toHaveBeenCalled(); - const mockLog = (log.error as jest.Mock).mock; + expect(await screen.findByText('page')).toBeInTheDocument(); + }); + + it('shows loading screen when configuring app', async () => { + (configureApp as jest.MockedFn).mockReturnValue( + () => + new Promise((_) => { + // never resolve the promise to pretend the app is still being configured + }) + ); + (PageContainer as jest.Mock).mockImplementation(() =>
    page
    ); + + render(); + + expect(await screen.findByText('Loading...')).toBeInTheDocument(); + expect(screen.queryByText('page')).toBeNull(); + }); + + it('catches errors using componentDidCatch and shows fallback UI', async () => { + // pretend app is configured successfully + (configureApp as jest.MockedFn).mockReturnValue( + async (dispatch) => { + dispatch(settingsLoaded()); + } + ); + // pretend PageContainer throw an error and see if will catch the error + (PageContainer as jest.Mock).mockImplementation(() => { + throw new Error('test PageContainer error'); + }); + + jest.spyOn(console, 'error').mockImplementation(() => { + // suppress console error + }); + + render(); + + await waitFor(() => { + // check that the error is logged + expect(log.error).toHaveBeenCalled(); + }); - expect(mockLog.calls).toContainEqual([ - `datagateway_dataview failed with error: ${error}`, - ]); + // check that fallback UI is shown + expect(await screen.findByText('app.error')).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-dataview/src/page/__snapshots__/translatedHomePage.component.test.tsx.snap b/packages/datagateway-dataview/src/page/__snapshots__/translatedHomePage.component.test.tsx.snap index b859a8b2d..bee184af6 100644 --- a/packages/datagateway-dataview/src/page/__snapshots__/translatedHomePage.component.test.tsx.snap +++ b/packages/datagateway-dataview/src/page/__snapshots__/translatedHomePage.component.test.tsx.snap @@ -1,15 +1,256 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HomePage translated homepage renders correctly 1`] = ` - + +
    +
    +
    +
    +

    + + Data discovery + + and + + access + +

    +

    + for + + large-scale + + science facilities +

    +
    +
    +
    +
    +
    +
    +
    +
    +

    + home_page.browse.title +

    +

    + home_page.browse.description1 +

    +

    + + DataGateway + + focuses on providing data discovery and data access functionality to the data. +

    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    + home_page.search.title +

    +

    + home_page.search.description +

    + +
    +
    +
    +
    +
    +
    +
    + +
    +

    + home_page.download.title +

    +

    + home_page.download.description +

    + +
    +
    +
    +
    +
    +
    +
    +

    + home_page.facility.title +

    +

    + home_page.facility.description +

    + +
    +
    +
    +
    +
    +
    +
    + `; diff --git a/packages/datagateway-dataview/src/page/breadcrumbs.component.test.tsx b/packages/datagateway-dataview/src/page/breadcrumbs.component.test.tsx index 6ab73deda..3d71d0e49 100644 --- a/packages/datagateway-dataview/src/page/breadcrumbs.component.test.tsx +++ b/packages/datagateway-dataview/src/page/breadcrumbs.component.test.tsx @@ -1,18 +1,21 @@ -import React from 'react'; +import * as React from 'react'; import { Provider } from 'react-redux'; - import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { Router } from 'react-router-dom'; import { dGCommonInitialState } from 'datagateway-common'; import { initialState as dgDataViewInitialState } from '../state/reducers/dgdataview.reducer'; -import { StateType } from '../state/app.types'; -import { createLocation, createMemoryHistory, History } from 'history'; -import { flushPromises } from '../setupTests'; +import type { StateType } from '../state/app.types'; +import { createLocation, createMemoryHistory, type History } from 'history'; import PageBreadcrumbs from './breadcrumbs.component'; import axios from 'axios'; -import { mount, ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { + render, + type RenderResult, + screen, + within, +} from '@testing-library/react'; jest.mock('loglevel'); @@ -46,12 +49,12 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { let state: StateType; let history: History; - const createWrapper = ( + const renderComponent = ( state: StateType, landingPageEntities: string[] = [] - ): ReactWrapper => { + ): RenderResult => { const mockStore = configureStore([thunk]); - return mount( + return render( @@ -113,19 +116,13 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(genericRoutes['investigations'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state); // Expect the axios.get to not have been made. expect(axios.get).not.toBeCalled(); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] p').text()).toEqual( + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + expect(screen.getByTestId('Breadcrumb-base')).toHaveTextContent( 'breadcrumbs.investigation' ); }); @@ -140,11 +137,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { ); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state); // Expect the axios.get to have been called once to get the investigation. expect(axios.get).toBeCalledTimes(1); @@ -154,21 +147,22 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.investigation' + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute( + 'href', + '/browse/investigation?view=card' ); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.investigation'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(within(breadcrumbs[0]).getByText('Title 1')).toBeInTheDocument(); + expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/investigation?view=card'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] p').text() - ).toEqual('Title 1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.dataset' - ); + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.dataset' + ) + ).toBeInTheDocument(); }); it('generic route renders correctly at the datafile level and requests the investigation & dataset entities', async () => { @@ -176,11 +170,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(genericRoutes['datafiles'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state); // Expect the axios.get to have been called twice; first to get the investigation // and second to get the dataset. @@ -196,27 +186,20 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.investigation' - ); - expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/investigation'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').text() - ).toEqual('Title 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').prop('href') - ).toEqual('/browse/investigation/1/dataset'); + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/investigation'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.investigation'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(within(breadcrumbs[0]).getByText('Title 1')).toBeInTheDocument(); + expect(within(breadcrumbs[1]).getByText('Name 1')).toBeInTheDocument(); + expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] p').text() - ).toEqual('Name 1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.datafile' - ); + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.datafile' + ) + ).toBeInTheDocument(); }); it('DLS route renders correctly at the base level and does not request', async () => { @@ -224,21 +207,14 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(DLSRoutes['proposals'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state); // Expect the axios.get to not have been called. expect(axios.get).not.toBeCalled(); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] p').text()).toEqual( - 'breadcrumbs.proposal' - ); + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.proposal'); }); it('DLS route renders correctly at the investigation level and requests the proposal entity', async () => { @@ -246,11 +222,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(DLSRoutes['investigations'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state); // Expect the axios.get to have been called twice. expect(axios.get).toHaveBeenCalledTimes(1); @@ -264,18 +236,10 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { } ); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.proposal' - ); - expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/proposal'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] p').text() - ).toEqual('Title 1'); + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/proposal'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.proposal'); }); it('DLS route renders correctly at the dataset level and requests the proposal & investigation entities', async () => { @@ -283,11 +247,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(DLSRoutes['datasets'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state); // Expect the axios.get to have been called twice. expect(axios.get).toHaveBeenCalledTimes(2); @@ -307,27 +267,24 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.proposal' + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/proposal'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.proposal'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(breadcrumbs[0]).toHaveAttribute( + 'href', + '/browse/proposal/INVESTIGATION 1/investigation' ); + expect(within(breadcrumbs[0]).getByText('Title 1')).toBeInTheDocument(); + expect(within(breadcrumbs[1]).getByText('1')).toBeInTheDocument(); + expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/proposal'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').text() - ).toEqual('Title 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').prop('href') - ).toEqual('/browse/proposal/INVESTIGATION 1/investigation'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] p').text() - ).toEqual('1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.dataset' - ); + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.dataset' + ) + ).toBeInTheDocument(); }); it('DLS route renders correctly at the datafile level and requests the proposal, investigation and dataset entities', async () => { @@ -335,11 +292,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(DLSRoutes['datafiles'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state); // Expect the axios.get to have been called three times. expect(axios.get).toHaveBeenCalledTimes(3); @@ -364,33 +317,29 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/proposal'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.proposal'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(breadcrumbs[0]).toHaveAttribute( + 'href', + '/browse/proposal/INVESTIGATION 1/investigation' ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.proposal' + expect(within(breadcrumbs[0]).getByText('Title 1')).toBeInTheDocument(); + expect(breadcrumbs[1]).toHaveAttribute( + 'href', + '/browse/proposal/INVESTIGATION 1/investigation/1/dataset' ); + expect(within(breadcrumbs[1]).getByText('1')).toBeInTheDocument(); + expect(within(breadcrumbs[2]).getByText('Name 1')).toBeInTheDocument(); + expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/proposal'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').text() - ).toEqual('Title 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').prop('href') - ).toEqual('/browse/proposal/INVESTIGATION 1/investigation'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] a').text() - ).toEqual('1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] a').prop('href') - ).toEqual('/browse/proposal/INVESTIGATION 1/investigation/1/dataset'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-3"] p').text() - ).toEqual('Name 1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.datafile' - ); + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.datafile' + ) + ).toBeInTheDocument(); }); it('ISIS route renders correctly at the base level and does not request', async () => { @@ -398,19 +347,13 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(ISISRoutes['instruments'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state, ['investigation', 'dataset']); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state, ['investigation', 'dataset']); // Expect the axios.get not to have been called expect(axios.get).not.toHaveBeenCalled(); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] p').text()).toEqual( + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + expect(screen.getByTestId('Breadcrumb-base')).toHaveTextContent( 'breadcrumbs.instrument' ); }); @@ -420,11 +363,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(ISISRoutes['facilityCycles'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state, ['investigation', 'dataset']); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state, ['investigation', 'dataset']); // Expect the axios.get to have been called three times. expect(axios.get).toHaveBeenCalledTimes(1); @@ -434,21 +373,19 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.instrument' - ); - expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/instrument'); + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/instrument'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.instrument'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(within(breadcrumbs[0]).getByText('Name 1')).toBeInTheDocument(); + expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] p').text() - ).toEqual('Name 1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.facilityCycle' - ); + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.facilityCycle' + ) + ).toBeInTheDocument(); }); it('ISIS route renders correctly at the investigation level and requests the instrument and facility cycle entities', async () => { @@ -456,11 +393,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(ISISRoutes['investigations'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state, ['investigation', 'dataset']); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state, ['investigation', 'dataset']); // Expect the axios.get to have been called three times. expect(axios.get).toHaveBeenCalledTimes(2); @@ -475,27 +408,24 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' - ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.instrument' + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/instrument'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.instrument'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(breadcrumbs[0]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle' ); + expect(within(breadcrumbs[0]).getByText('Name 1')).toBeInTheDocument(); + expect(within(breadcrumbs[1]).getByText('Name 1')).toBeInTheDocument(); + expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/instrument'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').text() - ).toEqual('Name 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] p').text() - ).toEqual('Name 1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.investigation' - ); + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.investigation' + ) + ).toBeInTheDocument(); }); it('ISIS route renders correctly at the dataset level and requests the instrument, facility cycle and investigation entities', async () => { @@ -503,11 +433,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(ISISRoutes['datasets'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state, ['investigation', 'dataset']); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state, ['investigation', 'dataset']); // Expect the axios.get to have been called three times. expect(axios.get).toHaveBeenCalledTimes(3); @@ -526,36 +452,34 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { Authorization: 'Bearer null', }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' + + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/instrument'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.instrument'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(breadcrumbs[0]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle' ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.instrument' + expect(within(breadcrumbs[0]).getByText('Name 1')).toBeInTheDocument(); + expect(breadcrumbs[1]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle/1/investigation' ); - expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/instrument'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').text() - ).toEqual('Name 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] a').text() - ).toEqual('Name 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle/1/investigation'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-3"] a').text() - ).toEqual('Title 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-3"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle/1/investigation/1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.dataset' + expect(within(breadcrumbs[1]).getByText('Name 1')).toBeInTheDocument(); + expect(breadcrumbs[2]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle/1/investigation/1' ); + expect(within(breadcrumbs[2]).getByText('Title 1')).toBeInTheDocument(); + + expect( + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.dataset' + ) + ).toBeInTheDocument(); }); it('ISIS route renders correctly at the datafile level and requests the instrument, facility cycle, investigation and dataset entities', async () => { @@ -563,11 +487,7 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { history.replace(createLocation(ISISRoutes['datafiles'])); // Set up store with test state and mount the breadcrumb. - const wrapper = createWrapper(state, ['investigation', 'dataset']); - - // Flush promises and update the re-render the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(state, ['investigation', 'dataset']); // Expect the axios.get to have been called three times. expect(axios.get).toHaveBeenCalledTimes(4); @@ -592,41 +512,32 @@ describe('PageBreadcrumbs tests (Generic, DLS, ISIS)', () => { }, }); - expect(wrapper.find('[data-testid="Breadcrumb-home"] p').text()).toEqual( - 'breadcrumbs.home' + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/instrument'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.instrument'); + + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(breadcrumbs[0]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle' ); - expect(wrapper.find('[data-testid="Breadcrumb-base"] a').text()).toEqual( - 'breadcrumbs.instrument' + expect(within(breadcrumbs[0]).getByText('Name 1')).toBeInTheDocument(); + expect(breadcrumbs[1]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle/1/investigation' ); - expect( - wrapper.find('[data-testid="Breadcrumb-base"] a').prop('href') - ).toEqual('/browse/instrument'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').text() - ).toEqual('Name 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-1"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] a').text() - ).toEqual('Name 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-2"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle/1/investigation'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-3"] a').text() - ).toEqual('Title 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-3"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle/1/investigation/1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-4"] a').text() - ).toEqual('Name 1'); - expect( - wrapper.find('[data-testid="Breadcrumb-hierarchy-4"] a').prop('href') - ).toEqual('/browse/instrument/1/facilityCycle/1/investigation/1/dataset/1'); - expect(wrapper.find('[data-testid="Breadcrumb-last"] p').text()).toEqual( - 'breadcrumbs.datafile' + expect(within(breadcrumbs[1]).getByText('Name 1')).toBeInTheDocument(); + expect(breadcrumbs[2]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle/1/investigation/1' ); + expect(within(breadcrumbs[2]).getByText('Title 1')).toBeInTheDocument(); + + expect( + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.datafile' + ) + ).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-dataview/src/page/doiRedirect.component.test.tsx b/packages/datagateway-dataview/src/page/doiRedirect.component.test.tsx index 20197349b..ba3c964c3 100644 --- a/packages/datagateway-dataview/src/page/doiRedirect.component.test.tsx +++ b/packages/datagateway-dataview/src/page/doiRedirect.component.test.tsx @@ -1,21 +1,20 @@ -import React from 'react'; -import { mount } from 'enzyme'; +import * as React from 'react'; import { - Investigation, - Instrument, FacilityCycle, - useInvestigation, + Instrument, + Investigation, + NotificationType, useFacilityCyclesByInvestigation, useInstrumentsPaginated, - NotificationType, + useInvestigation, } from 'datagateway-common'; import { Router } from 'react-router-dom'; -import { ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import DoiRedirect from './doiRedirect.component'; import { createLocation, createMemoryHistory } from 'history'; import log from 'loglevel'; import { AnyAction } from 'redux'; +import { render, type RenderResult, screen } from '@testing-library/react'; // jest.mock('loglevel'); @@ -50,15 +49,14 @@ describe('DOI Redirect page', () => { let mockInstrumentData: Instrument[] = []; let mockFacilityCycleData: FacilityCycle[] = []; - const createWrapper = (): ReactWrapper => { - return mount( + const renderComponent = (): RenderResult => + render( ); - }; beforeEach(() => { history = createMemoryHistory({ @@ -109,20 +107,7 @@ describe('DOI Redirect page', () => { }); it('redirects to correct link when everything loads correctly', async () => { - createWrapper(); - - expect(useInvestigation).toHaveBeenCalledWith(1); - expect(useInstrumentsPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'investigationInstruments.investigation.id': { eq: 1 }, - }), - }, - ]); - expect(useFacilityCyclesByInvestigation).toHaveBeenCalledWith( - '2022-04-01 00:00:00' - ); + renderComponent(); expect(history.location.pathname).toBe( '/browse/instrument/2/facilityCycle/3/investigation/1/dataset' ); @@ -133,51 +118,18 @@ describe('DOI Redirect page', () => { data: [], isLoading: true, }); - let wrapper = createWrapper(); - - expect(wrapper.find('Preloader').exists()).toBe(true); - expect(wrapper.find('Preloader').prop('loading')).toBe(true); - - (useInvestigation as jest.Mock).mockReturnValue({ - data: mockInvestigationData, - isLoading: false, - }); (useInstrumentsPaginated as jest.Mock).mockReturnValue({ data: [], isLoading: true, }); - wrapper = createWrapper(); - expect(wrapper.find('Preloader').exists()).toBe(true); - expect(wrapper.find('Preloader').prop('loading')).toBe(true); + renderComponent(); - (useInstrumentsPaginated as jest.Mock).mockReturnValue({ - data: mockInstrumentData, - isLoading: false, - }); - (useFacilityCyclesByInvestigation as jest.Mock).mockReturnValue({ - data: [], - isLoading: true, - isIdle: false, - }); - wrapper = createWrapper(); - - expect(wrapper.find('Preloader').exists()).toBe(true); - expect(wrapper.find('Preloader').prop('loading')).toBe(true); - - (useFacilityCyclesByInvestigation as jest.Mock).mockReturnValue({ - data: [], - isLoading: false, - isIdle: true, - }); - wrapper = createWrapper(); - - expect(wrapper.find('Preloader').exists()).toBe(true); - expect(wrapper.find('Preloader').prop('loading')).toBe(true); + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); }); - it('throws error and redirects to homepage if data invalid', async () => { - let events = []; + it('throws error and redirects to homepage if no investigation is returned', async () => { + const events = []; document.dispatchEvent = (e: Event) => { events.push(e as CustomEvent); @@ -187,7 +139,7 @@ describe('DOI Redirect page', () => { data: [], isLoading: false, }); - createWrapper(); + renderComponent(); expect(history.location.pathname).toBe('/datagateway'); expect(log.error).toHaveBeenCalledWith('Invalid DOI redirect'); @@ -200,9 +152,18 @@ describe('DOI Redirect page', () => { 'Cannot read the investigation. You may not have read access, or it may not be published yet.', }, }); + }); + + it('throws error and redirects to homepage if no instrument is returned', () => { + const events = []; + + document.dispatchEvent = (e: Event) => { + events.push(e as CustomEvent); + return true; + }; history.push('/doi-redirect/LILS/investigation/1'); - events = []; + (log.error as jest.Mock).mockClear(); (useInvestigation as jest.Mock).mockReturnValue({ data: mockInvestigationData, @@ -212,7 +173,7 @@ describe('DOI Redirect page', () => { data: [], isLoading: false, }); - createWrapper(); + renderComponent(); expect(history.location.pathname).toBe('/datagateway'); expect(log.error).toHaveBeenCalledWith('Invalid DOI redirect'); @@ -225,9 +186,18 @@ describe('DOI Redirect page', () => { 'Cannot read the investigation. You may not have read access, or it may not be published yet.', }, }); + }); + + it('throws error and redirects to homepage if no facility cycle is returned', () => { + const events = []; + + document.dispatchEvent = (e: Event) => { + events.push(e as CustomEvent); + return true; + }; history.push('/doi-redirect/LILS/investigation/1'); - events = []; + (log.error as jest.Mock).mockClear(); (useInstrumentsPaginated as jest.Mock).mockReturnValue({ data: mockInstrumentData, @@ -238,7 +208,7 @@ describe('DOI Redirect page', () => { isLoading: false, isIdle: false, }); - createWrapper(); + renderComponent(); expect(history.location.pathname).toBe('/datagateway'); expect(log.error).toHaveBeenCalledWith('Invalid DOI redirect'); diff --git a/packages/datagateway-dataview/src/page/pageContainer.component.test.tsx b/packages/datagateway-dataview/src/page/pageContainer.component.test.tsx index 710eed8aa..b3e4566c1 100644 --- a/packages/datagateway-dataview/src/page/pageContainer.component.test.tsx +++ b/packages/datagateway-dataview/src/page/pageContainer.component.test.tsx @@ -1,26 +1,24 @@ -import React from 'react'; -import { ReactWrapper, mount } from 'enzyme'; - +import * as React from 'react'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import { StateType } from '../state/app.types'; import { initialState as dgDataViewInitialState } from '../state/reducers/dgdataview.reducer'; import { dGCommonInitialState, + DownloadCartItem, readSciGatewayToken, - useCart, - ClearFiltersButton, } from 'datagateway-common'; - -import { LinearProgress } from '@mui/material'; -import { createLocation, createMemoryHistory, History } from 'history'; +import { + createLocation, + createMemoryHistory, + createPath, + History, +} from 'history'; import { Router } from 'react-router-dom'; import PageContainer, { paths } from './pageContainer.component'; import { checkInstrumentId, checkInvestigationId } from './idCheckFunctions'; -import axios from 'axios'; -import { act } from 'react-dom/test-utils'; -import { flushPromises } from '../setupTests'; +import axios, { AxiosResponse } from 'axios'; import { QueryClient, QueryClientProvider, @@ -28,6 +26,15 @@ import { useQueryClient, } from 'react-query'; import { Provider } from 'react-redux'; +import { + render, + type RenderResult, + screen, + waitFor, + within, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('loglevel'); jest.mock('./idCheckFunctions'); @@ -38,7 +45,6 @@ jest.mock('datagateway-common', () => { return { __esModule: true, ...originalModule, - useCart: jest.fn(() => ({ data: [] })), // mock table and cardview to opt out of rendering them in these tests as there's no need Table: jest.fn(() => 'MockedTable'), CardView: jest.fn(() => 'MockedCardView'), @@ -58,11 +64,14 @@ jest.mock('react-query', () => ({ describe('PageContainer - Tests', () => { let queryClient: QueryClient; let history: History; + let user: UserEvent; + let cartItems: DownloadCartItem[]; + let holder: HTMLElement; - const createWrapper = ( + const renderComponent = ( h: History = history, client: QueryClient = queryClient - ): ReactWrapper => { + ): RenderResult => { const state: StateType = { dgcommon: dGCommonInitialState, dgdataview: dgDataViewInitialState, @@ -73,7 +82,7 @@ describe('PageContainer - Tests', () => { }; const mockStore = configureStore([thunk]); const testStore = mockStore(state); - return mount( + return render( @@ -85,218 +94,248 @@ describe('PageContainer - Tests', () => { }; beforeEach(() => { - (axios.get as jest.Mock).mockImplementation((url: string) => { - if (url.includes('count')) { - return Promise.resolve({ data: 0 }); - } else { - return Promise.resolve({ data: [] }); - } - }); queryClient = new QueryClient(); history = createMemoryHistory({ initialEntries: ['/'], }); + user = userEvent.setup(); + + delete window.location; + window.location = new URL(`http://localhost/`); + + // below code keeps window.location in sync with history changes + // (needed because useUpdateQueryParam uses window.location not history) + const historyReplace = history.replace; + const historyReplaceSpy = jest.spyOn(history, 'replace'); + historyReplaceSpy.mockImplementation((args) => { + historyReplace(args); + if (typeof args === 'string') { + window.location = new URL(`http://localhost${args}`); + } else { + window.location = new URL(`http://localhost${createPath(args)}`); + } + }); + const historyPush = history.push; + const historyPushSpy = jest.spyOn(history, 'push'); + historyPushSpy.mockImplementation((args) => { + historyPush(args); + if (typeof args === 'string') { + window.location = new URL(`http://localhost${args}`); + } else { + window.location = new URL(`http://localhost${createPath(args)}`); + } + }); + + holder = document.createElement('div'); + holder.setAttribute('id', 'datagateway-search'); + document.body.appendChild(holder); + (useQueryClient as jest.Mock).mockReturnValue({ getQueryData: jest.fn(() => 0), }); + + (axios.get as jest.Mock).mockImplementation( + (url: string): Promise> => { + if (url.includes('count')) { + return Promise.resolve({ data: 0 }); + } + + if (url.includes('/user/cart')) { + return Promise.resolve({ + data: { cartItems }, + }); + } + + if (/.*\/\w+\/\d+$/.test(url)) { + // fetch entity information + return Promise.resolve({ + data: { + id: 1, + name: 'Name 1', + title: 'Title 1', + visitId: '1', + }, + }); + } + + return Promise.resolve({ data: [] }); + } + ); }); afterEach(() => { - (useCart as jest.Mock).mockClear(); + document.body.removeChild(holder); }); - it('displays the correct entity count', () => { + it('displays the correct entity count', async () => { history.replace(paths.toggle.investigation); (useQueryClient as jest.Mock).mockReturnValue({ getQueryData: jest.fn(() => 101), }); - const wrapper = createWrapper(); - - expect( - wrapper.find('[aria-label="view-count"]').first().find('h3').text() - ).toBe('app.results: 101'); - }); - - it('fetches cart on mount', () => { - (useCart as jest.Mock).mockReturnValueOnce({ - data: [ - { - entityId: 1, - entityType: 'dataset', - id: 1, - name: 'Test 1', - parentEntities: [], - }, - ], - }); - - createWrapper(); + renderComponent(); - expect(useCart).toHaveBeenCalled(); + expect(await screen.findByLabelText('view-count')).toHaveTextContent( + 'app.results: 101' + ); }); - it('opens search plugin when icon clicked', () => { - const wrapper = createWrapper(); + it('opens search plugin when icon clicked', async () => { + renderComponent(); - wrapper.find('[aria-label="view-search"]').last().simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'view-search' }) + ); expect(history.location.pathname).toBe('/search/data'); history.push('/browse/instrument'); - wrapper.find('[aria-label="view-search"]').last().simulate('click'); + + await user.click( + await screen.findByRole('button', { name: 'view-search' }) + ); expect(history.location.pathname).toBe('/search/isis'); history.push('/browse/proposal'); - wrapper.find('[aria-label="view-search"]').last().simulate('click'); + + await user.click( + await screen.findByRole('button', { name: 'view-search' }) + ); expect(history.location.pathname).toBe('/search/dls'); }); - it('opens download plugin when Download Cart clicked', () => { - const wrapper = createWrapper(); + it('opens download plugin when Download Cart clicked', async () => { + renderComponent(); - wrapper.find('[aria-label="app.cart_arialabel"]').last().simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'app.cart_arialabel' }) + ); expect(history.length).toBe(2); expect(history.location.pathname).toBe('/download'); }); - it('do not display loading bar loading false', () => { - const wrapper = createWrapper(); - - expect(wrapper.exists(LinearProgress)).toBeFalsy(); + it('do not display loading bar loading false', async () => { + renderComponent(); + await waitFor(() => { + expect(screen.queryByRole('progressbar')).toBeNull(); + }); }); - it('display loading bar when loading true', () => { - (useIsFetching as jest.Mock).mockReturnValueOnce(1); - const wrapper = createWrapper(); - - expect(wrapper.exists(LinearProgress)).toBeTruthy(); + it('display loading bar when loading true', async () => { + (useIsFetching as jest.Mock).mockReturnValue(1); + renderComponent(); + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); + (useIsFetching as jest.Mock).mockReturnValue(0); }); - it('display clear filters button and clear for filters onClick', () => { + it('display clear filters button and clear for filters onClick', async () => { history.replace( '/browse/investigation?filters=%7B"title"%3A%7B"value"%3A"spend"%2C"type"%3A"include"%7D%7D' ); - const wrapper = createWrapper(); - - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(false); + renderComponent(); - wrapper - .find('[data-testid="clear-filters-button"]') - .last() - .simulate('click'); - - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ); - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(true); + expect( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); expect(history.location.search).toEqual('?'); }); - it('display clear filters button and clear for filters onClick (/my-data/DLS)', () => { + it('display clear filters button and clear for filters onClick (/my-data/DLS)', async () => { const dateNow = `${new Date(Date.now()).toISOString().split('T')[0]}`; history.replace( '/my-data/DLS?filters=%7B"startDate"%3A%7B"endDate"%3A" ' + dateNow + - '"%7D%2C"title"%3A%7B"value"%3A"test"%2C"type"%3A"include"%7D%7D&sort=%7B"startDate"%3A"desc"%7D' + '"%7D%2C"title"%3A%7B"value"%3A"test"%2C"type"%3A"include"%7D%7D&sort=%7B%22startDate%22%3A%22desc%22%7D' ); const response = { username: 'SomePerson' }; (readSciGatewayToken as jest.Mock).mockReturnValue(response); - const wrapper = createWrapper(); - - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(false); - - wrapper - .find('[data-testid="clear-filters-button"]') - .last() - .simulate('click'); - - wrapper.update(); - - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(true); + renderComponent(); + await user.click( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ); + expect( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); expect(history.location.search).toEqual( '?filters=%7B%22startDate%22%3A%7B%22endDate%22%3A%22' + dateNow + - '%22%7D%7D' + '%22%7D%7D&sort=%7B%22startDate%22%3A%22desc%22%7D' ); (readSciGatewayToken as jest.Mock).mockClear(); }); - it('display disabled clear filters button', () => { + it('display disabled clear filters button', async () => { history.replace(paths.toggle.investigation); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(true); + expect( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); }); it('display filter warning on datafile table', async () => { history.replace('/browse/investigation/1/dataset/25/datafile'); (checkInvestigationId as jest.Mock).mockResolvedValueOnce(true); - const wrapper = createWrapper(); + renderComponent(); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); expect( - wrapper.find('[aria-label="filter-message"]').first().text() - ).toEqual('loading.filter_message'); + await screen.findByText('loading.filter_message') + ).toBeInTheDocument(); }); - it('switches view button display name when clicked', () => { + it('switches view button display name when clicked', async () => { history.replace(paths.toggle.investigation); - const wrapper = createWrapper(); + renderComponent(); - expect( - wrapper.find('[aria-label="page view app.view_cards"]').exists() - ).toBeTruthy(); - expect( - wrapper.find('[aria-label="page view app.view_cards"]').first().text() - ).toEqual('app.view_cards'); - - // Click view button - wrapper - .find('[aria-label="page view app.view_cards"]') - .last() - .simulate('click'); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'page view app.view_cards' }) + ); // Check that the text on the button has changed expect( - wrapper.find('[aria-label="page view app.view_table"]').first().text() - ).toEqual('app.view_table'); + await screen.findByRole('button', { name: 'page view app.view_table' }) + ).toBeInTheDocument(); }); - it('displays role selector when on My Data route', () => { + it('displays role selector when on My Data route', async () => { history.replace(paths.myData.root); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.find('#role-selector').exists()).toBeTruthy(); + expect( + await screen.findByRole('button', { name: 'my_data_table.role_selector' }) + ).toBeInTheDocument(); }); - it('display filter warning on toggle table', () => { + it('display filter warning on toggle table', async () => { history.replace(`${paths.toggle.investigation}?view=table`); - const wrapper = createWrapper(); + renderComponent(); - expect( - wrapper.find('[aria-label="filter-message"]').first().text() - ).toEqual('loading.filter_message'); + expect(await screen.findByLabelText('filter-message')).toHaveTextContent( + 'loading.filter_message' + ); }); - it('do not display filter warning on toggle card', () => { + it('do not display filter warning on toggle card', async () => { history.replace(`${paths.toggle.investigation}?view=card`); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.exists('[aria-label="filter-message"]')).toBeFalsy(); + await waitFor(() => { + expect(screen.queryByLabelText('filter-message')).toBeNull(); + }); }); it('do not use StyledRouting component on landing pages', async () => { @@ -306,129 +345,128 @@ describe('PageContainer - Tests', () => { }); history.replace(`${paths.studyHierarchy.landing.isisStudyLanding}`); - const wrapper = createWrapper(); + renderComponent(); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - - expect(wrapper.exists('StyledRouting')).toBeFalsy(); + expect(screen.queryByTestId('styled-routing')).toBeNull(); }); it('set view to card if cardview stored in localstorage', () => { localStorage.setItem('dataView', 'card'); history.replace(paths.toggle.investigation); - createWrapper(); + renderComponent(); expect(history.location.search).toBe('?view=card'); + + localStorage.removeItem('dataView'); }); - it('displays warning label when browsing data anonymously', () => { + it('displays warning label when browsing data anonymously', async () => { const response = { username: 'anon/anon' }; - (readSciGatewayToken as jest.Mock).mockReturnValueOnce(response); + (readSciGatewayToken as jest.Mock).mockReturnValue(response); - const wrapper = createWrapper(); + renderComponent(); expect( - wrapper.find('[aria-label="open-data-warning"]').exists() - ).toBeTruthy(); + await screen.findByLabelText('open-data-warning') + ).toBeInTheDocument(); }); - it('displays warning label when browsing study hierarchy', () => { + it('displays warning label when browsing study hierarchy', async () => { history.replace(paths.studyHierarchy.toggle.isisStudy); const response = { username: 'SomePerson' }; (readSciGatewayToken as jest.Mock).mockReturnValueOnce(response); - const wrapper = createWrapper(); + renderComponent(); expect( - wrapper.find('[aria-label="open-data-warning"]').exists() - ).toBeTruthy(); + await screen.findByLabelText('open-data-warning') + ).toBeInTheDocument(); }); - it('does not display warning label when logged in', () => { + it('does not display warning label when logged in', async () => { const response = { username: 'SomePerson' }; (readSciGatewayToken as jest.Mock).mockReturnValueOnce(response); - const wrapper = createWrapper(); + renderComponent(); - expect( - wrapper.find('[aria-label="open-data-warning"]').exists() - ).toBeFalsy(); + await waitFor(() => { + expect(screen.queryByLabelText('open-data-warning')).toBeNull(); + }); }); - it('shows SelectionAlert banner when item selected', () => { + it('shows SelectionAlert banner when item selected', async () => { // Supply data to make SelectionAlert display - (useCart as jest.Mock).mockReturnValueOnce({ - data: [ - { - entityId: 1, - entityType: 'dataset', - id: 1, - name: 'Test 1', - parentEntities: [], - }, - ], - }); - const wrapper = createWrapper(); + cartItems = [ + { + entityId: 1, + entityType: 'dataset', + id: 1, + name: 'Test 1', + parentEntities: [], + }, + ]; + renderComponent(); - expect(wrapper.exists('[aria-label="selection-alert"]')).toBeTruthy(); + expect(await screen.findByLabelText('selection-alert')).toBeInTheDocument(); }); - it('does not show SelectionAlert banner when no items are selected', () => { - (useCart as jest.Mock).mockReturnValueOnce({ - data: [], - }); - const wrapper = createWrapper(); + it('does not show SelectionAlert banner when no items are selected', async () => { + renderComponent(); - expect(wrapper.exists('[aria-label="selection-alert"]')).toBeFalsy(); + await waitFor(() => { + expect(screen.queryByLabelText('selection-alert')).toBeNull(); + }); }); - it('opens download plugin when link in SelectionAlert clicked', () => { + it('opens download plugin when link in SelectionAlert clicked', async () => { // Supply data to make SelectionAlert display - (useCart as jest.Mock).mockReturnValueOnce({ - data: [ - { - entityId: 1, - entityType: 'dataset', - id: 1, - name: 'Test 1', - parentEntities: [], - }, - ], - }); - const wrapper = createWrapper(); + cartItems = [ + { + entityId: 1, + entityType: 'dataset', + id: 1, + name: 'Test 1', + parentEntities: [], + }, + ]; + renderComponent(); - wrapper - .find('[aria-label="selection-alert-link"]') - .first() - .simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'selection-alert-link' }) + ); expect(history.location.pathname).toBe('/download'); }); - it('passes correct landing page entities to breadcrumbs', () => { - history.replace(paths.toggle.isisInvestigation); - let wrapper = createWrapper(); + it('shows breadcrumb according to the current path', async () => { + history.replace('/browse/instrument/1/facilityCycle/1/investigation'); + renderComponent(); - expect(wrapper.find('PageBreadcrumbs').prop('landingPageEntities')).toEqual( - ['investigation', 'dataset'] - ); - - history.replace(paths.studyHierarchy.toggle.isisInvestigation); - wrapper = createWrapper(); + expect(await screen.findByText('breadcrumbs.home')).toBeInTheDocument(); + const baseBreadcrumb = screen.getByTestId('Breadcrumb-base'); + expect(baseBreadcrumb).toHaveAttribute('href', '/browse/instrument'); + expect(baseBreadcrumb).toHaveTextContent('breadcrumbs.instrument'); - expect(wrapper.find('PageBreadcrumbs').prop('landingPageEntities')).toEqual( - ['study', 'investigation', 'dataset'] + const breadcrumbs = screen.getAllByTestId(/^Breadcrumb-hierarchy-\d+$/); + expect(breadcrumbs[0]).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle' ); + expect(within(breadcrumbs[0]).getByText('Name 1')).toBeInTheDocument(); + expect(within(breadcrumbs[1]).getByText('Name 1')).toBeInTheDocument(); + + expect( + within(screen.getByTestId('Breadcrumb-last')).getByText( + 'breadcrumbs.investigation' + ) + ).toBeInTheDocument(); }); it('does not fetch cart when on homepage (cart request errors when user is viewing homepage unauthenticated)', () => { history.replace(paths.homepage); - createWrapper(); + renderComponent(); - expect(useCart).not.toHaveBeenCalled(); + expect(axios.get).not.toHaveBeenCalledWith('/user/cart'); }); }); diff --git a/packages/datagateway-dataview/src/page/pageContainer.component.tsx b/packages/datagateway-dataview/src/page/pageContainer.component.tsx index 7b5d4d7b5..c4406b1cd 100644 --- a/packages/datagateway-dataview/src/page/pageContainer.component.tsx +++ b/packages/datagateway-dataview/src/page/pageContainer.component.tsx @@ -383,7 +383,7 @@ const StyledRouting = (props: { tablePaperHeight ); return ( -
    +
    {viewStyle !== 'card' && displayFilterMessage && ( { let state: StateType; let history: History; - const createTableWrapper = ( - path: string, - loggedInAnonymously?: boolean - ): ReactWrapper => { - const mockStore = configureStore([thunk]); - const client = new QueryClient(); - history.push(path); - return mount( - - - - - - - - ); - }; - - const createCardWrapper = (path: string): ReactWrapper => { - const mockStore = configureStore([thunk]); - const client = new QueryClient(); - history.push(path); - return mount( - - - - - - - - ); - }; - - const createLandingWrapper = (path: string): ReactWrapper => { + function Wrapper({ children }: { children: React.ReactNode }): JSX.Element { const mockStore = configureStore([thunk]); - const client = new QueryClient(); - history.push(path); - return mount( + const client = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + return ( - - - + {children} ); - }; + } beforeEach(() => { history = createMemoryHistory(); @@ -206,195 +133,572 @@ describe('PageTable', () => { describe('Generic', () => { it('renders PageTable correctly', () => { - const wrapper = createTableWrapper('/'); - expect(wrapper.exists(Link)).toBe(true); + history.push('/'); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect( + screen.getByRole('link', { name: 'Browse investigations' }) + ).toHaveAttribute('href', '/browse/investigation'); }); it('renders PageCard correctly', () => { - const wrapper = createCardWrapper('/'); - expect(wrapper.exists(Link)).toBe(true); + history.push('/'); + + render( + , + { wrapper: Wrapper } + ); + + expect( + screen.getByRole('link', { name: 'Browse investigations' }) + ).toHaveAttribute('href', '/browse/investigation?view=card'); }); - it('renders InvestigationTable for generic investigations route', () => { - const wrapper = createTableWrapper(genericRoutes['investigations']); - expect(wrapper.exists(InvestigationTable)).toBe(true); + it('renders InvestigationTable for generic investigations route', async () => { + history.push(genericRoutes['investigations']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.visit_id') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.doi') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.instrument') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); }); - it('renders InvestigationCardView for generic investigations route', () => { - const wrapper = createCardWrapper(genericRoutes['investigations']); - expect(wrapper.exists(InvestigationCardView)).toBe(true); + it('renders InvestigationCardView for generic investigations route', async () => { + history.push(genericRoutes.investigations); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('investigation-card-view') + ).toBeInTheDocument(); }); - it('renders DatasetTable for generic datasets route', () => { - const wrapper = createTableWrapper(genericRoutes['datasets']); - expect(wrapper.exists(DatasetTable)).toBe(true); + it('renders DatasetTable for generic datasets route', async () => { + history.push(genericRoutes['datasets']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect(await findColumnHeaderByName('datasets.name')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.datafile_count') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.create_time') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.modified_time') + ).toBeInTheDocument(); }); - it('renders DatasetCardView for generic datasets route', () => { - const wrapper = createCardWrapper(genericRoutes['datasets']); - expect(wrapper.exists(DatasetCardView)).toBe(true); + it('renders DatasetCardView for generic datasets route', async () => { + history.push(genericRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('dataset-card-view') + ).toBeInTheDocument(); }); it('renders DatafileTable for generic datafiles route', async () => { - const wrapper = createTableWrapper(genericRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DatafileTable)).toBe(true); + history.push(genericRoutes['datafiles']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect( + await findColumnHeaderByName('datafiles.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.location') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.modified_time') + ).toBeInTheDocument(); }); it('does not render DatafileTable for incorrect generic datafiles route', async () => { (checkInvestigationId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createTableWrapper(genericRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DatafileTable)).toBe(false); + history.push(genericRoutes['datafiles']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); }); describe('ISIS', () => { - it('renders ISISMyDataTable for ISIS my data route', () => { - const wrapper = createTableWrapper(ISISRoutes['mydata'], false); - expect(wrapper.exists(ISISMyDataTable)).toBe(true); + it('renders ISISMyDataTable for ISIS my data route', async () => { + history.push(ISISRoutes['mydata']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.doi') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.visit_id') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.instrument') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); }); it('redirects to login page when not signed in (ISISMyDataTable) ', () => { - const wrapper = createTableWrapper(ISISRoutes['mydata'], true); - expect(wrapper.exists(ISISMyDataTable)).toBe(false); - expect(history.length).toBe(2); + history.push(ISISRoutes['mydata']); + + render( + , + { + wrapper: Wrapper, + } + ); + expect(history.location.pathname).toBe('/login'); }); - it('renders ISISInstrumentsTable for ISIS instruments route', () => { - const wrapper = createTableWrapper(ISISRoutes['instruments']); - expect(wrapper.exists(ISISInstrumentsTable)).toBe(true); + it('renders ISISInstrumentsTable for ISIS instruments route', async () => { + history.push(ISISRoutes['instruments']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect( + await findColumnHeaderByName('instruments.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('instruments.type') + ).toBeInTheDocument(); }); - it('renders ISISInstrumentsCardView for ISIS instruments route', () => { - const wrapper = createCardWrapper(ISISRoutes['instruments']); - expect(wrapper.exists(ISISInstrumentsCardView)).toBe(true); + it('renders ISISInstrumentsCardView for ISIS instruments route', async () => { + history.push(ISISRoutes.instruments); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('isis-instruments-card-view') + ).toBeInTheDocument(); }); - it('renders ISISFacilityCyclesTable for ISIS facilityCycles route', () => { - const wrapper = createTableWrapper(ISISRoutes['facilityCycles']); - expect(wrapper.exists(ISISFacilityCyclesTable)).toBe(true); + it('renders ISISFacilityCyclesTable for ISIS facilityCycles route', async () => { + history.push(ISISRoutes['facilityCycles']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect( + await findColumnHeaderByName('facilitycycles.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('facilitycycles.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('facilitycycles.end_date') + ).toBeInTheDocument(); }); - it('renders ISISFacilityCyclesCardView for ISIS facilityCycles route', () => { - const wrapper = createCardWrapper(ISISRoutes['facilityCycles']); - expect(wrapper.exists(ISISFacilityCyclesCardView)).toBe(true); + it('renders ISISFacilityCyclesCardView for ISIS facilityCycles route', async () => { + history.push(ISISRoutes.facilityCycles); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('isis-facility-card-view') + ).toBeInTheDocument(); }); - it('renders ISISInvestigationsTable for ISIS investigations route', () => { - const wrapper = createTableWrapper(ISISRoutes['investigations']); - expect(wrapper.exists(ISISInvestigationsTable)).toBe(true); + it('renders ISISInvestigationsTable for ISIS investigations route', async () => { + history.push(ISISRoutes['investigations']); + + render( + , + { + wrapper: Wrapper, + } + ); + + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.doi') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.principal_investigators') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); }); - it('renders ISISInvestigationsCardView for ISIS investigations route', () => { - const wrapper = createCardWrapper(ISISRoutes['investigations']); - expect(wrapper.exists(ISISInvestigationsCardView)).toBe(true); + it('renders ISISInvestigationsCardView for ISIS investigations route', async () => { + history.push(ISISRoutes.investigations); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('isis-investigations-card-view') + ).toBeInTheDocument(); }); it('renders ISISInvestigationLanding for ISIS investigation route', async () => { - const wrapper = createLandingWrapper( - ISISRoutes['landing']['investigation'] + history.push(ISISRoutes['landing']['investigation']); + + render( + , + { + wrapper: Wrapper, + } ); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISInvestigationLanding)).toBe(true); + + expect( + await screen.findByLabelText('branding-title') + ).toBeInTheDocument(); }); it('does not render ISISInvestigationLanding for incorrect ISIS investigation route', async () => { (checkInstrumentAndFacilityCycleId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createLandingWrapper( - ISISRoutes['landing']['investigation'] + + history.push(ISISRoutes.landing.investigation); + + render( + , + { wrapper: Wrapper } ); + await act(async () => { await flushPromises(); - wrapper.update(); }); - expect(wrapper.exists(ISISInvestigationLanding)).toBe(false); + + expect(screen.getByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISDatasetsTable for ISIS datasets route', async () => { - const wrapper = createTableWrapper(ISISRoutes['datasets']); + history.push(ISISRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + await act(async () => { await flushPromises(); - wrapper.update(); }); - expect(wrapper.exists(ISISDatasetsTable)).toBe(true); + + expect(await findColumnHeaderByName('datasets.name')).toBeInTheDocument(); + expect(await findColumnHeaderByName('datasets.size')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.create_time') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.modified_time') + ).toBeInTheDocument(); }); it('does not render ISISDatasetsTable for incorrect ISIS datasets route', async () => { (checkInstrumentAndFacilityCycleId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createTableWrapper(ISISRoutes['datasets']); + + history.push(ISISRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + await act(async () => { await flushPromises(); - wrapper.update(); }); - expect(wrapper.exists(ISISDatasetsTable)).toBe(false); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISDatasetsCardview for ISIS datasets route', async () => { - const wrapper = createCardWrapper(ISISRoutes['datasets']); + history.push(ISISRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + await act(async () => { await flushPromises(); - wrapper.update(); }); - expect(wrapper.exists(ISISDatasetsCardView)).toBe(true); + + expect(screen.getByTestId('isis-datasets-card-view')).toBeInTheDocument(); }); it('does not render ISISDatasetsCardView for incorrect ISIS datasets route', async () => { (checkInstrumentAndFacilityCycleId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createCardWrapper(ISISRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetsCardView)).toBe(false); + + history.push(ISISRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISDatasetLanding for ISIS dataset route', async () => { - const wrapper = createLandingWrapper(ISISRoutes['landing']['dataset']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetLanding)).toBe(true); + history.push(ISISRoutes.landing.dataset); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('isis-dataset-landing') + ).toBeInTheDocument(); }); it('does not render ISISDatasetLanding for incorrect ISIS dataset route', async () => { (checkInstrumentAndFacilityCycleId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createLandingWrapper(ISISRoutes['landing']['dataset']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetLanding)).toBe(false); + + history.push(ISISRoutes.landing.dataset); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISDatafilesTable for ISIS datafiles route', async () => { - const wrapper = createTableWrapper(ISISRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatafilesTable)).toBe(true); + history.push(ISISRoutes.datafiles); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await findColumnHeaderByName('datafiles.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.location') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.modified_time') + ).toBeInTheDocument(); }); it('does not render ISISDatafilesTable for incorrect ISIS datafiles route', async () => { @@ -404,86 +708,208 @@ describe('PageTable', () => { (checkInvestigationId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createTableWrapper(ISISRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatafilesTable)).toBe(false); + + history.push(ISISRoutes.datafiles); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); }); describe('ISIS Study Hierarchy', () => { - it('renders ISISInstrumentsTable for ISIS instruments route in Study Hierarchy', () => { - const wrapper = createTableWrapper( - ISISStudyHierarchyRoutes['instruments'] + it('renders ISISInstrumentsTable for ISIS instruments route in Study Hierarchy', async () => { + history.push(ISISStudyHierarchyRoutes.instruments); + + render( + , + { wrapper: Wrapper } ); - expect(wrapper.exists(ISISInstrumentsTable)).toBe(true); + + expect( + await findColumnHeaderByName('instruments.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('instruments.type') + ).toBeInTheDocument(); }); - it('renders ISISInstrumentsCardView for ISIS instruments route in Study Hierarchy', () => { - const wrapper = createCardWrapper( - ISISStudyHierarchyRoutes['instruments'] + it('renders ISISInstrumentsCardView for ISIS instruments route in Study Hierarchy', async () => { + history.push(ISISStudyHierarchyRoutes.instruments); + + render( + , + { wrapper: Wrapper } ); - expect(wrapper.exists(ISISInstrumentsCardView)).toBe(true); + + expect( + await screen.findByTestId('isis-instruments-card-view') + ).toBeInTheDocument(); }); - it('renders ISISStudiesTable for ISIS studies route in Study Hierarchy', () => { - const wrapper = createTableWrapper(ISISStudyHierarchyRoutes['studies']); - expect(wrapper.exists(ISISStudiesTable)).toBe(true); + it('renders ISISStudiesTable for ISIS studies route in Study Hierarchy', async () => { + history.push(ISISStudyHierarchyRoutes['studies']); + + render( + , + { wrapper: Wrapper } + ); + + expect(await findColumnHeaderByName('studies.name')).toBeInTheDocument(); + expect(await findColumnHeaderByName('studies.title')).toBeInTheDocument(); + expect(await findColumnHeaderByName('studies.pid')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('studies.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('studies.end_date') + ).toBeInTheDocument(); }); - it('renders ISISStudiesCardView for ISIS studies route in Study Hierarchy', () => { - const wrapper = createCardWrapper(ISISStudyHierarchyRoutes['studies']); - expect(wrapper.exists(ISISStudiesCardView)).toBe(true); + it('renders ISISStudiesCardView for ISIS studies route in Study Hierarchy', async () => { + history.push(ISISStudyHierarchyRoutes.studies); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('isis-studies-card-view') + ).toBeInTheDocument(); }); it('renders ISISStudyLanding for ISIS study route for studyHierarchy', async () => { - const wrapper = createLandingWrapper( - ISISStudyHierarchyRoutes['landing']['study'] + history.push(ISISStudyHierarchyRoutes.landing.study); + + render( + , + { wrapper: Wrapper } ); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISStudyLanding)).toBe(true); + + expect( + await screen.findByTestId('isis-study-landing') + ).toBeInTheDocument(); }); it('does not render ISISStudyLanding for incorrect ISIS study route for studyHierarchy', async () => { (checkInstrumentId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createLandingWrapper( - ISISStudyHierarchyRoutes['landing']['study'] + + history.push(ISISStudyHierarchyRoutes.landing.study); + + render( + , + { wrapper: Wrapper } ); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISStudyLanding)).toBe(false); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); - it('renders ISISInvestigationsTable for ISIS investigations route in Study Hierarchy', () => { - const wrapper = createTableWrapper( - ISISStudyHierarchyRoutes['investigations'] + it('renders ISISInvestigationsTable for ISIS investigations route in Study Hierarchy', async () => { + history.push(ISISStudyHierarchyRoutes.investigations); + + render( + , + { wrapper: Wrapper } ); - expect(wrapper.exists(ISISInvestigationsTable)).toBe(true); + + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.doi') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.principal_investigators') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); }); - it('renders ISISInvestigationsCardView for ISIS investigations route in Study Hierarchy', () => { - const wrapper = createCardWrapper( - ISISStudyHierarchyRoutes['investigations'] + it('renders ISISInvestigationsCardView for ISIS investigations route in Study Hierarchy', async () => { + history.push(ISISStudyHierarchyRoutes.investigations); + + render( + , + { wrapper: Wrapper } ); - expect(wrapper.exists(ISISInvestigationsCardView)).toBe(true); + + expect( + await screen.findByTestId('isis-investigations-card-view') + ).toBeInTheDocument(); }); it('renders ISISDatasetsTable for ISIS datasets route in Study Hierarchy', async () => { - const wrapper = createTableWrapper(ISISStudyHierarchyRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetsTable)).toBe(true); + history.push(ISISStudyHierarchyRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect(await findColumnHeaderByName('datasets.name')).toBeInTheDocument(); + expect(await findColumnHeaderByName('datasets.size')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.create_time') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.modified_time') + ).toBeInTheDocument(); }); it('does not render ISISDatasetsTable for incorrect ISIS datasets route in Study Hierarchy', async () => { @@ -493,23 +919,36 @@ describe('PageTable', () => { (checkStudyId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createTableWrapper(ISISStudyHierarchyRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetsTable)).toBe(false); + + history.push(ISISStudyHierarchyRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISInvestigationLanding for ISIS investigation route for studyHierarchy', async () => { - const wrapper = createLandingWrapper( - ISISStudyHierarchyRoutes['landing']['investigation'] + history.push(ISISStudyHierarchyRoutes.landing.investigation); + + render( + , + { wrapper: Wrapper } ); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISInvestigationLanding)).toBe(true); + + expect( + await screen.findByTestId('isis-investigation-landing') + ).toBeInTheDocument(); }); it('does not render ISISInvestigationLanding for incorrect ISIS investigation route for studyHierarchy', async () => { @@ -519,23 +958,36 @@ describe('PageTable', () => { (checkStudyId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createLandingWrapper( - ISISStudyHierarchyRoutes['landing']['investigation'] + + history.push(ISISStudyHierarchyRoutes.landing.investigation); + + render( + , + { wrapper: Wrapper } ); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISInvestigationLanding)).toBe(false); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISDatasetsCardView for ISIS datasets route in Study Hierarchy', async () => { - const wrapper = createCardWrapper(ISISStudyHierarchyRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetsCardView)).toBe(true); + history.push(ISISStudyHierarchyRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('isis-datasets-card-view') + ).toBeInTheDocument(); }); it('does not render ISISDatasetsCardView for incorrect ISIS datasets route in Study Hierarchy', async () => { @@ -545,23 +997,36 @@ describe('PageTable', () => { (checkStudyId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createCardWrapper(ISISStudyHierarchyRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetsCardView)).toBe(false); + + history.push(ISISStudyHierarchyRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISDatasetLanding for ISIS dataset route for studyHierarchy', async () => { - const wrapper = createLandingWrapper( - ISISStudyHierarchyRoutes['landing']['dataset'] + history.push(ISISStudyHierarchyRoutes.landing.dataset); + + render( + , + { wrapper: Wrapper } ); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetLanding)).toBe(true); + + expect( + await screen.findByTestId('isis-dataset-landing') + ).toBeInTheDocument(); }); it('does not render ISISDatasetLanding for incorrect ISIS dataset route for studyHierarchy', async () => { @@ -571,23 +1036,45 @@ describe('PageTable', () => { (checkStudyId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createLandingWrapper( - ISISStudyHierarchyRoutes['landing']['dataset'] + + history.push(ISISStudyHierarchyRoutes.landing.dataset); + + render( + , + { wrapper: Wrapper } ); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatasetLanding)).toBe(false); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders ISISDatafilesTable for ISIS datafiles route in Study Hierarchy', async () => { - const wrapper = createTableWrapper(ISISStudyHierarchyRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatafilesTable)).toBe(true); + history.push(ISISStudyHierarchyRoutes.datafiles); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await findColumnHeaderByName('datafiles.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.location') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.modified_time') + ).toBeInTheDocument(); }); it('does not render ISISDatafilesTable for incorrect ISIS datafiles route in Study Hierarchy', async () => { @@ -600,97 +1087,256 @@ describe('PageTable', () => { (checkInvestigationId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createTableWrapper(ISISStudyHierarchyRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(ISISDatafilesTable)).toBe(false); + + history.push(ISISStudyHierarchyRoutes.datafiles); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); }); describe('DLS', () => { - it('renders DLSMyDataTable for DLS my data route', () => { - const wrapper = createTableWrapper(DLSRoutes['mydata'], false); - expect(wrapper.exists(DLSMyDataTable)).toBe(true); + it('renders DLSMyDataTable for DLS my data route', async () => { + history.push(DLSRoutes.mydata); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.visit_id') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.dataset_count') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.instrument') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); }); it('redirects to login page when not signed in (DLSMyDataTable) ', () => { - const wrapper = createTableWrapper(DLSRoutes['mydata'], true); - expect(wrapper.exists(DLSMyDataTable)).toBe(false); - expect(history.length).toBe(2); + history.push(DLSRoutes.mydata); + + render( + , + { wrapper: Wrapper } + ); + expect(history.location.pathname).toBe('/login'); }); - it('renders DLSProposalTable for DLS proposal route', () => { - const wrapper = createTableWrapper(DLSRoutes['proposals']); - expect(wrapper.exists(DLSProposalsTable)).toBe(true); + it('renders DLSProposalTable for DLS proposal route', async () => { + history.push(DLSRoutes.proposals); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.name') + ).toBeInTheDocument(); }); - it('renders DLSProposalCardView for DLS proposal route', () => { - const wrapper = createCardWrapper(DLSRoutes['proposals']); - expect(wrapper.exists(DLSProposalsCardView)).toBe(true); + it('renders DLSProposalCardView for DLS proposal route', async () => { + history.push(DLSRoutes.proposals); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('dls-proposals-card-view') + ).toBeInTheDocument(); }); - it('renders DLSVisitsTable for DLS investigations route', () => { - const wrapper = createTableWrapper(DLSRoutes['investigations']); - expect(wrapper.exists(DLSVisitsTable)).toBe(true); + it('renders DLSVisitsTable for DLS investigations route', async () => { + history.push(DLSRoutes.investigations); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await findColumnHeaderByName('investigations.visit_id') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.dataset_count') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.instrument') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); }); - it('renders DLSVisitsCardView for DLS investigations route', () => { - const wrapper = createCardWrapper(DLSRoutes['investigations']); - expect(wrapper.exists(DLSVisitsCardView)).toBe(true); + it('renders DLSVisitsCardView for DLS investigations route', async () => { + history.push(DLSRoutes.investigations); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('dls-visits-card-view') + ).toBeInTheDocument(); }); it('renders DLSDatasetsTable for DLS datasets route', async () => { - const wrapper = createTableWrapper(DLSRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DLSDatasetsTable)).toBe(true); + history.push(DLSRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect(await findColumnHeaderByName('datasets.name')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.datafile_count') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.create_time') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.modified_time') + ).toBeInTheDocument(); }); it('does not render DLSDatasetsTable for incorrect DLS datasets route', async () => { (checkProposalName as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createTableWrapper(DLSRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DLSDatasetsTable)).toBe(false); + + history.push(DLSRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders DLSDatasetsCardView for DLS datasets route', async () => { - const wrapper = createCardWrapper(DLSRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DLSDatasetsCardView)).toBe(true); + history.push(DLSRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await screen.findByTestId('dls-datasets-card-view') + ).toBeInTheDocument(); }); it('does not render DLSDatasetsCardView for incorrect DLS datasets route', async () => { (checkProposalName as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createCardWrapper(DLSRoutes['datasets']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DLSDatasetsCardView)).toBe(false); + + history.push(DLSRoutes.datasets); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); it('renders DLSDatafilesTable for DLS datafiles route', async () => { - const wrapper = createTableWrapper(DLSRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DLSDatafilesTable)).toBe(true); + history.push(DLSRoutes.datafiles); + + render( + , + { wrapper: Wrapper } + ); + + expect( + await findColumnHeaderByName('datafiles.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.location') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.create_time') + ).toBeInTheDocument(); }); it('does not render DLSDatafilesTable for incorrect DLS datafiles route', async () => { @@ -700,12 +1346,19 @@ describe('PageTable', () => { (checkInvestigationId as jest.Mock).mockImplementation(() => Promise.resolve(false) ); - const wrapper = createTableWrapper(DLSRoutes['datafiles']); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(wrapper.exists(DLSDatafilesTable)).toBe(false); + + history.push(DLSRoutes.datafiles); + + render( + , + { wrapper: Wrapper } + ); + + expect(await screen.findByText('loading.oops')).toBeInTheDocument(); }); }); }); diff --git a/packages/datagateway-dataview/src/page/translatedHomePage.component.test.tsx b/packages/datagateway-dataview/src/page/translatedHomePage.component.test.tsx index fbd425f9a..3437aa8a3 100644 --- a/packages/datagateway-dataview/src/page/translatedHomePage.component.test.tsx +++ b/packages/datagateway-dataview/src/page/translatedHomePage.component.test.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; import { TranslatedHomePage as HomePage, TranslatedHomePageStateProps, } from './translatedHomePage.component'; +import { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; describe('HomePage', () => { let props: TranslatedHomePageStateProps; @@ -15,7 +16,11 @@ describe('HomePage', () => { }); it('translated homepage renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { asFragment } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-dataview/src/setupTests.ts b/packages/datagateway-dataview/src/setupTests.ts index 223b5685c..ef0657630 100644 --- a/packages/datagateway-dataview/src/setupTests.ts +++ b/packages/datagateway-dataview/src/setupTests.ts @@ -3,18 +3,14 @@ import '@testing-library/jest-dom'; // Blob implementation in jsdom is not complete (https://github.com/jsdom/jsdom/issues/2555) // blob-polyfill fills in the gap import 'blob-polyfill'; -import Enzyme from 'enzyme'; -import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; import { Action } from 'redux'; import { StateType } from './state/app.types'; import { initialState as dgDataViewInitialState } from './state/reducers/dgdataview.reducer'; import { dGCommonInitialState } from 'datagateway-common'; +import { screen, within } from '@testing-library/react'; jest.setTimeout(15000); -// Unofficial React 17 Enzyme adapter -Enzyme.configure({ adapter: new Adapter() }); - function noOp(): void { // required as work-around for enzyme/jest environment not implementing window.URL.createObjectURL method } @@ -74,3 +70,65 @@ export const applyDatePickerWorkaround = (): void => { export const cleanupDatePickerWorkaround = (): void => { delete window.matchMedia; }; + +/** + * Finds the index of the column with the given name. + */ +export const findColumnIndexByName = async ( + columnName: string +): Promise => { + const columnHeaders = await screen.findAllByRole('columnheader'); + return columnHeaders.findIndex( + (header) => within(header).queryByText(columnName) !== null + ); +}; + +/** + * Find the header row of the table currently rendered. + * This assumes that the first row is always the header row. + */ +export const findColumnHeaderByName = async ( + name: string +): Promise => { + const columnHeaders = await screen.findAllByRole('columnheader'); + const header = columnHeaders.find( + (header) => within(header).queryByText(name) !== null + ); + if (!header) { + throw new Error(`Cannot find column header by name: ${name}`); + } + return header; +}; + +/** + * Finds all table rows except the header row. + */ +export const findAllRows = async (): Promise => + (await screen.findAllByRole('row')).slice(1); + +/** + * Find the table row at the given index. This assumes the first table row is always the header row. + * + * @param index The index of the table row, igoring the header row. For example, if the table has 2 rows and the first row is the header row, + * the actual row that contains the data is considered the first row, and has an index of 0. + */ +export const findRowAt = async (index: number): Promise => { + const rows = await screen.findAllByRole('row'); + const row = rows[index + 1]; + if (!row) { + throw new Error(`Cannot find row at index ${index}`); + } + return row; +}; + +export const findCellInRow = ( + row: HTMLElement, + { columnIndex }: { columnIndex: number } +): HTMLElement => { + const cells = within(row).getAllByRole('gridcell'); + const cell = cells[columnIndex]; + if (!cell) { + throw new Error(`Cannot find cell in row.`); + } + return cell; +}; diff --git a/packages/datagateway-dataview/src/utils.test.tsx b/packages/datagateway-dataview/src/utils.test.tsx index 635dc1c81..20e5e5c8e 100644 --- a/packages/datagateway-dataview/src/utils.test.tsx +++ b/packages/datagateway-dataview/src/utils.test.tsx @@ -1,15 +1,5 @@ -import React from 'react'; import useAfterMountEffect from './utils'; -import { mount } from 'enzyme'; - -const TestHook = (props: { - callback: () => void; - triggerProp: number; - nonTriggerProp?: number; -}): React.ReactElement => { - props.callback(); - return
    ; -}; +import { renderHook } from '@testing-library/react-hooks'; describe('Utils', () => { describe('useAfterMountEffect', () => { @@ -20,38 +10,33 @@ describe('Utils', () => { }); it('calls effect only upon prop changes, not on mount', () => { - const wrapper = mount( - useAfterMountEffect(mockFunction)} - /> - ); + const callback = jest.fn(); - expect(mockFunction).not.toHaveBeenCalled(); + const { rerender } = renderHook(() => useAfterMountEffect(callback)); - wrapper.setProps({ triggerProp: 2 }); - expect(mockFunction).toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); + + rerender(); + expect(callback).toHaveBeenCalled(); }); it('respects dependency array', () => { - let triggerProp = 1; - - const wrapper = mount( - useAfterMountEffect(mockFunction, [triggerProp])} - /> + const callback = jest.fn(); + + const { rerender } = renderHook( + (testProp) => useAfterMountEffect(callback, [testProp]), + { + initialProps: 1, + } ); - expect(mockFunction).not.toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); - wrapper.setProps({ nonTriggerProp: 2 }); - expect(mockFunction).not.toHaveBeenCalled(); + rerender(1); + expect(callback).not.toHaveBeenCalled(); - triggerProp = 2; - wrapper.setProps({}); - expect(mockFunction).toHaveBeenCalled(); + rerender(2); + expect(callback).toHaveBeenCalled(); }); }); }); diff --git a/packages/datagateway-dataview/src/views/card/__snapshots__/datasetCardView.component.test.tsx.snap b/packages/datagateway-dataview/src/views/card/__snapshots__/datasetCardView.component.test.tsx.snap deleted file mode 100644 index bb82d5942..000000000 --- a/packages/datagateway-dataview/src/views/card/__snapshots__/datasetCardView.component.test.tsx.snap +++ /dev/null @@ -1,81 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dataset - Card View renders correctly 1`] = ` -Object { - "buttons": Array [ - [Function], - ], - "data": Array [ - Object { - "createTime": "2019-07-23", - "id": 1, - "modTime": "2019-07-23", - "name": "Test 1", - "size": 1, - }, - ], - "description": Object { - "dataKey": "description", - "filterComponent": [Function], - "label": "datasets.details.description", - }, - "filters": Object {}, - "information": Array [ - Object { - "dataKey": "datafileCount", - "disableSort": true, - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "datasets.datafile_count", - }, - Object { - "dataKey": "createTime", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "datasets.create_time", - }, - Object { - "dataKey": "modTime", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "datasets.modified_time", - }, - ], - "loadedCount": true, - "loadedData": true, - "onFilter": [Function], - "onPageChange": [Function], - "onResultsChange": [Function], - "onSort": [Function], - "page": null, - "results": null, - "sort": Object {}, - "title": Object { - "content": [Function], - "dataKey": "name", - "filterComponent": [Function], - "label": "datasets.name", - }, - "totalDataCount": 1, -} -`; diff --git a/packages/datagateway-dataview/src/views/card/__snapshots__/investigationCardView.component.test.tsx.snap b/packages/datagateway-dataview/src/views/card/__snapshots__/investigationCardView.component.test.tsx.snap deleted file mode 100644 index 6cc06e810..000000000 --- a/packages/datagateway-dataview/src/views/card/__snapshots__/investigationCardView.component.test.tsx.snap +++ /dev/null @@ -1,137 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Investigation - Card View renders correctly 1`] = ` -Object { - "buttons": Array [ - [Function], - ], - "customFilters": Array [ - Object { - "dataKey": "type.id", - "filterItems": Array [], - "label": "investigations.type.id", - "prefixLabel": true, - }, - Object { - "dataKey": "facility.id", - "filterItems": Array [], - "label": "investigations.facility.id", - "prefixLabel": true, - }, - ], - "data": Array [ - Object { - "doi": "doi 1", - "id": 1, - "name": "Test 1", - "title": "Test 1", - "visitId": "1", - }, - ], - "description": Object { - "dataKey": "summary", - "filterComponent": [Function], - "label": "investigations.details.summary", - }, - "filters": Object {}, - "information": Array [ - Object { - "content": [Function], - "dataKey": "doi", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.doi", - }, - Object { - "dataKey": "visitId", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.visit_id", - }, - Object { - "dataKey": "name", - "disableSort": true, - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.details.name", - }, - Object { - "content": [Function], - "dataKey": "datasetCount", - "disableSort": true, - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.dataset_count", - }, - Object { - "dataKey": "startDate", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.details.start_date", - }, - Object { - "dataKey": "endDate", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.details.end_date", - }, - ], - "loadedCount": true, - "loadedData": true, - "onFilter": [Function], - "onPageChange": [Function], - "onResultsChange": [Function], - "onSort": [Function], - "page": null, - "results": null, - "sort": Object {}, - "title": Object { - "content": [Function], - "dataKey": "title", - "filterComponent": [Function], - "label": "investigations.title", - }, - "totalDataCount": 1, -} -`; diff --git a/packages/datagateway-dataview/src/views/card/datasetCardView.component.test.tsx b/packages/datagateway-dataview/src/views/card/datasetCardView.component.test.tsx index 886b748d2..ffc7bb947 100644 --- a/packages/datagateway-dataview/src/views/card/datasetCardView.component.test.tsx +++ b/packages/datagateway-dataview/src/views/card/datasetCardView.component.test.tsx @@ -1,27 +1,31 @@ -import { ListItemText } from '@mui/material'; import { - AdvancedFilter, + type Dataset, dGCommonInitialState, - useDatasetsPaginated, useDatasetCount, - Dataset, - AddToCartButton, + useDatasetsPaginated, } from 'datagateway-common'; -import { mount, ReactWrapper } from 'enzyme'; -import React from 'react'; +import * as React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { StateType } from '../../state/app.types'; +import type { StateType } from '../../state/app.types'; import DatasetCardView from './datasetCardView.component'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { createMemoryHistory, History } from 'history'; +import { createMemoryHistory, type History } from 'history'; import { initialState as dgDataViewInitialState } from '../../state/reducers/dgdataview.reducer'; import { applyDatePickerWorkaround, cleanupDatePickerWorkaround, } from '../../setupTests'; +import { + render, + type RenderResult, + screen, + within, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -39,11 +43,11 @@ describe('Dataset - Card View', () => { let state: StateType; let cardData: Dataset[]; let history: History; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -51,19 +55,21 @@ describe('Dataset - Card View', () => { ); - }; beforeEach(() => { cardData = [ { id: 1, name: 'Test 1', + description: 'Test description', size: 1, modTime: '2019-07-23', createTime: '2019-07-23', + datafileCount: 1, }, ]; history = createMemoryHistory(); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -89,41 +95,100 @@ describe('Dataset - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('renders datasets as cards', async () => { + renderComponent(); - it('calls the correct data fetching hooks on load', () => { - const investigationId = '1'; - createWrapper(); - expect(useDatasetCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'investigation.id': { eq: investigationId }, - }), - }, - ]); - expect(useDatasetsPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'investigation.id': { eq: investigationId }, - }), - }, - ]); + const cards = await screen.findAllByTestId('card'); + expect(cards).toHaveLength(1); + + const card = within(cards[0]); + + // check that title & description is displayed correctly + expect( + within(card.getByLabelText('card-title')).getByRole('link', { + name: 'Test 1', + }) + ).toHaveAttribute('href', '/browse/investigation/1/dataset/1/datafile'); + expect( + within(card.getByLabelText('card-description')).getByText( + 'Test description' + ) + ).toBeInTheDocument(); + + // check that datafile count is displayed correctly + expect( + card.getByTestId('card-info-datasets.datafile_count') + ).toBeInTheDocument(); + expect( + within(card.getByTestId('card-info-datasets.datafile_count')).getByTestId( + 'ConfirmationNumberIcon' + ) + ).toBeInTheDocument(); + expect( + card.getByTestId('card-info-datasets.datafile_count') + ).toHaveTextContent('datasets.datafile_count'); + expect( + within( + card.getByTestId('card-info-data-datasets.datafile_count') + ).getByText('1') + ).toBeInTheDocument(); + + // check that datafile create time is displayed correctly + expect( + card.getByTestId('card-info-datasets.create_time') + ).toBeInTheDocument(); + expect( + card.getByTestId('card-info-datasets.create_time') + ).toHaveTextContent('datasets.create_time'); + expect( + within(card.getByTestId('card-info-datasets.create_time')).getByTestId( + 'CalendarTodayIcon' + ) + ).toBeInTheDocument(); + expect( + within(card.getByTestId('card-info-data-datasets.create_time')).getByText( + '2019-07-23' + ) + ).toBeInTheDocument(); + + // check that datafile modified time is displayed correctly + expect( + card.getByTestId('card-info-datasets.modified_time') + ).toBeInTheDocument(); + expect( + card.getByTestId('card-info-datasets.modified_time') + ).toHaveTextContent('datasets.modified_time'); + expect( + within(card.getByTestId('card-info-datasets.modified_time')).getByTestId( + 'CalendarTodayIcon' + ) + ).toBeInTheDocument(); + expect( + within( + card.getByTestId('card-info-data-datasets.modified_time') + ).getByText('2019-07-23') + ).toBeInTheDocument(); + + // check that buttons are displayed correctly + expect( + card.getByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('updates filter query params on text filter', async () => { + renderComponent(); + + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'Filter by datasets.name', + hidden: true, + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -131,62 +196,54 @@ describe('Dataset - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); + + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'datasets.modified_time filter to', + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + await user.type(filter, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"modTime":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); cleanupDatePickerWorkaround(); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('datasets.name'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by DATASETS.NAME' }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"name":"asc"}')}` ); }); - it('renders buttons correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find(AddToCartButton).exists()).toBeTruthy(); - expect(wrapper.find(AddToCartButton).text()).toEqual('buttons.add_to_cart'); - }); - it('renders fine with incomplete data', () => { (useDatasetCount as jest.Mock).mockReturnValue({}); (useDatasetsPaginated as jest.Mock).mockReturnValue({}); - expect(() => createWrapper()).not.toThrowError(); + expect(() => renderComponent()).not.toThrowError(); }); }); diff --git a/packages/datagateway-dataview/src/views/card/datasetCardView.component.tsx b/packages/datagateway-dataview/src/views/card/datasetCardView.component.tsx index 8c7f37279..71ede5e86 100644 --- a/packages/datagateway-dataview/src/views/card/datasetCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/datasetCardView.component.tsx @@ -120,6 +120,7 @@ const DatasetCardView = (props: DatasetCardViewProps): React.ReactElement => { return ( { const originalModule = jest.requireActual('datagateway-common'); @@ -40,11 +38,11 @@ describe('DLS Datasets - Card View', () => { let state: StateType; let cardData: Dataset[]; let history: History; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -52,7 +50,6 @@ describe('DLS Datasets - Card View', () => { ); - }; beforeEach(() => { cardData = [ @@ -64,6 +61,7 @@ describe('DLS Datasets - Card View', () => { }, ]; history = createMemoryHistory(); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -90,41 +88,20 @@ describe('DLS Datasets - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('updates filter query params on text filter', async () => { + renderComponent(); - it('calls the correct data fetching hooks on load', () => { - const investigationId = '1'; - createWrapper(); - expect(useDatasetCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'investigation.id': { eq: investigationId }, - }), - }, - ]); - expect(useDatasetsPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'investigation.id': { eq: investigationId }, - }), - }, - ]); - }); + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + const filter = await screen.findByRole('textbox', { + name: 'Filter by datasets.name', + hidden: true, + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -132,34 +109,32 @@ describe('DLS Datasets - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); + + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'datasets.details.end_date filter to', + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + await user.type(filter, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"endDate":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); @@ -167,48 +142,44 @@ describe('DLS Datasets - Card View', () => { }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"createTime":"desc"}')}` ); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('datasets.name'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by DATASETS.NAME' }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"name":"asc"}')}` ); - }); + }, 10000); - it('displays details panel when more information is expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(DLSDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(DLSDatasetDetailsPanel).exists()).toBeTruthy(); + it('displays details panel when more information is expanded', async () => { + renderComponent(); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect(screen.findByTestId('dataset-details-panel')).toBeTruthy(); }); - it('renders buttons correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find(AddToCartButton).exists()).toBeTruthy(); - expect(wrapper.find(AddToCartButton).text()).toEqual('buttons.add_to_cart'); + it('renders buttons correctly', async () => { + renderComponent(); + expect( + await screen.findByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); }); it('renders fine with incomplete data', () => { (useDatasetCount as jest.Mock).mockReturnValueOnce({}); (useDatasetsPaginated as jest.Mock).mockReturnValueOnce({}); - expect(() => createWrapper()).not.toThrowError(); + expect(() => renderComponent()).not.toThrowError(); }); }); diff --git a/packages/datagateway-dataview/src/views/card/dls/dlsDatasetsCardView.component.tsx b/packages/datagateway-dataview/src/views/card/dls/dlsDatasetsCardView.component.tsx index 48491b6d2..d1b51ba15 100644 --- a/packages/datagateway-dataview/src/views/card/dls/dlsDatasetsCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/dls/dlsDatasetsCardView.component.tsx @@ -149,6 +149,7 @@ const DLSDatasetsCardView = (props: DLSDatasetsCVProps): React.ReactElement => { return ( { const originalModule = jest.requireActual('datagateway-common'); @@ -33,11 +34,11 @@ describe('DLS Proposals - Card View', () => { let state: StateType; let cardData: Investigation[]; let history: History; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -45,7 +46,6 @@ describe('DLS Proposals - Card View', () => { ); - }; beforeEach(() => { cardData = [ @@ -57,6 +57,7 @@ describe('DLS Proposals - Card View', () => { }, ]; history = createMemoryHistory(); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -83,39 +84,20 @@ describe('DLS Proposals - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('updates filter query params on text filter', async () => { + renderComponent(); - it('calls the correct data fetching hooks on load', () => { - createWrapper(); - expect(useInvestigationCount).toHaveBeenCalledWith([ - { - filterType: 'distinct', - filterValue: JSON.stringify(['name', 'title']), - }, - ]); - expect(useInvestigationsPaginated).toHaveBeenCalledWith( - [ - { - filterType: 'distinct', - filterValue: JSON.stringify(['name', 'title']), - }, - ], - true + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) ); - }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + const filter = await screen.findByRole('textbox', { + name: 'Filter by investigations.title', + hidden: true, + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -123,18 +105,13 @@ describe('DLS Proposals - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"title":"asc"}')}` @@ -145,6 +122,6 @@ describe('DLS Proposals - Card View', () => { (useInvestigationCount as jest.Mock).mockReturnValueOnce({}); (useInvestigationsPaginated as jest.Mock).mockReturnValueOnce({}); - expect(() => createWrapper()).not.toThrowError(); + expect(() => renderComponent()).not.toThrowError(); }); }); diff --git a/packages/datagateway-dataview/src/views/card/dls/dlsProposalsCardView.component.tsx b/packages/datagateway-dataview/src/views/card/dls/dlsProposalsCardView.component.tsx index 0b1a4a695..0a9a70377 100644 --- a/packages/datagateway-dataview/src/views/card/dls/dlsProposalsCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/dls/dlsProposalsCardView.component.tsx @@ -82,6 +82,7 @@ const DLSProposalsCardView = (): React.ReactElement => { return ( { const originalModule = jest.requireActual('datagateway-common'); @@ -41,11 +40,11 @@ describe('DLS Visits - Card View', () => { let state: StateType; let cardData: Investigation[]; let history: History; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -53,7 +52,6 @@ describe('DLS Visits - Card View', () => { ); - }; beforeEach(() => { cardData = [ @@ -65,6 +63,7 @@ describe('DLS Visits - Card View', () => { }, ]; history = createMemoryHistory(); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -92,44 +91,20 @@ describe('DLS Visits - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('updates filter query params on text filter', async () => { + renderComponent(); - it('calls the correct data fetching hooks on load', () => { - const proposalName = 'test'; - createWrapper(); - expect(useInvestigationCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ name: { eq: proposalName } }), - }, - ]); - expect(useInvestigationsPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ name: { eq: proposalName } }), - }, - { - filterType: 'include', - filterValue: JSON.stringify({ - investigationInstruments: 'instrument', - }), - }, - ]); - expect(useInvestigationsDatasetCount).toHaveBeenCalledWith(cardData); - }); + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + const filter = await screen.findByRole('textbox', { + name: 'Filter by investigations.visit_id', + hidden: true, + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -137,34 +112,32 @@ describe('DLS Visits - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); + + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'investigations.end_date filter to', + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + await user.type(filter, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"endDate":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); @@ -172,36 +145,33 @@ describe('DLS Visits - Card View', () => { }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"startDate":"desc"}')}` ); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('investigations.visit_id'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { + name: 'Sort by INVESTIGATIONS.VISIT_ID', + }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"visitId":"asc"}')}` ); }); - it('displays details panel when more information is expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(DLSVisitDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(DLSVisitDetailsPanel).exists()).toBeTruthy(); + it('displays details panel when more information is expanded', async () => { + renderComponent(); + await user.click(await screen.findByLabelText('card-more-info-expand')); + expect( + await screen.findByTestId('visit-details-panel') + ).toBeInTheDocument(); }); it('renders fine with incomplete data', () => { @@ -209,6 +179,6 @@ describe('DLS Visits - Card View', () => { (useInvestigationsPaginated as jest.Mock).mockReturnValueOnce({}); (useInvestigationsDatasetCount as jest.Mock).mockReturnValueOnce([]); - expect(() => createWrapper()).not.toThrowError(); + expect(() => renderComponent()).not.toThrowError(); }); }); diff --git a/packages/datagateway-dataview/src/views/card/dls/dlsVisitsCardView.component.tsx b/packages/datagateway-dataview/src/views/card/dls/dlsVisitsCardView.component.tsx index c9ece16e7..a135c74fb 100644 --- a/packages/datagateway-dataview/src/views/card/dls/dlsVisitsCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/dls/dlsVisitsCardView.component.tsx @@ -146,6 +146,7 @@ const DLSVisitsCardView = (props: DLSVisitsCVProps): React.ReactElement => { return ( { - const originalModule = jest.requireActual('datagateway-common'); - - return { - __esModule: true, - ...originalModule, - useInvestigationCount: jest.fn(), - useInvestigationsPaginated: jest.fn(), - useInvestigationsDatasetCount: jest.fn(), - }; -}); +import { + render, + type RenderResult, + screen, + within, +} from '@testing-library/react'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; +import axios, { AxiosResponse } from 'axios'; describe('Investigation - Card View', () => { let mockStore; let state: StateType; let cardData: Investigation[]; let history: History; + let user: UserEvent; + let cartItems: DownloadCartItem[]; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -53,19 +45,23 @@ describe('Investigation - Card View', () => { ); - }; beforeEach(() => { + cartItems = []; cardData = [ { id: 1, - title: 'Test 1', - name: 'Test 1', - visitId: '1', + title: 'Test title 1', + summary: 'Test summary', + name: 'Test name 1', + visitId: 'visit id 1', doi: 'doi 1', + startDate: '2020-01-01', + endDate: '2020-01-02', }, ]; history = createMemoryHistory(); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -75,15 +71,33 @@ describe('Investigation - Card View', () => { }) ); - (useInvestigationCount as jest.Mock).mockReturnValue({ - data: 1, - isLoading: false, - }); - (useInvestigationsPaginated as jest.Mock).mockReturnValue({ - data: cardData, - isLoading: false, - }); - (useInvestigationsDatasetCount as jest.Mock).mockReturnValue({ data: 1 }); + axios.get = jest + .fn() + .mockImplementation((url: string): Promise> => { + if (url.includes('/investigations/count')) { + return Promise.resolve({ + data: 1, + }); + } + + if (url.includes('/investigations')) { + return Promise.resolve({ + data: cardData, + }); + } + + if (url.includes('/datasets/count')) { + return Promise.resolve({ + data: 1, + }); + } + + if (url.includes('/user/cart')) { + return Promise.resolve({ + data: { cartItems }, + }); + } + }); // Prevent error logging window.scrollTo = jest.fn(); @@ -93,27 +107,134 @@ describe('Investigation - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('renders investigations as cards', async () => { + renderComponent(); + + const cards = await screen.findAllByTestId('card'); + expect(cards).toHaveLength(1); + + const card = within(cards[0]); + + // check that title & description is displayed correctly + expect( + within(card.getByLabelText('card-title')).getByRole('link', { + name: 'Test title 1', + }) + ).toHaveAttribute('href', '/browse/investigation/1/dataset'); + expect( + within(card.getByLabelText('card-description')).getByText('Test summary') + ).toBeInTheDocument(); - it('calls the correct data fetching hooks on load', () => { - createWrapper(); - expect(useInvestigationCount).toHaveBeenCalled(); - expect(useInvestigationsPaginated).toHaveBeenCalled(); - expect(useInvestigationsDatasetCount).toHaveBeenCalledWith(cardData); + // check that investigation doi is displayed correctly + expect( + within(card.getByTestId('card-info-investigations.doi')).getByTestId( + 'PublicIcon' + ) + ).toBeInTheDocument(); + expect(card.getByTestId('card-info-investigations.doi')).toHaveTextContent( + 'investigations.doi' + ); + expect( + within(card.getByTestId('card-info-data-investigations.doi')).getByRole( + 'link', + { name: 'doi 1' } + ) + ).toHaveAttribute('href', 'https://doi.org/doi 1'); + + // check that visit id is displayed correctly + expect( + card.getByTestId('card-info-investigations.visit_id') + ).toHaveTextContent('investigations.visit_id'); + expect( + within(card.getByTestId('card-info-investigations.visit_id')).getByTestId( + 'FingerprintIcon' + ) + ).toBeInTheDocument(); + expect( + within( + card.getByTestId('card-info-data-investigations.visit_id') + ).getByText('visit id 1') + ).toBeInTheDocument(); + + // check that investigation name is displayed correctly + expect( + card.getByTestId('card-info-investigations.details.name') + ).toHaveTextContent('investigations.details.name'); + expect( + within( + card.getByTestId('card-info-investigations.details.name') + ).getByTestId('FingerprintIcon') + ).toBeInTheDocument(); + expect( + within( + card.getByTestId('card-info-data-investigations.details.name') + ).getByText('Test name 1') + ).toBeInTheDocument(); + + // check that investigation dataset count is displayed correctly + expect( + card.getByTestId('card-info-investigations.dataset_count') + ).toHaveTextContent('investigations.dataset_count'); + expect( + within( + card.getByTestId('card-info-investigations.dataset_count') + ).getByTestId('ConfirmationNumberIcon') + ).toBeInTheDocument(); + expect( + within( + card.getByTestId('card-info-data-investigations.dataset_count') + ).getByText('1') + ).toBeInTheDocument(); + + // check that investigation start date is displayed correctly + expect( + card.getByTestId('card-info-investigations.details.start_date') + ).toHaveTextContent('investigations.details.start_date'); + expect( + within( + card.getByTestId('card-info-investigations.details.start_date') + ).getByTestId('CalendarTodayIcon') + ).toBeInTheDocument(); + expect( + within( + card.getByTestId('card-info-data-investigations.details.start_date') + ).getByText('2020-01-01') + ).toBeInTheDocument(); + + // check that investigation start date is displayed correctly + expect( + card.getByTestId('card-info-investigations.details.end_date') + ).toHaveTextContent('investigations.details.end_date'); + expect( + within( + card.getByTestId('card-info-investigations.details.end_date') + ).getByTestId('CalendarTodayIcon') + ).toBeInTheDocument(); + expect( + within( + card.getByTestId('card-info-data-investigations.details.end_date') + ).getByText('2020-01-02') + ).toBeInTheDocument(); + + // check that card buttons are displayed correctly + expect( + card.getByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('updates filter query params on text filter', async () => { + renderComponent(); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'Filter by investigations.title', + hidden: true, + }); + + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -121,79 +242,48 @@ describe('Investigation - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); + + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'investigations.details.end_date filter to', + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + await user.type(filter, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"endDate":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); cleanupDatePickerWorkaround(); }); - it('displays DOI and renders the expected Link ', () => { - const wrapper = createWrapper(); - expect( - wrapper.find('[data-testid="investigation-card-doi-link"]').first().text() - ).toEqual('doi 1'); - - expect( - wrapper - .find('[data-testid="investigation-card-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/doi 1'); - }); - - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('investigations.title'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { + name: 'Sort by INVESTIGATIONS.TITLE', + }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"title":"asc"}')}` ); }); - - it('renders buttons correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find(AddToCartButton).exists()).toBeTruthy(); - expect(wrapper.find(AddToCartButton).text()).toEqual('buttons.add_to_cart'); - }); - - it('renders fine with incomplete data', () => { - (useInvestigationCount as jest.Mock).mockReturnValueOnce({}); - (useInvestigationsPaginated as jest.Mock).mockReturnValueOnce({}); - (useInvestigationsDatasetCount as jest.Mock).mockReturnValueOnce([ - { data: 0 }, - ]); - - expect(() => createWrapper()).not.toThrowError(); - }); }); diff --git a/packages/datagateway-dataview/src/views/card/investigationCardView.component.tsx b/packages/datagateway-dataview/src/views/card/investigationCardView.component.tsx index 4253e89ac..a7e82e6c2 100644 --- a/packages/datagateway-dataview/src/views/card/investigationCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/investigationCardView.component.tsx @@ -204,6 +204,7 @@ const InvestigationCardView = (): React.ReactElement => { return ( { const originalModule = jest.requireActual('datagateway-common'); @@ -41,11 +38,11 @@ describe('ISIS Datasets - Card View', () => { let state: StateType; let cardData: Dataset[]; let history: History; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + { ); - }; beforeEach(() => { cardData = [ @@ -71,6 +67,7 @@ describe('ISIS Datasets - Card View', () => { }, ]; history = createMemoryHistory(); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -97,43 +94,17 @@ describe('ISIS Datasets - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); - - it('calls the correct data fetching hooks on load', () => { - const investigationId = '1'; - createWrapper(); - expect(useDatasetCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'investigation.id': { eq: investigationId }, - }), - }, - ]); - expect(useDatasetsPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'investigation.id': { eq: investigationId }, - }), - }, - ]); - }); - - it('correct link used when NOT in studyHierarchy', () => { - const wrapper = createWrapper(); - expect( - wrapper.find('[aria-label="card-title"]').last().childAt(0).prop('to') - ).toEqual('/browse/instrument/1/facilityCycle/1/investigation/1/dataset/1'); + it('correct link used when NOT in studyHierarchy', async () => { + renderComponent(); + expect(await screen.findByRole('link', { name: 'Test 1' })).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle/1/investigation/1/dataset/1' + ); }); - it('correct link used for studyHierarchy', () => { - const store = mockStore(state); - const wrapper = mount( - + it('correct link used for studyHierarchy', async () => { + render( + { ); - expect( - wrapper.find('[aria-label="card-title"]').last().childAt(0).prop('to') - ).toEqual( + expect(await screen.findByRole('link', { name: 'Test 1' })).toHaveAttribute( + 'href', '/browseStudyHierarchy/instrument/1/study/1/investigation/1/dataset/1' ); }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('updates filter query params on text filter', async () => { + renderComponent(); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'Filter by datasets.name', + hidden: true, + }); + + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -169,34 +144,32 @@ describe('ISIS Datasets - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); + + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'datasets.modified_time filter to', + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + await user.type(filter, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"modTime":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); @@ -204,47 +177,48 @@ describe('ISIS Datasets - Card View', () => { }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"createTime":"desc"}')}` ); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('datasets.name'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by DATASETS.NAME' }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"name":"asc"}')}` ); }); - it('renders buttons correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find(AddToCartButton).exists()).toBeTruthy(); - expect(wrapper.find(AddToCartButton).text()).toEqual('buttons.add_to_cart'); - - expect(wrapper.find(DownloadButton).exists()).toBeTruthy(); - expect(wrapper.find(DownloadButton).text()).toEqual('buttons.download'); + it('renders buttons correctly', async () => { + renderComponent(); + expect( + await screen.findByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); + expect( + await screen.findByRole('button', { name: 'buttons.download' }) + ).toBeInTheDocument(); }); - it('displays details panel when more information is expanded and navigates to datafiles view when tab clicked', () => { - const wrapper = createWrapper(); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); + it('displays details panel when more information is expanded and navigates to datafiles view when tab clicked', async () => { + renderComponent(); + + await user.click(await screen.findByLabelText('card-more-info-expand')); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeTruthy(); + expect( + await screen.findByTestId('isis-dataset-details-panel') + ).toBeInTheDocument(); + + await user.click( + await screen.findByRole('tab', { name: 'datasets.details.datafiles' }) + ); - wrapper.find('#dataset-datafiles-tab').last().simulate('click'); expect(history.location.pathname).toBe( '/browse/instrument/1/facilityCycle/1/investigation/1/dataset/1/datafile' ); @@ -254,6 +228,6 @@ describe('ISIS Datasets - Card View', () => { (useDatasetCount as jest.Mock).mockReturnValueOnce({}); (useDatasetsPaginated as jest.Mock).mockReturnValueOnce({}); - expect(() => createWrapper()).not.toThrowError(); + expect(() => renderComponent()).not.toThrowError(); }); }); diff --git a/packages/datagateway-dataview/src/views/card/isis/isisDatasetsCardView.component.tsx b/packages/datagateway-dataview/src/views/card/isis/isisDatasetsCardView.component.tsx index 73497a252..06af2f94a 100644 --- a/packages/datagateway-dataview/src/views/card/isis/isisDatasetsCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/isis/isisDatasetsCardView.component.tsx @@ -177,6 +177,7 @@ const ISISDatasetsCardView = ( return ( { const originalModule = jest.requireActual('datagateway-common'); @@ -39,11 +39,11 @@ describe('ISIS Facility Cycles - Card View', () => { let cardData: FacilityCycle[]; let history: History; let replaceSpy: jest.SpyInstance; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -51,9 +51,9 @@ describe('ISIS Facility Cycles - Card View', () => { ); - }; beforeEach(() => { + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( JSON.stringify({ @@ -88,29 +88,20 @@ describe('ISIS Facility Cycles - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('updates filter query params on text filter', async () => { + renderComponent(); - it('calls the correct data fetching hooks on load', () => { - const instrumentId = '1'; - createWrapper(); - expect(useFacilityCycleCount).toHaveBeenCalledWith(parseInt(instrumentId)); - expect(useFacilityCyclesPaginated).toHaveBeenCalledWith( - parseInt(instrumentId) + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) ); - }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + const filter = await screen.findByRole('textbox', { + name: 'Filter by facilitycycles.name', + hidden: true, + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -118,34 +109,32 @@ describe('ISIS Facility Cycles - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'facilitycycles.end_date filter to', + }); + + await user.type(filter, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"endDate":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); @@ -153,21 +142,19 @@ describe('ISIS Facility Cycles - Card View', () => { }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(replaceSpy).toHaveBeenCalledWith({ search: `?sort=${encodeURIComponent('{"startDate":"desc"}')}`, }); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('facilitycycles.name'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by FACILITYCYCLES.NAME' }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"name":"asc"}')}` @@ -178,6 +165,6 @@ describe('ISIS Facility Cycles - Card View', () => { (useFacilityCycleCount as jest.Mock).mockReturnValueOnce({}); (useFacilityCyclesPaginated as jest.Mock).mockReturnValueOnce({}); - expect(() => createWrapper()).not.toThrowError(); + expect(() => renderComponent()).not.toThrowError(); }); }); diff --git a/packages/datagateway-dataview/src/views/card/isis/isisFacilityCyclesCardView.component.tsx b/packages/datagateway-dataview/src/views/card/isis/isisFacilityCyclesCardView.component.tsx index 9cfc05aca..1d70648e6 100644 --- a/packages/datagateway-dataview/src/views/card/isis/isisFacilityCyclesCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/isis/isisFacilityCyclesCardView.component.tsx @@ -92,6 +92,7 @@ const ISISFacilityCyclesCardView = ( return ( { const originalModule = jest.requireActual('datagateway-common'); @@ -35,11 +34,11 @@ describe('ISIS Instruments - Card View', () => { let state: StateType; let cardData: Instrument[]; let history: History; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -47,9 +46,9 @@ describe('ISIS Instruments - Card View', () => { ); - }; beforeEach(() => { + user = userEvent.setup(); cardData = [ { id: 1, @@ -83,28 +82,17 @@ describe('ISIS Instruments - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); - - it('calls the correct data fetching hooks on load', () => { - createWrapper(); - expect(useInstrumentCount).toHaveBeenCalled(); - expect(useInstrumentsPaginated).toHaveBeenCalled(); - }); - - it('correct link used when NOT in studyHierarchy', () => { - const wrapper = createWrapper(); - expect( - wrapper.find('[aria-label="card-title"]').last().childAt(0).prop('to') - ).toEqual('/browse/instrument/1/facilityCycle'); + it('correct link used when NOT in studyHierarchy', async () => { + renderComponent(); + expect(await screen.findByRole('link', { name: 'Test 1' })).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle' + ); }); - it('correct link used for studyHierarchy', () => { - const store = mockStore(state); - const wrapper = mount( - + it('correct link used for studyHierarchy', async () => { + render( + @@ -112,20 +100,26 @@ describe('ISIS Instruments - Card View', () => { ); - expect( - wrapper.find('[aria-label="card-title"]').last().childAt(0).prop('to') - ).toEqual('/browseStudyHierarchy/instrument/1/study'); + expect(await screen.findByRole('link', { name: 'Test 1' })).toHaveAttribute( + 'href', + '/browseStudyHierarchy/instrument/1/study' + ); }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('updates filter query params on text filter', async () => { + renderComponent(); + + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'Filter by instruments.name', + hidden: true, + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -133,51 +127,41 @@ describe('ISIS Instruments - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"fullName":"asc"}')}` ); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('instruments.name'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by INSTRUMENTS.NAME' }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"fullName":"desc"}')}` ); }); - it('displays details panel when more information is expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(ISISInstrumentDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(ISISInstrumentDetailsPanel).exists()).toBeTruthy(); + it('displays details panel when more information is expanded', async () => { + renderComponent(); + await user.click(await screen.findByLabelText('card-more-info-expand')); + expect(await screen.findByTestId('instrument-details-panel')); }); it('renders fine with incomplete data', () => { (useInstrumentCount as jest.Mock).mockReturnValueOnce({}); (useInstrumentsPaginated as jest.Mock).mockReturnValueOnce({}); - expect(() => createWrapper()).not.toThrowError(); + expect(() => renderComponent()).not.toThrowError(); }); }); diff --git a/packages/datagateway-dataview/src/views/card/isis/isisInstrumentsCardView.component.tsx b/packages/datagateway-dataview/src/views/card/isis/isisInstrumentsCardView.component.tsx index a55f82f72..c051d93f7 100644 --- a/packages/datagateway-dataview/src/views/card/isis/isisInstrumentsCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/isis/isisInstrumentsCardView.component.tsx @@ -99,6 +99,7 @@ const ISISInstrumentsCardView = ( return ( { - const originalModule = jest.requireActual('datagateway-common'); - - return { - __esModule: true, - ...originalModule, - useISISInvestigationCount: jest.fn(), - useISISInvestigationsPaginated: jest.fn(), - useInvestigationSizes: jest.fn(), - }; -}); +import { + render, + type RenderResult, + screen, + within, +} from '@testing-library/react'; +import type { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; +import axios, { type AxiosResponse } from 'axios'; describe('ISIS Investigations - Card View', () => { let mockStore; @@ -44,13 +30,19 @@ describe('ISIS Investigations - Card View', () => { let cardData: Investigation[]; let history: History; let replaceSpy: jest.SpyInstance; + let user: UserEvent; - const createWrapper = (studyHierarchy = false): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (studyHierarchy = false): RenderResult => + render( + - + { ); - }; beforeEach(() => { cardData = [ { id: 1, - title: 'Test 1', + title: 'Test title 1', name: 'Test 1', visitId: '1', + startDate: '2022-01-01', + endDate: '2022-01-03', studyInvestigations: [ { id: 1, study: { id: 1, pid: 'study pid' }, name: 'study 1' }, ], @@ -88,6 +81,7 @@ describe('ISIS Investigations - Card View', () => { ]; history = createMemoryHistory(); replaceSpy = jest.spyOn(history, 'replace'); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -97,15 +91,46 @@ describe('ISIS Investigations - Card View', () => { }) ); - (useISISInvestigationCount as jest.Mock).mockReturnValue({ - data: 1, - isLoading: false, - }); - (useISISInvestigationsPaginated as jest.Mock).mockReturnValue({ - data: cardData, - isLoading: false, - }); - (useInvestigationSizes as jest.Mock).mockReturnValue([{ data: 1 }]); + axios.get = jest + .fn() + .mockImplementation((url: string): Promise> => { + if ( + /\/instruments\/1\/facilitycycles\/1\/investigations\/count$/.test( + url + ) + ) { + // isis investigation count query + return Promise.resolve({ + data: 1, + }); + } + + if (/\/investigations\/count$/.test(url)) { + // investigation count query + return Promise.resolve({ + data: 1, + }); + } + + if (/\/investigations$/.test(url)) { + // investigations query + return Promise.resolve({ + data: cardData, + }); + } + + if (/\/user\/getSize$/.test(url)) { + // investigation size query + return Promise.resolve({ + data: 123, + }); + } + + return Promise.reject({ + response: { status: 403 }, + message: `Endpoint not mocked: ${url}`, + }); + }); // Prevent error logging window.scrollTo = jest.fn(); @@ -115,53 +140,123 @@ describe('ISIS Investigations - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('renders investigations as cards', async () => { + renderComponent(); - it('calls required query, filter and sort functions on page load', () => { - const instrumentId = '1'; - const instrumentChildId = '1'; - const studyHierarchy = false; - createWrapper(); - expect(useISISInvestigationCount).toHaveBeenCalledWith( - parseInt(instrumentId), - parseInt(instrumentChildId), - studyHierarchy - ); - expect(useISISInvestigationsPaginated).toHaveBeenCalledWith( - parseInt(instrumentId), - parseInt(instrumentChildId), - studyHierarchy + const allCards = await screen.findAllByTestId('card'); + expect(allCards).toHaveLength(1); + + const firstCard = within(allCards[0]); + expect( + firstCard.getByRole('link', { name: 'Test title 1' }) + ).toBeInTheDocument(); + expect(firstCard.getByText('investigations.name:')).toBeInTheDocument(); + expect(firstCard.getByText('Test 1')).toBeInTheDocument(); + expect(firstCard.getByText('investigations.doi:')).toBeInTheDocument(); + expect(firstCard.getByRole('link', { name: 'study pid' })).toHaveAttribute( + 'href', + 'https://doi.org/study pid' ); - expect(useInvestigationSizes).toHaveBeenCalledWith(cardData); + expect( + firstCard.getByText('investigations.details.size:') + ).toBeInTheDocument(); + expect(firstCard.getByText('123 B')).toBeInTheDocument(); + expect( + firstCard.getByText('investigations.principal_investigators:') + ).toBeInTheDocument(); + expect(firstCard.getByText('Test PI')).toBeInTheDocument(); + expect( + firstCard.getByText('investigations.details.start_date:') + ).toBeInTheDocument(); + expect(firstCard.getByText('2022-01-01')).toBeInTheDocument(); + expect( + firstCard.getByText('investigations.details.end_date:') + ).toBeInTheDocument(); + expect(firstCard.getByText('2022-01-03')).toBeInTheDocument(); }); - it('correct link used when NOT in studyHierarchy', () => { - const wrapper = createWrapper(); - expect( - wrapper.find('[aria-label="card-title"]').last().childAt(0).prop('to') - ).toEqual('/browse/instrument/1/facilityCycle/1/investigation/1'); + it('renders no card if no investigation is returned', async () => { + axios.get = jest + .fn() + .mockImplementation((url: string): Promise> => { + if ( + /\/instruments\/1\/facilitycycles\/1\/investigations\/count$/.test( + url + ) + ) { + // isis investigation count query + return Promise.resolve({ + data: 0, + }); + } + + if (/\/investigations\/count$/.test(url)) { + // investigation count query + return Promise.resolve({ + data: 0, + }); + } + + if (/\/investigations$/.test(url)) { + // investigations query + return Promise.resolve({ + data: [], + }); + } + + if (/\/user\/getSize$/.test(url)) { + // investigation size query + return Promise.resolve({ + data: 123, + }); + } + + return Promise.reject({ + response: { status: 403 }, + message: `Endpoint not mocked: ${url}`, + }); + }); + + renderComponent(); + await flushPromises(); + + expect(screen.queryAllByTestId('card')).toHaveLength(0); }); - it('correct link used for studyHierarchy', () => { - const wrapper = createWrapper(true); + it('correct link used when NOT in studyHierarchy', async () => { + renderComponent(); + expect( + await screen.findByRole('link', { name: 'Test title 1' }) + ).toHaveAttribute( + 'href', + '/browse/instrument/1/facilityCycle/1/investigation/1' + ); + }); + it('correct link used for studyHierarchy', async () => { + renderComponent(true); expect( - wrapper.find('[aria-label="card-title"]').last().childAt(0).prop('to') - ).toEqual('/browseStudyHierarchy/instrument/1/study/1/investigation/1'); + await screen.findByRole('link', { name: 'Test title 1' }) + ).toHaveAttribute( + 'href', + '/browseStudyHierarchy/instrument/1/study/1/investigation/1' + ); }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('updates filter query params on text filter', async () => { + renderComponent(); + + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'Filter by investigations.title', + hidden: true, + }); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filter, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -169,121 +264,93 @@ describe('ISIS Investigations - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'investigations.details.end_date filter to', + }); + + await user.type(filter, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"endDate":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); expect(history.location.search).toBe('?'); cleanupDatePickerWorkaround(); }); - it('displays DOI and renders the expected Link ', () => { - const wrapper = createWrapper(); - expect( - wrapper - .find('[data-testid="isis-investigations-card-doi-link"]') - .first() - .text() - ).toEqual('study pid'); - + it('displays DOI and renders the expected Link ', async () => { + renderComponent(); expect( - wrapper - .find('[data-testid="isis-investigations-card-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/study pid'); + await screen.findByRole('link', { name: 'study pid' }) + ).toHaveAttribute('href', 'https://doi.org/study pid'); }); - it('displays the correct user as the PI ', () => { - const wrapper = createWrapper(); - - expect( - wrapper - .find( - '[data-testid="card-info-data-investigations.principal_investigators"]' - ) - .text() - ).toEqual('Test PI'); + it('displays the correct user as the PI ', async () => { + renderComponent(); + expect(await screen.findByText('Test PI')).toBeInTheDocument(); }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(replaceSpy).toHaveBeenCalledWith({ search: `?sort=${encodeURIComponent('{"startDate":"desc"}')}`, }); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); - - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('investigations.title'); - button.find('div').simulate('click'); - + it('updates sort query params on sort', async () => { + renderComponent(); + await user.click( + await screen.findByRole('button', { + name: 'Sort by INVESTIGATIONS.TITLE', + }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"title":"asc"}')}` ); }); - it('renders buttons correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find(AddToCartButton).exists()).toBeTruthy(); - expect(wrapper.find(AddToCartButton).text()).toEqual('buttons.add_to_cart'); - - expect(wrapper.find(DownloadButton).exists()).toBeTruthy(); - expect(wrapper.find(DownloadButton).text()).toEqual('buttons.download'); + it('renders buttons correctly', async () => { + renderComponent(); + expect( + await screen.findByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); + expect( + await screen.findByRole('button', { name: 'buttons.download' }) + ).toBeInTheDocument(); }); - it('displays details panel when more information is expanded and navigates to datasets view when tab clicked', () => { - const wrapper = createWrapper(); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeTruthy(); - - wrapper.find('#investigation-datasets-tab').last().simulate('click'); + it('displays details panel when more information is expanded and navigates to datasets view when tab clicked', async () => { + renderComponent(); + await user.click(await screen.findByLabelText('card-more-info-expand')); + expect( + await screen.findByTestId('isis-investigation-details-panel') + ).toBeTruthy(); + await user.click( + await screen.findByRole('tab', { + name: 'investigations.details.datasets', + }) + ); expect(history.location.pathname).toBe( '/browse/instrument/1/facilityCycle/1/investigation/1/dataset' ); }); - - it('renders fine with incomplete data', () => { - (useISISInvestigationCount as jest.Mock).mockReturnValueOnce({}); - (useISISInvestigationsPaginated as jest.Mock).mockReturnValueOnce({}); - (useInvestigationSizes as jest.Mock).mockReturnValueOnce([{ data: 0 }]); - - expect(() => createWrapper()).not.toThrowError(); - }); }); diff --git a/packages/datagateway-dataview/src/views/card/isis/isisInvestigationsCardView.component.tsx b/packages/datagateway-dataview/src/views/card/isis/isisInvestigationsCardView.component.tsx index 086536de7..76d29ff7a 100644 --- a/packages/datagateway-dataview/src/views/card/isis/isisInvestigationsCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/isis/isisInvestigationsCardView.component.tsx @@ -224,6 +224,7 @@ const ISISInvestigationsCardView = ( return ( { - const originalModule = jest.requireActual('datagateway-common'); - - return { - __esModule: true, - ...originalModule, - useStudyCount: jest.fn(), - useStudiesPaginated: jest.fn(), - }; -}); +import { + render, + type RenderResult, + screen, + within, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import axios, { AxiosResponse } from 'axios'; describe('ISIS Studies - Card View', () => { let mockStore; @@ -44,10 +28,9 @@ describe('ISIS Studies - Card View', () => { let cardData: Study[]; let history: History; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -55,7 +38,6 @@ describe('ISIS Studies - Card View', () => { ); - }; beforeEach(() => { cardData = [ @@ -73,6 +55,8 @@ describe('ISIS Studies - Card View', () => { title: 'investigation title', name: 'investigation name', visitId: 'IPim0', + startDate: '1999-01-01', + endDate: '1999-01-02', }, }, ], @@ -88,14 +72,21 @@ describe('ISIS Studies - Card View', () => { }) ); - (useStudyCount as jest.Mock).mockReturnValue({ - data: 1, - isLoading: false, - }); - (useStudiesPaginated as jest.Mock).mockReturnValue({ - data: cardData, - isLoading: false, - }); + axios.get = jest + .fn() + .mockImplementation((url: string): Promise> => { + switch (url) { + case '/studies': + return Promise.resolve({ + data: cardData, + }); + + case '/studies/count': + return Promise.resolve({ + data: 1, + }); + } + }); // Prevent error logging window.scrollTo = jest.fn(); @@ -105,78 +96,31 @@ describe('ISIS Studies - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); + it('renders correctly', async () => { + renderComponent(); - it('calls the correct data fetching hooks on load', () => { - const instrumentId = '1'; - createWrapper(); - expect(useStudyCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'studyInvestigations.investigation.investigationInstruments.instrument.id': - { - eq: instrumentId, - }, - }), - }, - { - filterType: 'where', - filterValue: JSON.stringify({ - 'studyInvestigations.investigation.releaseDate': { - lt: '2021-10-27 00:00:00', - }, - }), - }, - ]); - expect(useStudiesPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - 'studyInvestigations.investigation.investigationInstruments.instrument.id': - { - eq: instrumentId, - }, - }), - }, - { - filterType: 'where', - filterValue: JSON.stringify({ - 'studyInvestigations.investigation.releaseDate': { - lt: '2021-10-27 00:00:00', - }, - }), - }, - { - filterType: 'include', - filterValue: JSON.stringify({ - studyInvestigations: 'investigation', - }), - }, - ]); - }); + const cards = await screen.findAllByTestId('card'); + expect(cards).toHaveLength(1); - it('displays Experiment DOI (PID) and renders the expected Link ', () => { - const wrapper = createWrapper(); - expect( - wrapper.find('[data-testid="landing-study-card-pid-link"]').first().text() - ).toEqual('doi'); - - expect( - wrapper - .find('[data-testid="landing-study-card-pid-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/doi'); + const card = cards[0]; + // card title should be rendered as link to study + expect(within(card).getByRole('link', { name: 'Test 1' })).toHaveAttribute( + 'href', + '/browseStudyHierarchy/instrument/1/study/1' + ); + expect(within(card).getByLabelText('card-description')).toHaveTextContent( + 'investigation title' + ); + expect(within(card).getByRole('link', { name: 'doi' })).toHaveAttribute( + 'href', + 'https://doi.org/doi' + ); + expect(within(card).getByText('1999-01-01')).toBeInTheDocument(); + expect(within(card).getByText('1999-01-02')).toBeInTheDocument(); }); it('uses default sort', () => { - const wrapper = createWrapper(); - wrapper.update(); - + renderComponent(); expect(history.length).toBe(1); expect(history.location.search).toBe( `?sort=${encodeURIComponent( @@ -185,71 +129,84 @@ describe('ISIS Studies - Card View', () => { ); }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('updates filter query params on text filter', async () => { + jest.useFakeTimers(); + const user = userEvent.setup({ + advanceTimers: jest.advanceTimersByTime, + }); + + renderComponent(); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + // click on button to show advanced filters + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filter = await screen.findByRole('textbox', { + name: 'Filter by studies.name', + hidden: true, + }); + + await user.type(filter, 'tes'); + jest.runAllTimers(); expect(history.location.search).toBe( `?filters=${encodeURIComponent( - '{"name":{"value":"test","type":"include"}}' + '{"name":{"value":"tes","type":"include"}}' )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); + await user.clear(filter); + jest.runAllTimers(); expect(history.location.search).toBe('?'); + + jest.useRealTimers(); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { + const user = userEvent.setup(); applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + // open advanced filter + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); + + const filterInput = screen.getByRole('textbox', { + name: 'studies.end_date filter to', + }); + await user.type(filterInput, '2019-08-06'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( '{"studyInvestigations.investigation.endDate":{"endDate":"2019-08-06"}}' )}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); - + await user.clear(filterInput); expect(history.location.search).toBe('?'); cleanupDatePickerWorkaround(); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + const user = userEvent.setup(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('studies.name'); - button.find('div').simulate('click'); + renderComponent(); + + await user.click( + await screen.findByRole('button', { name: 'Sort by STUDIES.NAME' }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"name":"asc"}')}` ); }); - it('displays information from investigation when investigation present', () => { + it('displays information from investigation when investigation present', async () => { cardData = [ { ...cardData[0], @@ -271,40 +228,13 @@ describe('ISIS Studies - Card View', () => { ], }, ]; - (useStudiesPaginated as jest.Mock).mockReturnValue({ - data: cardData, - }); - - const wrapper = createWrapper(); - - expect( - wrapper.find('[aria-label="card-description"]').last().text() - ).toEqual('Test investigation'); - }); - - it('renders fine with incomplete data', () => { - (useStudyCount as jest.Mock).mockReturnValueOnce({}); - (useStudiesPaginated as jest.Mock).mockReturnValueOnce({}); - expect(() => createWrapper()).not.toThrowError(); + renderComponent(); - cardData = [ - { - ...cardData[0], - studyInvestigations: [ - { - id: 2, - study: { - ...cardData[0], - }, - }, - ], - }, - ]; - (useStudiesPaginated as jest.Mock).mockReturnValue({ - data: cardData, - }); + const card = (await screen.findAllByTestId('card'))[0]; - expect(() => createWrapper()).not.toThrowError(); + expect(within(card).getByLabelText('card-description')).toHaveTextContent( + 'Test investigation' + ); }); }); diff --git a/packages/datagateway-dataview/src/views/card/isis/isisStudiesCardView.component.tsx b/packages/datagateway-dataview/src/views/card/isis/isisStudiesCardView.component.tsx index c28133219..53229497f 100644 --- a/packages/datagateway-dataview/src/views/card/isis/isisStudiesCardView.component.tsx +++ b/packages/datagateway-dataview/src/views/card/isis/isisStudiesCardView.component.tsx @@ -191,6 +191,7 @@ const ISISStudiesCardView = (props: ISISStudiesCVProps): React.ReactElement => { return ( { let queryClient; + let user: UserEvent; const props = { doi: 'test', @@ -18,15 +25,15 @@ describe('Citation formatter component tests', () => { startDate: '2019-04-03', }; - const createWrapper = (props): ReactWrapper => { - return mount( + const renderComponent = (componentProps): RenderResult => + render( - + ); - }; beforeEach(() => { + user = userEvent.setup(); queryClient = new QueryClient({ defaultOptions: { queries: { @@ -41,130 +48,77 @@ describe('Citation formatter component tests', () => { }); it('renders correctly', async () => { - const wrapper = createWrapper(props); - await act(async () => flushPromises()); - wrapper.update(); + renderComponent(props); expect( - wrapper.find('[data-testid="citation-formatter-title"]').first().text() - ).toEqual('studies.details.citation_formatter.label'); - expect( - wrapper.find('[data-testid="citation-formatter-details"]').first().text() - ).toEqual( - 'studies.details.citation_formatter.details studies.details.citation_formatter.details_select_format' - ); + await screen.findByText('studies.details.citation_formatter.label') + ).toBeInTheDocument(); expect( - wrapper - .find( - '[aria-label="studies.details.citation_formatter.select_arialabel"]' - ) - .first() - .prop('defaultValue') - ).toEqual('default'); + await screen.findByText( + 'studies.details.citation_formatter.details studies.details.citation_formatter.details_select_format' + ) + ).toBeInTheDocument(); expect( - wrapper - .find( - '[aria-label="studies.details.citation_formatter.select_arialabel"]' - ) - .first() - .text() - ).toEqual('studies.details.citation_formatter.default_format'); + await screen.findByText( + 'studies.details.citation_formatter.default_format' + ) + ).toBeInTheDocument(); expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual( - 'John Smith; 2019: title, doi_constants.publisher.name, https://doi.org/test' - ); + await screen.findByText( + 'John Smith; 2019: title, doi_constants.publisher.name, https://doi.org/test' + ) + ).toBeInTheDocument(); expect( - wrapper.find('#citation-formatter-copy-citation').first().prop('disabled') - ).toEqual(false); + await screen.findByRole('button', { + name: 'studies.details.citation_formatter.copy_citation_arialabel', + }) + ).toBeEnabled(); }); it('renders correctly without a doi', async () => { const newProps = { ...props, doi: undefined }; - const wrapper = createWrapper(newProps); - await act(async () => flushPromises()); - wrapper.update(); + renderComponent(newProps); expect( - wrapper.find('[data-testid="citation-formatter-title"]').first().text() - ).toEqual('studies.details.citation_formatter.label'); + await screen.findByText('studies.details.citation_formatter.label') + ).toBeInTheDocument(); expect( - wrapper.find('[data-testid="citation-formatter-details"]').first().text() - ).toEqual('studies.details.citation_formatter.details'); + await screen.findByText('studies.details.citation_formatter.details') + ).toBeInTheDocument(); expect( - wrapper - .find( - '[aria-label="studies.details.citation_formatter.select_arialabel"]' - ) - .exists() - ).toEqual(false); + screen.queryByLabelText( + 'studies.details.citation_formatter.select_arialabel' + ) + ).toBeNull(); expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual('John Smith; 2019: title, doi_constants.publisher.name'); + await screen.findByText( + 'John Smith; 2019: title, doi_constants.publisher.name' + ) + ).toBeInTheDocument(); expect( - wrapper.find('#citation-formatter-copy-citation').first().prop('disabled') - ).toEqual(false); - }); - - it('formats citation on load', async () => { - const wrapper = createWrapper(props); - await act(async () => flushPromises()); - wrapper.update(); - - expect( - wrapper - .find( - '[aria-label="studies.details.citation_formatter.select_arialabel"]' - ) - .first() - .prop('defaultValue') - ).toEqual('default'); - - expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual( - 'John Smith; 2019: title, doi_constants.publisher.name, https://doi.org/test' - ); - expect( - wrapper.find('#citation-formatter-copy-citation').first().prop('disabled') - ).toEqual(false); - }); - - it('formats citation on load without a doi', async () => { - const newProps = { ...props, doi: undefined }; - const wrapper = createWrapper(newProps); - await act(async () => flushPromises()); - wrapper.update(); - - expect( - wrapper - .find( - '[aria-label="studies.details.citation_formatter.select_arialabel"]' - ) - .exists() - ).toBeFalsy(); - - expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual('John Smith; 2019: title, doi_constants.publisher.name'); - expect( - wrapper.find('#citation-formatter-copy-citation').first().prop('disabled') - ).toEqual(false); + await screen.findByRole('button', { + name: 'studies.details.citation_formatter.copy_citation_arialabel', + }) + ).toBeEnabled(); }); it('sends axios request to fetch a formatted citation when a format is selected', async () => { - const wrapper = createWrapper(props); - (axios.get as jest.Mock).mockResolvedValue({ data: 'This is a test', }); - wrapper - .find('input') - .first() - .simulate('change', { target: { value: 'format2' } }); - await act(async () => flushPromises()); - wrapper.update(); + renderComponent(props); + + // click on the format dropdown + await user.click( + within( + await screen.findByLabelText( + 'studies.details.citation_formatter.select_arialabel' + ) + ).getByRole('button') + ); + // then select the format2 option + await user.click(await screen.findByRole('option', { name: 'format2' })); const params = new URLSearchParams({ style: 'format2', @@ -181,42 +135,40 @@ describe('Citation formatter component tests', () => { params.toString() ); + expect(await screen.findByText('This is a test')).toBeInTheDocument(); expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual('This is a test'); - expect( - wrapper.find('#citation-formatter-copy-citation').first().prop('disabled') - ).toEqual(false); + await screen.findByRole('button', { + name: 'studies.details.citation_formatter.copy_citation_arialabel', + }) + ).toBeEnabled(); }); it('copies data citation to clipboard', async () => { - const wrapper = createWrapper(props); - await act(async () => flushPromises()); - wrapper.update(); + renderComponent(props); // Mock the clipboard object - const testWriteText = jest.fn(); - Object.assign(navigator, { - clipboard: { - writeText: testWriteText, - }, - }); + const testWriteText = jest.spyOn(navigator.clipboard, 'writeText'); expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual( - 'John Smith; 2019: title, doi_constants.publisher.name, https://doi.org/test' - ); + await screen.findByText( + 'John Smith; 2019: title, doi_constants.publisher.name, https://doi.org/test' + ) + ).toBeInTheDocument(); - wrapper.find('#citation-formatter-copy-citation').last().simulate('click'); + await user.click( + await screen.findByRole('button', { + name: 'studies.details.citation_formatter.copy_citation_arialabel', + }) + ); expect(testWriteText).toHaveBeenCalledWith( 'John Smith; 2019: title, doi_constants.publisher.name, https://doi.org/test' ); - expect( - wrapper.find('#citation-formatter-copied-citation').first().text() - ).toEqual('studies.details.citation_formatter.copied_citation'); + await screen.findByRole('button', { + name: 'studies.details.citation_formatter.copied_citation', + }) + ).toBeInTheDocument(); }); it('displays error message when axios request to fetch a formatted citation fails', async () => { @@ -226,13 +178,18 @@ describe('Citation formatter component tests', () => { message: 'error', }); - const wrapper = createWrapper(props); - wrapper - .find('input') - .first() - .simulate('change', { target: { value: 'format2' } }); - await act(async () => flushPromises()); - wrapper.update(); + renderComponent(props); + + // click on the format dropdown + await user.click( + within( + await screen.findByLabelText( + 'studies.details.citation_formatter.select_arialabel' + ) + ).getByRole('button') + ); + // then select the format2 option + await user.click(await screen.findByRole('option', { name: 'format2' })); const params = new URLSearchParams({ style: 'format2', @@ -250,35 +207,41 @@ describe('Citation formatter component tests', () => { ); expect( - wrapper.find('#citation-formatter-error-message').first().text() - ).toEqual('studies.details.citation_formatter.error'); - + await screen.findByText('studies.details.citation_formatter.error') + ).toBeInTheDocument(); expect( - wrapper.find('#citation-formatter-copy-citation').first().prop('disabled') - ).toEqual(true); + await screen.findByRole('button', { + name: 'studies.details.citation_formatter.copy_citation_arialabel', + }) + ).toBeDisabled(); }); it('displays loading spinner while waiting for a response from DataCite', async () => { - (axios.get as jest.Mock).mockRejectedValueOnce({ - message: 'error', - }); + let reject: () => void; + (axios.get as jest.Mock).mockReturnValueOnce( + new Promise((_, _reject) => { + reject = _reject; + }) + ); - const wrapper = createWrapper(props); - wrapper - .find('input') - .first() - .simulate('change', { target: { value: 'format2' } }); - wrapper.update(); + renderComponent(props); - expect( - wrapper.find('[data-testid="loading-spinner"]').exists() - ).toBeTruthy(); + // click on the format dropdown + await user.click( + within( + await screen.findByLabelText( + 'studies.details.citation_formatter.select_arialabel' + ) + ).getByRole('button') + ); + // then select the format2 option + await user.click(await screen.findByRole('option', { name: 'format2' })); - await act(async () => flushPromises()); - wrapper.update(); + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); - expect( - wrapper.find('[data-testid="loading-spinner"]').exists() - ).toBeFalsy(); + reject(); + await waitFor(() => { + expect(screen.queryByRole('progressbar')).toBeNull(); + }); }); }); diff --git a/packages/datagateway-dataview/src/views/landing/isis/__snapshots__/isisStudyLanding.component.test.tsx.snap b/packages/datagateway-dataview/src/views/landing/isis/__snapshots__/isisStudyLanding.component.test.tsx.snap deleted file mode 100644 index 7d5129da4..000000000 --- a/packages/datagateway-dataview/src/views/landing/isis/__snapshots__/isisStudyLanding.component.test.tsx.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ISIS Study Landing page renders structured data correctly 1`] = ` - - - -`; diff --git a/packages/datagateway-dataview/src/views/landing/isis/isisBranding.component.tsx b/packages/datagateway-dataview/src/views/landing/isis/isisBranding.component.tsx index 7905fb5e1..f37705cca 100644 --- a/packages/datagateway-dataview/src/views/landing/isis/isisBranding.component.tsx +++ b/packages/datagateway-dataview/src/views/landing/isis/isisBranding.component.tsx @@ -42,7 +42,7 @@ const Branding = (props: { pluginHost: string }): React.ReactElement => { diff --git a/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.test.tsx b/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.test.tsx index 6883f3839..7f325d9fc 100644 --- a/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.test.tsx +++ b/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import ISISDatasetLanding from './isisDatasetLanding.component'; import { initialState as dgDataViewInitialState } from '../../../state/reducers/dgdataview.reducer'; import configureStore from 'redux-mock-store'; @@ -11,11 +11,12 @@ import { } from 'datagateway-common'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; -import { Typography } from '@mui/material'; -import { mount, ReactWrapper } from 'enzyme'; import { createMemoryHistory, History } from 'history'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { Router } from 'react-router-dom'; +import { render, type RenderResult, screen } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -32,11 +33,11 @@ describe('ISIS Dataset Landing page', () => { const mockStore = configureStore([thunk]); let state: StateType; let history: History; + let user: UserEvent; - const createWrapper = (studyHierarchy = false): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (studyHierarchy = false): RenderResult => + render( + { ); - }; const initialData: Dataset = { id: 87, @@ -68,6 +68,7 @@ describe('ISIS Dataset Landing page', () => { description: 'The first type', }, }; + beforeEach(() => { state = JSON.parse( JSON.stringify({ @@ -76,6 +77,7 @@ describe('ISIS Dataset Landing page', () => { }) ); history = createMemoryHistory(); + user = userEvent.setup(); (useDatasetDetails as jest.Mock).mockReturnValue({ data: initialData, @@ -89,71 +91,83 @@ describe('ISIS Dataset Landing page', () => { jest.clearAllMocks(); }); - it('calls the correct data fetching hooks', () => { - createWrapper(); + describe('links to the correct url in the datafiles tab', () => { + it('for facility cycle hierarchy and normal view', async () => { + renderComponent(); - expect(useDatasetDetails).toHaveBeenCalledWith(87); - expect(useDatasetSizes).toHaveBeenCalledWith(initialData); - }); + await user.click( + await screen.findByRole('tab', { name: 'datasets.details.datafiles' }) + ); + + expect(history.location.pathname).toBe( + '/browse/instrument/4/facilityCycle/5/investigation/1/dataset/87/datafile' + ); + }); - it('links to the correct url in the datafiles tab for both hierarchies and both views', () => { - const facilityCycleWrapper = createWrapper(); + it('for facility cycle hierarchy and cards view', async () => { + history.replace('/?view=card'); + renderComponent(); - facilityCycleWrapper - .find('#dataset-datafiles-tab') - .last() - .simulate('click'); + await user.click( + await screen.findByRole('tab', { name: 'datasets.details.datafiles' }) + ); - expect(history.location.pathname).toBe( - '/browse/instrument/4/facilityCycle/5/investigation/1/dataset/87/datafile' - ); + expect(history.location.pathname).toBe( + '/browse/instrument/4/facilityCycle/5/investigation/1/dataset/87/datafile' + ); + expect(history.location.search).toBe('?view=card'); + }); - history.replace('/?view=card'); - const studyWrapper = createWrapper(true); + it('for study hierarchy and normal view', async () => { + renderComponent(true); - studyWrapper.find('#dataset-datafiles-tab').last().simulate('click'); + await user.click( + await screen.findByRole('tab', { name: 'datasets.details.datafiles' }) + ); - expect(history.location.pathname).toBe( - '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset/87/datafile' - ); - expect(history.location.search).toBe('?view=card'); + expect(history.location.pathname).toBe( + '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset/87/datafile' + ); + }); + + it('for study hierarchy and cards view', async () => { + history.replace('/?view=card'); + renderComponent(true); + + await user.click( + await screen.findByRole('tab', { name: 'datasets.details.datafiles' }) + ); + + expect(history.location.pathname).toBe( + '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset/87/datafile' + ); + expect(history.location.search).toBe('?view=card'); + }); }); - it('displays DOI and renders the expected Link ', () => { - const wrapper = createWrapper(); - - expect( - wrapper - .find('[data-testid="isis-dataset-landing-doi-link"]') - .first() - .text() - ).toEqual('doi 1'); - - expect( - wrapper - .find('[data-testid="isis-dataset-landing-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/doi 1'); + it('displays DOI and renders the expected Link ', async () => { + renderComponent(); + expect(await screen.findByRole('link', { name: 'doi 1' })).toHaveAttribute( + 'href', + 'https://doi.org/doi 1' + ); }); it('useDatasetSizes queries not sent if no data returned from useDatasetDetails', () => { (useDatasetDetails as jest.Mock).mockReturnValue({ data: undefined, }); - createWrapper(); + renderComponent(); expect(useDatasetSizes).toHaveBeenCalledWith(undefined); }); - it('incomplete datasets render correctly', () => { + it('incomplete datasets render correctly', async () => { initialData.complete = false; (useDatasetDetails as jest.Mock).mockReturnValue({ data: initialData, }); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.find(Typography).last().text()).toEqual( - 'datasets.incomplete' - ); + expect(await screen.findByText('datasets.incomplete')).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.tsx b/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.tsx index 0ac5af5b5..f8497c8c8 100644 --- a/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.tsx +++ b/packages/datagateway-dataview/src/views/landing/isis/isisDatasetLanding.component.tsx @@ -135,7 +135,7 @@ const LandingPage = (props: LandingPageProps): React.ReactElement => { ]; return ( - + diff --git a/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.test.tsx b/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.test.tsx index 0fe38378c..039741641 100644 --- a/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.test.tsx +++ b/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import ISISInvestigationLanding from './isisInvestigationLanding.component'; import { initialState as dgDataViewInitialState } from '../../../state/reducers/dgdataview.reducer'; import configureStore from 'redux-mock-store'; @@ -10,12 +10,17 @@ import { } from 'datagateway-common'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; -import { mount, ReactWrapper } from 'enzyme'; import { createMemoryHistory, History } from 'history'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { Router } from 'react-router-dom'; -import { flushPromises } from '../../../setupTests'; -import { act } from 'react-dom/test-utils'; +import { + render, + type RenderResult, + screen, + within, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -32,11 +37,11 @@ describe('ISIS Investigation Landing page', () => { const mockStore = configureStore([thunk]); let state: StateType; let history: History; + let user: UserEvent; - const createWrapper = (studyHierarchy = false): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (studyHierarchy = false): RenderResult => + render( + { ); - }; const initialData = [ { id: 1, - title: 'Test 1', + title: 'Test title 1', name: 'Test 1', summary: 'foo bar', - visitId: '1', + visitId: 'visit id 1', doi: 'doi 1', size: 1, + facility: { + name: 'LILS', + }, investigationInstruments: [ { id: 1, @@ -173,12 +180,14 @@ describe('ISIS Investigation Landing page', () => { }) ); history = createMemoryHistory(); + user = userEvent.setup(); (useInvestigation as jest.Mock).mockReturnValue({ data: initialData, }); (useInvestigationSizes as jest.Mock).mockReturnValue([ { + isSuccess: true, data: 1, }, ]); @@ -188,239 +197,382 @@ describe('ISIS Investigation Landing page', () => { jest.clearAllMocks(); }); - it('calls the correct data fetching hooks', () => { - createWrapper(); + it('renders landing for investigation correctly', () => { + renderComponent(); - expect(useInvestigation).toHaveBeenCalledWith(1, [ - { - filterType: 'include', - filterValue: JSON.stringify([ - { - investigationUsers: 'user', - }, - 'samples', - 'publications', - 'datasets', - { - studyInvestigations: 'study', - }, - { - investigationInstruments: 'instrument', - }, - ]), - }, - ]); - expect(useInvestigationSizes).toHaveBeenCalledWith(initialData); - }); + // branding should be visible + expect(screen.getByRole('img', { name: 'STFC Logo' })).toBeInTheDocument(); + expect( + screen.getByText('doi_constants.branding.title') + ).toBeInTheDocument(); + expect(screen.getByText('doi_constants.branding.body')).toBeInTheDocument(); - it('links to the correct url in the datafiles tab for both hierarchies and both views', () => { - const facilityCycleWrapper = createWrapper(); + // investigation details should be visible + expect(screen.getByText('Test title 1')).toBeInTheDocument(); + expect(screen.getByText('foo bar')).toBeInTheDocument(); - facilityCycleWrapper - .find('#investigation-datasets-tab') - .last() - .simulate('click'); + // publisher section should be visible + expect(screen.getByText('studies.details.publisher')).toBeInTheDocument(); + expect( + screen.getByText('doi_constants.publisher.name') + ).toBeInTheDocument(); + + // investigation samples should be hidden (initial data does not have samples) + expect( + screen.queryByText('investigations.details.samples.label') + ).toBeNull(); + expect( + screen.queryByText('investigations.details.samples.no_samples') + ).toBeNull(); + expect( + screen.queryAllByLabelText(/landing-investigation-sample-\d+$/) + ).toHaveLength(0); + + // publication section should be hidden (initial data does not have publications) + expect( + screen.queryByText('investigations.details.publications.label') + ).toBeNull(); + expect( + screen.queryByText('investigations.details.publications.no_publications') + ).toBeNull(); + expect( + screen.queryAllByLabelText(/landing-investigation-reference-\d+$/) + ).toHaveLength(0); - expect(history.location.pathname).toBe( - '/browse/instrument/4/facilityCycle/5/investigation/1/dataset' + // short format information should be visible + expect(screen.getByText('investigations.visit_id:')).toBeInTheDocument(); + expect(screen.getByText('visit id 1')).toBeInTheDocument(); + expect(screen.getByText('investigations.doi:')).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'doi 1' })).toHaveAttribute( + 'href', + 'https://doi.org/doi 1' + ); + expect(screen.getByText('investigations.parent_doi:')).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'study pid' })).toHaveAttribute( + 'href', + 'https://doi.org/study pid' + ); + expect(screen.getByText('investigations.name:')).toBeInTheDocument(); + expect(screen.getByText('Test 1')).toBeInTheDocument(); + expect(screen.getByText('investigations.size:')).toBeInTheDocument(); + expect(screen.getByText('1 B')).toBeInTheDocument(); + expect( + screen.getByText('investigations.details.facility:') + ).toBeInTheDocument(); + expect(screen.getByText('LILS')).toBeInTheDocument(); + expect(screen.getByText('investigations.instrument:')).toBeInTheDocument(); + expect(screen.getByText('LARMOR')).toBeInTheDocument(); + expect(screen.getByText('studies.details.format:')).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'doi_constants.distribution.format' }) + ).toHaveAttribute( + 'href', + 'https://www.isis.stfc.ac.uk/Pages/ISIS-Raw-File-Format.aspx' + ); + expect(screen.queryByText('investigations.release_date:')).toBeNull(); + expect(screen.getByText('investigations.start_date:')).toBeInTheDocument(); + expect(screen.getByText('2019-06-10')).toBeInTheDocument(); + expect(screen.getByText('investigations.end_date:')).toBeInTheDocument(); + expect(screen.getByText('2019-06-11')).toBeInTheDocument(); + + const actionButtonContainer = screen.getByTestId( + 'investigation-landing-action-container' ); - history.replace('/?view=card'); - const studyWrapper = createWrapper(true); + // actions should be visible + expect(actionButtonContainer).toBeInTheDocument(); + expect( + within(actionButtonContainer).getByRole('button', { + name: 'buttons.add_to_cart', + }) + ).toBeInTheDocument(); + }); - studyWrapper.find('#investigation-datasets-tab').last().simulate('click'); + describe('renders datasets for the investigation correctly', () => { + it('for facility cycle hierarchy and normal view', () => { + renderComponent(); + + expect( + screen.getByRole('link', { name: 'datasets.dataset: dataset 1' }) + ).toHaveAttribute( + 'href', + '/browse/instrument/4/facilityCycle/5/investigation/1/dataset/1' + ); + expect(screen.getByText('datasets.doi:')).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'landing-study-doi-link' }) + ).toHaveAttribute('href', 'https://doi.org/dataset doi'); + + // actions for datasets should be visible + const actionContainer = screen.getByTestId( + 'investigation-landing-dataset-0-action-container' + ); + expect(actionContainer).toBeInTheDocument(); + expect( + within(actionContainer).getByRole('button', { + name: 'buttons.add_to_cart', + }) + ).toBeInTheDocument(); + }); - expect(history.location.pathname).toBe( - '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset' - ); - expect(history.location.search).toBe('?view=card'); + it('for facility cycle hierarchy and card view', () => { + history.replace('/?view=card'); + + renderComponent(); + + expect( + screen.getByRole('link', { name: 'datasets.dataset: dataset 1' }) + ).toHaveAttribute( + 'href', + '/browse/instrument/4/facilityCycle/5/investigation/1/dataset/1?view=card' + ); + expect(screen.getByText('datasets.doi:')).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'landing-study-doi-link' }) + ).toHaveAttribute('href', 'https://doi.org/dataset doi'); + + // actions for datasets should be visible + const actionContainer = screen.getByTestId( + 'investigation-landing-dataset-0-action-container' + ); + expect(actionContainer).toBeInTheDocument(); + expect( + within(actionContainer).getByRole('button', { + name: 'buttons.add_to_cart', + }) + ).toBeInTheDocument(); + }); + + it('for study hierarchy and normal view', () => { + renderComponent(true); + + expect( + screen.getByRole('link', { name: 'datasets.dataset: dataset 1' }) + ).toHaveAttribute( + 'href', + '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset/1' + ); + expect(screen.getByText('datasets.doi:')).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'landing-study-doi-link' }) + ).toHaveAttribute('href', 'https://doi.org/dataset doi'); + + // actions for datasets should be visible + const actionContainer = screen.getByTestId( + 'investigation-landing-dataset-0-action-container' + ); + expect(actionContainer).toBeInTheDocument(); + expect( + within(actionContainer).getByRole('button', { + name: 'buttons.add_to_cart', + }) + ).toBeInTheDocument(); + }); + + it('for study hierarchy and card view', () => { + history.push('/?view=card'); + + renderComponent(true); + + expect( + screen.getByRole('link', { name: 'datasets.dataset: dataset 1' }) + ).toHaveAttribute( + 'href', + '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset/1?view=card' + ); + expect(screen.getByText('datasets.doi:')).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'landing-study-doi-link' }) + ).toHaveAttribute('href', 'https://doi.org/dataset doi'); + + // actions for datasets should be visible + const actionContainer = screen.getByTestId( + 'investigation-landing-dataset-0-action-container' + ); + expect(actionContainer).toBeInTheDocument(); + expect( + within(actionContainer).getByRole('button', { + name: 'buttons.add_to_cart', + }) + ).toBeInTheDocument(); + }); }); - it('users displayed correctly', () => { - let wrapper = createWrapper(); + describe('links to the correct url in the datafiles tab', () => { + it('for facility cycle hierarchy and normal view', async () => { + renderComponent(); - expect( - wrapper.find('[aria-label="landing-investigation-users-label"]') - ).toHaveLength(0); - expect( - wrapper.find('[aria-label="landing-investigation-user-0"]') - ).toHaveLength(0); + await user.click( + await screen.findByRole('tab', { + name: 'investigations.details.datasets', + }) + ); + + expect(history.location.pathname).toBe( + '/browse/instrument/4/facilityCycle/5/investigation/1/dataset' + ); + }); + it('for facility cycle hierarchy and cards view', async () => { + history.replace('/?view=card'); + renderComponent(); + + await user.click( + await screen.findByRole('tab', { + name: 'investigations.details.datasets', + }) + ); + + expect(history.location.pathname).toBe( + '/browse/instrument/4/facilityCycle/5/investigation/1/dataset' + ); + expect(history.location.search).toBe('?view=card'); + }); + + it('for study hierarchy and normal view', async () => { + history.replace('/?view=card'); + renderComponent(true); + + await user.click( + await screen.findByRole('tab', { + name: 'investigations.details.datasets', + }) + ); + + expect(history.location.pathname).toBe( + '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset' + ); + }); + + it('for study hierarchy and cards view', async () => { + history.replace('/?view=card'); + renderComponent(true); + + await user.click( + await screen.findByRole('tab', { + name: 'investigations.details.datasets', + }) + ); + + expect(history.location.pathname).toBe( + '/browseStudyHierarchy/instrument/4/study/5/investigation/1/dataset' + ); + expect(history.location.search).toBe('?view=card'); + }); + }); + + it('users displayed correctly', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], investigationUsers: investigationUser }], }); - wrapper = createWrapper(); + renderComponent(); expect( - wrapper.find('[aria-label="landing-investigation-users-label"]') - ).toHaveLength(4); - expect( - wrapper.find('[aria-label="landing-investigation-user-0"]').first().text() - ).toEqual('Principal Investigator: John Smith'); + await screen.findByLabelText('landing-investigation-user-0') + ).toHaveTextContent('Principal Investigator: John Smith'); expect( - wrapper.find('[aria-label="landing-investigation-user-1"]').first().text() - ).toEqual('Local Contact: Jane Smith'); + await screen.findByLabelText('landing-investigation-user-1') + ).toHaveTextContent('Local Contact: Jane Smith'); expect( - wrapper.find('[aria-label="landing-investigation-user-2"]').first().text() - ).toEqual('Experimenter: Jesse Smith'); - expect( - wrapper.find('[aria-label="landing-investigation-user-3"]') - ).toHaveLength(0); + await screen.findByLabelText('landing-investigation-user-2') + ).toHaveTextContent('Experimenter: Jesse Smith'); }); - it('renders text "No samples" when no data is present', () => { - let wrapper = createWrapper(); + it('renders text "No samples" when no data is present', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], samples: noSamples }], }); - wrapper = createWrapper(); + renderComponent(); expect( - wrapper - .find('[data-testid="investigation-details-panel-no-samples"]') - .exists() - ).toBeTruthy(); + await screen.findByText('investigations.details.samples.no_samples') + ).toBeInTheDocument(); }); - it('renders text "No publications" when no data is present', () => { - let wrapper = createWrapper(); + it('renders text "No publications" when no data is present', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], publications: noPublication }], }); - wrapper = createWrapper(); + renderComponent(); expect( - wrapper - .find('[data-testid="investigation-details-panel-no-publications"]') - .exists() - ).toBeTruthy(); + await screen.findByText( + 'investigations.details.publications.no_publications' + ) + ).toBeInTheDocument(); }); - it('publications displayed correctly', () => { - let wrapper = createWrapper(); - - expect( - wrapper.find('[aria-label="landing-investigation-publications-label"]') - ).toHaveLength(0); - expect( - wrapper.find('[aria-label="landing-investigation-publication-0"]') - ).toHaveLength(0); - + it('publications displayed correctly', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], publications: publication }], }); - wrapper = createWrapper(); + renderComponent(); expect( - wrapper.find('[aria-label="landing-investigation-publications-label"]') - ).toHaveLength(4); - expect( - wrapper - .find('[aria-label="landing-investigation-reference-0"]') - .first() - .text() - ).toEqual('Journal, Author, Date, DOI'); + await screen.findByText('Journal, Author, Date, DOI') + ).toBeInTheDocument(); }); - it('samples displayed correctly', () => { - let wrapper = createWrapper(); - - expect( - wrapper.find('[aria-label="landing-investigation-samples-label"]') - ).toHaveLength(0); - expect( - wrapper.find('[aria-label="landing-investigation-sample-0"]') - ).toHaveLength(0); - + it('samples displayed correctly', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], samples: sample }], }); - wrapper = createWrapper(); + renderComponent(); - expect( - wrapper.find('[aria-label="landing-investigation-samples-label"]') - ).toHaveLength(4); - expect( - wrapper - .find('[aria-label="landing-investigation-sample-0"]') - .first() - .text() - ).toEqual('Sample'); + expect(await screen.findByText('Sample')).toBeInTheDocument(); }); it('displays citation correctly when study missing', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], studyInvestigations: undefined }], }); - const wrapper = createWrapper(); - await act(async () => flushPromises()); - wrapper.update(); + renderComponent(); + expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual( - '2019: Test 1, doi_constants.publisher.name, https://doi.org/doi 1' - ); + await screen.findByText( + '2019: Test title 1, doi_constants.publisher.name, https://doi.org/doi 1' + ) + ).toBeInTheDocument(); }); it('displays citation correctly with one user', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], investigationUsers: [investigationUser[0]] }], }); - const wrapper = createWrapper(); - await act(async () => flushPromises()); - wrapper.update(); + renderComponent(); + expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual( - 'John Smith; 2019: Test 1, doi_constants.publisher.name, https://doi.org/doi 1' - ); + await screen.findByText( + 'John Smith; 2019: Test title 1, doi_constants.publisher.name, https://doi.org/doi 1' + ) + ).toBeInTheDocument(); }); it('displays citation correctly with multiple users', async () => { (useInvestigation as jest.Mock).mockReturnValue({ data: [{ ...initialData[0], investigationUsers: investigationUser }], }); - const wrapper = createWrapper(); - await act(async () => flushPromises()); - wrapper.update(); - expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual( - 'John Smith et al; 2019: Test 1, doi_constants.publisher.name, https://doi.org/doi 1' - ); - }); + renderComponent(); - it('displays DOI and renders the expected Link ', () => { - const wrapper = createWrapper(); expect( - wrapper - .find('[data-testid="isis-investigation-landing-doi-link"]') - .first() - .text() - ).toEqual('doi 1'); - - expect( - wrapper - .find('[data-testid="isis-investigation-landing-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/doi 1'); + await screen.findByText( + 'John Smith et al; 2019: Test title 1, doi_constants.publisher.name, https://doi.org/doi 1' + ) + ).toBeInTheDocument(); }); - it('displays Experiment DOI (PID) and renders the expected Link ', () => { - const wrapper = createWrapper(); - expect( - wrapper - .find('[data-testid="isis-investigations-landing-parent-doi-link"]') - .first() - .text() - ).toEqual('study pid'); + it('displays DOI and renders the expected Link ', async () => { + renderComponent(); + expect(await screen.findByRole('link', { name: 'doi 1' })).toHaveAttribute( + 'href', + 'https://doi.org/doi 1' + ); + }); + it('displays Experiment DOI (PID) and renders the expected Link ', async () => { + renderComponent(); expect( - wrapper - .find('[data-testid="isis-investigations-landing-parent-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/study pid'); + await screen.findByRole('link', { name: 'study pid' }) + ).toHaveAttribute('href', 'https://doi.org/study pid'); }); }); diff --git a/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.tsx b/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.tsx index 820f73df7..7b574ba26 100644 --- a/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.tsx +++ b/packages/datagateway-dataview/src/views/landing/isis/isisInvestigationLanding.component.tsx @@ -284,7 +284,10 @@ const LandingPage = (props: LandingPageProps): React.ReactElement => { ]; return ( - + @@ -434,11 +437,12 @@ const LandingPage = (props: LandingPageProps): React.ReactElement => { {field.content(data[0] as Investigation)} + z ) )} {/* Actions */} - + { ) )} - + { const originalModule = jest.requireActual('datagateway-common'); @@ -27,11 +27,11 @@ describe('ISIS Study Landing page', () => { const mockStore = configureStore([thunk]); let state: StateType; let history: History; + let user: UserEvent; - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -39,7 +39,6 @@ describe('ISIS Study Landing page', () => { ); - }; const investigationUser = [ { @@ -136,6 +135,7 @@ describe('ISIS Study Landing page', () => { ); history = createMemoryHistory(); + user = userEvent.setup(); (useStudy as jest.Mock).mockReturnValue({ data: initialData, @@ -146,42 +146,35 @@ describe('ISIS Study Landing page', () => { jest.clearAllMocks(); }); - it('calls the correct data fetching hooks', () => { - createWrapper(); - - expect(useStudy).toHaveBeenCalledWith(5); - }); - - it('links to the correct url in the datafiles tab for both hierarchies and both views', () => { - let wrapper = createWrapper(); + describe('links to the correct url in the datafiles tab', () => { + it('in normal view', async () => { + renderComponent(); - wrapper.find('#study-investigations-tab').last().simulate('click'); + await user.click( + screen.getByRole('tab', { name: 'studies.details.investigations' }) + ); - expect(history.location.pathname).toBe( - '/browseStudyHierarchy/instrument/4/study/5/investigation' - ); + expect(history.location.pathname).toBe( + '/browseStudyHierarchy/instrument/4/study/5/investigation' + ); + }); - history.replace('/?view=card'); - wrapper = createWrapper(); + it('in cards view', async () => { + history.replace('/?view=card'); + renderComponent(); - wrapper.find('#study-investigations-tab').last().simulate('click'); + await user.click( + screen.getByRole('tab', { name: 'studies.details.investigations' }) + ); - expect(history.location.pathname).toBe( - '/browseStudyHierarchy/instrument/4/study/5/investigation' - ); - expect(history.location.search).toBe('?view=card'); + expect(history.location.pathname).toBe( + '/browseStudyHierarchy/instrument/4/study/5/investigation' + ); + expect(history.location.search).toBe('?view=card'); + }); }); - it('single user displayed correctly', () => { - let wrapper = createWrapper(); - - expect( - wrapper.find('[data-testid="landing-study-users-label"]') - ).toHaveLength(0); - expect(wrapper.find('[data-testid="landing-study-user-0"]')).toHaveLength( - 0 - ); - + it('single user displayed correctly', async () => { (useStudy as jest.Mock).mockReturnValue({ data: [ { @@ -197,14 +190,11 @@ describe('ISIS Study Landing page', () => { }, ], }); - wrapper = createWrapper(); + renderComponent(); - expect( - wrapper.find('[data-testid="landing-study-users-label"]') - ).toHaveLength(4); - expect( - wrapper.find('[data-testid="landing-study-user-0"]').first().text() - ).toEqual('Principal Investigator: John Smith'); + expect(await screen.findByTestId('landing-study-user-0')).toHaveTextContent( + 'Principal Investigator: John Smith' + ); }); it('multiple users displayed correctly', async () => { @@ -223,63 +213,25 @@ describe('ISIS Study Landing page', () => { }, ], }); - const wrapper = createWrapper(); - await act(async () => flushPromises()); - wrapper.update(); - - expect( - wrapper.find('[data-testid="landing-study-users-label"]') - ).toHaveLength(4); - expect( - wrapper.find('[data-testid="landing-study-user-0"]').first().text() - ).toEqual('Principal Investigator: John Smith'); - expect( - wrapper.find('[data-testid="landing-study-user-1"]').first().text() - ).toEqual('Local Contact: Jane Smith'); - expect( - wrapper.find('[data-testid="landing-study-user-2"]').first().text() - ).toEqual('Experimenter: Jesse Smith'); - expect( - wrapper.find('[data-testid="landing-investigation-user-3"]') - ).toHaveLength(0); + renderComponent(); - expect( - wrapper.find('[data-testid="citation-formatter-citation"]').first().text() - ).toEqual( - 'John Smith et al; 2019: Title 1, doi_constants.publisher.name, https://doi.org/study pid' + expect(await screen.findByTestId('landing-study-user-0')).toHaveTextContent( + 'Principal Investigator: John Smith' + ); + expect(await screen.findByTestId('landing-study-user-1')).toHaveTextContent( + 'Local Contact: Jane Smith' + ); + expect(await screen.findByTestId('landing-study-user-2')).toHaveTextContent( + 'Experimenter: Jesse Smith' ); - }); - - it('displays DOI and renders the expected link', () => { - (useStudy as jest.Mock).mockReturnValue({ - data: [ - { - ...initialData[0], - studyInvestigations: [ - { - investigation: { - ...investigation, - investigationUsers: investigationUser, - }, - }, - ], - }, - ], - }); - const wrapper = createWrapper(); - expect( - wrapper - .find('[data-testid="landing-study-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/doi 1'); - expect( - wrapper.find('[data-testid="landing-study-doi-link"]').first().text() - ).toEqual('doi 1'); + await screen.findByText( + 'John Smith et al; 2019: Title 1, doi_constants.publisher.name, https://doi.org/study pid' + ) + ).toBeInTheDocument(); }); - it('displays Experiment DOI (PID) and renders the expected link', () => { + it('displays DOI and renders the expected link', async () => { (useStudy as jest.Mock).mockReturnValue({ data: [ { @@ -295,29 +247,15 @@ describe('ISIS Study Landing page', () => { }, ], }); - const wrapper = createWrapper(); - expect( - wrapper - .find('[data-testid="landing-study-pid-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/study pid'); + renderComponent(); - expect( - wrapper.find('[data-testid="landing-study-pid-link"]').first().text() - ).toEqual('study pid'); + expect(await screen.findByRole('link', { name: 'doi 1' })).toHaveAttribute( + 'href', + 'https://doi.org/doi 1' + ); }); - it.skip('renders structured data correctly', () => { - // mock getElementByTagNameSpy so we can snapshot mockElement - const docFragment = document.createDocumentFragment(); - const mockElement = document.createElement('head'); - docFragment.appendChild(mockElement); - const mockHTMLCollection = docFragment.children; - jest - .spyOn(document, 'getElementsByTagName') - .mockReturnValue(mockHTMLCollection); - + it('displays Experiment DOI (PID) and renders the expected link', async () => { (useStudy as jest.Mock).mockReturnValue({ data: [ { @@ -333,8 +271,10 @@ describe('ISIS Study Landing page', () => { }, ], }); - createWrapper(); + renderComponent(); - expect(mockElement).toMatchSnapshot(); + expect( + await screen.findByRole('link', { name: 'study pid' }) + ).toHaveAttribute('href', 'https://doi.org/study pid'); }); }); diff --git a/packages/datagateway-dataview/src/views/landing/isis/isisStudyLanding.component.tsx b/packages/datagateway-dataview/src/views/landing/isis/isisStudyLanding.component.tsx index dfccab55d..f772b0fd0 100644 --- a/packages/datagateway-dataview/src/views/landing/isis/isisStudyLanding.component.tsx +++ b/packages/datagateway-dataview/src/views/landing/isis/isisStudyLanding.component.tsx @@ -335,7 +335,7 @@ const LandingPage = (props: LandingPageProps): React.ReactElement => { ]; return ( - + diff --git a/packages/datagateway-dataview/src/views/roleSelector.component.test.tsx b/packages/datagateway-dataview/src/views/roleSelector.component.test.tsx index 5da7d0ef7..a4b0ab552 100644 --- a/packages/datagateway-dataview/src/views/roleSelector.component.test.tsx +++ b/packages/datagateway-dataview/src/views/roleSelector.component.test.tsx @@ -3,20 +3,21 @@ import axios from 'axios'; import configureStore from 'redux-mock-store'; import { dGCommonInitialState, - StateType, InvestigationUser, + parseSearchToQuery, readSciGatewayToken, + StateType, usePushFilter, - parseSearchToQuery, } from 'datagateway-common'; import { initialState as dgDataViewInitialState } from '../state/reducers/dgdataview.reducer'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { MemoryRouter } from 'react-router-dom'; -import { mount, ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import RoleSelector from './roleSelector.component'; -import { flushPromises } from '../setupTests'; +import { render, type RenderResult, screen } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -31,15 +32,15 @@ jest.mock('datagateway-common', () => { }); describe('Role Selector', () => { - const mockStore = configureStore([thunk]); let state: StateType; + let user: UserEvent; let mockData: InvestigationUser[] = []; const mockPushFilters = jest.fn(); + const mockStore = configureStore([thunk]); - const createWrapper = (): ReactWrapper => { - const store = mockStore(state); - return mount( - + const renderComponent = (): RenderResult => + render( + @@ -47,9 +48,9 @@ describe('Role Selector', () => { ); - }; beforeEach(() => { + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ dgdataview: dgDataViewInitialState, @@ -88,7 +89,7 @@ describe('Role Selector', () => { }); it('fetches roles when rendered and displays them in dropdown', async () => { - const wrapper = createWrapper(); + renderComponent(); const params = new URLSearchParams(); params.append('distinct', JSON.stringify('role')); @@ -108,44 +109,36 @@ describe('Role Selector', () => { expect((axios.get as jest.Mock).mock.calls[0][1].params.toString()).toBe( params.toString() ); - wrapper - .find('#role-selector') - .find('[role="button"]') - .last() - .simulate('mousedown', { button: 0 }); - // Flush promises and update the wrapper. - await flushPromises(); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'my_data_table.role_selector' }) + ); - expect(wrapper.find('[role="listbox"] li[role="option"]').length).toBe(3); expect( - wrapper.find('[role="listbox"] li[role="option"]').at(0).text() - ).toBe('my_data_table.all_roles'); + await screen.findByRole('option', { name: 'my_data_table.all_roles' }) + ).toBeInTheDocument(); expect( - wrapper.find('[role="listbox"] li[role="option"]').at(1).text() - ).toBe(mockData[0].role.toLowerCase()); + await screen.findByRole('option', { + name: mockData[0].role.toLowerCase(), + }) + ).toBeInTheDocument(); expect( - wrapper.find('[role="listbox"] li[role="option"]').at(2).text() - ).toBe(mockData[1].role); + await screen.findByRole('option', { name: mockData[1].role }) + ).toBeInTheDocument(); }); it('updates filters when role is selected', async () => { - const wrapper = createWrapper(); - - // Flush promises and update the wrapper. - await flushPromises(); - wrapper.update(); - - const selectInput = wrapper.find('input').first(); - selectInput.simulate('change', { target: { value: 'PI' } }); - - selectInput.simulate('change', { target: { value: '' } }); + renderComponent(); - expect(mockPushFilters).toHaveBeenCalledWith( - 'investigationUsers.role', - null + await user.click( + await screen.findByRole('button', { name: 'my_data_table.role_selector' }) ); + await user.click(await screen.findByRole('option', { name: 'pi' })); + + expect(mockPushFilters).toHaveBeenCalledWith('investigationUsers.role', { + value: 'PI', + type: 'include', + }); }); it('parses current role from query params correctly', async () => { @@ -158,15 +151,8 @@ describe('Role Selector', () => { }, }); - const wrapper = createWrapper(); - - // before roles have been loaded, it should be set to "" as a default - expect(wrapper.find('input').prop('value')).toBe(''); - - // Flush promises and update the wrapper. - await flushPromises(); - wrapper.update(); + renderComponent(); - expect(wrapper.find('input').prop('value')).toBe('experimenter'); + expect(await screen.findByText('experimenter')).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-dataview/src/views/roleSelector.component.tsx b/packages/datagateway-dataview/src/views/roleSelector.component.tsx index 43ad49e10..d6a22979d 100644 --- a/packages/datagateway-dataview/src/views/roleSelector.component.tsx +++ b/packages/datagateway-dataview/src/views/roleSelector.component.tsx @@ -97,8 +97,11 @@ const RoleSelector: React.FC = () => { variant="standard" sx={{ margin: 1, minWidth: '100px' }} > - {t('my_data_table.role_selector')} + + {t('my_data_table.role_selector')} + + +
    +
    +
    +
    +
    - - - - searchBox.limited_results_message {maxNumResults:{tabs:{datasetTab:false,datafileTab:false,investigationTab:false},selectAllSetting:true,settingsLoaded:false,sideLayout:false,searchableEntities:[investigation,dataset,datafile],maxNumResults:300}} - +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    +
    - - - -`; - -exports[`SearchBoxContainerSide - Tests renders searchBoxContainerSide correctly 1`] = ` - - - - - - - - +
    - - - - - - - - +
    +
    + + + + +
    +
    +
    + +
    - - - - +
    +
    + +
    +
    +
    + +
    `; diff --git a/packages/datagateway-search/src/__snapshots__/searchPageContainer.component.test.tsx.snap b/packages/datagateway-search/src/__snapshots__/searchPageContainer.component.test.tsx.snap deleted file mode 100644 index c2ceec09e..000000000 --- a/packages/datagateway-search/src/__snapshots__/searchPageContainer.component.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SearchPageContainer - Tests renders searchPageContainer correctly 1`] = ` - - - Search data - - -`; diff --git a/packages/datagateway-search/src/card/__snapshots__/datasetSearchCardView.component.test.tsx.snap b/packages/datagateway-search/src/card/__snapshots__/datasetSearchCardView.component.test.tsx.snap deleted file mode 100644 index c47ae51e9..000000000 --- a/packages/datagateway-search/src/card/__snapshots__/datasetSearchCardView.component.test.tsx.snap +++ /dev/null @@ -1,141 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dataset - Card View renders correctly 1`] = ` -Object { - "buttons": Array [ - [Function], - ], - "data": Array [ - Object { - "createTime": "2019-07-23", - "endDate": "2019-07-25", - "id": 1, - "investigation": Object { - "doi": "doi 1", - "endDate": "2019-06-11", - "facility": Object { - "id": 7, - "name": "facility name", - }, - "id": 2, - "investigationInstruments": Array [ - Object { - "id": 3, - "instrument": Object { - "id": 4, - "name": "LARMOR", - }, - }, - ], - "name": "Investigation test name", - "size": 1, - "startDate": "2019-06-10", - "studyInvestigations": Array [ - Object { - "id": 5, - "investigation": Object { - "id": 2, - "name": "Investigation test name", - "title": "Investigation test title", - "visitId": "1", - }, - "study": Object { - "createTime": "2019-06-10", - "id": 6, - "modTime": "2019-06-10", - "name": "study name", - "pid": "study pid", - }, - }, - ], - "summary": "foo bar", - "title": "Investigation test title", - "visitId": "1", - }, - "modTime": "2019-07-23", - "name": "Dataset test name", - "size": 1, - "startDate": "2019-07-24", - }, - ], - "description": Object { - "dataKey": "description", - "filterComponent": [Function], - "label": "datasets.details.description", - }, - "filters": Object {}, - "information": Array [ - Object { - "content": [Function], - "dataKey": "datafileCount", - "disableSort": true, - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "datasets.datafile_count", - }, - Object { - "content": [Function], - "dataKey": "investigation.title", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "datasets.investigation", - }, - Object { - "dataKey": "createTime", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "datasets.create_time", - }, - Object { - "dataKey": "modTime", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "datasets.modified_time", - }, - ], - "loadedCount": true, - "loadedData": true, - "moreInformation": [Function], - "onFilter": [Function], - "onPageChange": [Function], - "onResultsChange": [Function], - "onSort": [Function], - "page": null, - "results": null, - "sort": Object {}, - "title": Object { - "content": [Function], - "dataKey": "name", - "filterComponent": [Function], - "label": "datasets.name", - }, - "totalDataCount": 1, -} -`; diff --git a/packages/datagateway-search/src/card/__snapshots__/investigationSearchCardView.component.test.tsx.snap b/packages/datagateway-search/src/card/__snapshots__/investigationSearchCardView.component.test.tsx.snap deleted file mode 100644 index edecd9d12..000000000 --- a/packages/datagateway-search/src/card/__snapshots__/investigationSearchCardView.component.test.tsx.snap +++ /dev/null @@ -1,171 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Investigation - Card View renders correctly 1`] = ` -Object { - "buttons": Array [ - [Function], - ], - "data": Array [ - Object { - "createTime": "2019-07-23", - "doi": "doi 1", - "endDate": "2019-07-25", - "id": 1, - "investigationInstruments": Array [ - Object { - "id": 3, - "instrument": Object { - "id": 4, - "name": "LARMOR", - }, - }, - ], - "modTime": "2019-07-23", - "name": "Investigation test name", - "size": 1, - "startDate": "2019-07-24", - "studyInvestigations": Array [ - Object { - "id": 5, - "investigation": Object { - "id": 2, - "name": "Investigation test name", - "title": "Investigation test title", - "visitId": "1", - }, - "study": Object { - "createTime": "2019-06-10", - "id": 6, - "modTime": "2019-06-10", - "name": "study name", - "pid": "study pid", - }, - }, - ], - "title": "Test 1", - "visitId": "1", - }, - ], - "description": Object { - "dataKey": "summary", - "filterComponent": [Function], - "label": "investigations.details.summary", - }, - "filters": Object {}, - "information": Array [ - Object { - "content": [Function], - "dataKey": "doi", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.doi", - }, - Object { - "dataKey": "visitId", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.visit_id", - }, - Object { - "dataKey": "name", - "disableSort": true, - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.details.name", - }, - Object { - "content": [Function], - "dataKey": "datasetCount", - "disableSort": true, - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.dataset_count", - }, - Object { - "content": [Function], - "dataKey": "investigationInstruments.instrument.name", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.instrument", - "noTooltip": true, - }, - Object { - "dataKey": "startDate", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.details.start_date", - }, - Object { - "dataKey": "endDate", - "filterComponent": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "label": "investigations.details.end_date", - }, - ], - "loadedCount": true, - "loadedData": true, - "moreInformation": [Function], - "onFilter": [Function], - "onPageChange": [Function], - "onResultsChange": [Function], - "onSort": [Function], - "page": null, - "results": null, - "sort": Object {}, - "title": Object { - "content": [Function], - "dataKey": "title", - "filterComponent": [Function], - "label": "investigations.title", - }, - "totalDataCount": 1, -} -`; diff --git a/packages/datagateway-search/src/card/datasetSearchCardView.component.test.tsx b/packages/datagateway-search/src/card/datasetSearchCardView.component.test.tsx index d00a03891..2e179fc8e 100644 --- a/packages/datagateway-search/src/card/datasetSearchCardView.component.test.tsx +++ b/packages/datagateway-search/src/card/datasetSearchCardView.component.test.tsx @@ -1,21 +1,14 @@ -import { ListItemText } from '@mui/material'; import { - AdvancedFilter, - dGCommonInitialState, - useDatasetsPaginated, - useDatasetCount, Dataset, - useLuceneSearch, + dGCommonInitialState, useAllFacilityCycles, - useDatasetSizes, + useDatasetCount, useDatasetsDatafileCount, - CardView, - DLSDatasetDetailsPanel, - ISISDatasetDetailsPanel, - DatasetDetailsPanel, + useDatasetSizes, + useDatasetsPaginated, + useLuceneSearch, } from 'datagateway-common'; -import { mount, ReactWrapper } from 'enzyme'; -import React from 'react'; +import * as React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; import configureStore from 'redux-mock-store'; @@ -29,6 +22,15 @@ import { applyDatePickerWorkaround, cleanupDatePickerWorkaround, } from '../setupTests'; +import { + render, + type RenderResult, + screen, + waitFor, + within, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -50,9 +52,10 @@ describe('Dataset - Card View', () => { let state: StateType; let cardData: Dataset[]; let history: History; + let user: UserEvent; - const createWrapper = (hierarchy?: string): ReactWrapper => { - return mount( + const renderComponent = (hierarchy?: string): RenderResult => + render( @@ -61,7 +64,6 @@ describe('Dataset - Card View', () => { ); - }; beforeEach(() => { cardData = [ @@ -118,6 +120,7 @@ describe('Dataset - Card View', () => { }, ]; history = createMemoryHistory(); + user = userEvent.setup(); mockStore = configureStore([thunk]); state = JSON.parse( @@ -173,157 +176,97 @@ describe('Dataset - Card View', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); - - it('calls the correct data fetching hooks on load', () => { - (useLuceneSearch as jest.Mock).mockReturnValue({ - data: [1], - }); - - createWrapper(); - - expect(useLuceneSearch).toHaveBeenCalledWith('Dataset', { - searchText: '', - startDate: null, - endDate: null, - maxCount: 300, - }); - - expect(useDatasetCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ]); - expect(useDatasetsPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - { - filterType: 'include', - filterValue: JSON.stringify({ - investigation: { investigationInstruments: 'instrument' }, - }), - }, - ]); - expect(useDatasetsDatafileCount).toHaveBeenCalledWith(cardData); - expect(useDatasetSizes).toHaveBeenCalledWith(undefined); - }); - - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('updates filter query params on date filter', async () => { + applyDatePickerWorkaround(); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').last().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + renderComponent(); - expect(history.location.search).toBe( - `?filters=${encodeURIComponent( - '{"name":{"value":"test","type":"include"}}' - )}` + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); - - expect(history.location.search).toBe('?'); - }); - - it('updates filter query params on date filter', () => { - applyDatePickerWorkaround(); - - const wrapper = createWrapper(); - - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + await user.type( + await screen.findByRole('textbox', { + name: 'datasets.modified_time filter to', + }), + '2019-08-06' + ); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"modTime":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear( + await screen.findByRole('textbox', { + name: 'datasets.modified_time filter to', + }) + ); expect(history.location.search).toBe('?'); cleanupDatePickerWorkaround(); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('datasets.name'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'Sort by DATASETS.NAME' }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"name":"asc"}')}` ); }); - it('renders fine with incomplete data', () => { + it('renders fine with incomplete data', async () => { (useDatasetCount as jest.Mock).mockReturnValue({}); (useDatasetsPaginated as jest.Mock).mockReturnValue({}); - expect(() => createWrapper()).not.toThrowError(); + renderComponent(); + + expect(screen.queryAllByTestId('card')).toHaveLength(0); }); - it('renders generic link & pending count correctly', () => { + it('renders generic link & pending count correctly', async () => { (useDatasetsDatafileCount as jest.Mock).mockImplementation(() => [ { isFetching: true, }, ]); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.find(CardView).find('a').first().prop('href')).toEqual( - `/browse/investigation/2/dataset/1/datafile` - ); - expect(wrapper.find(CardView).find('a').first().text()).toEqual( - 'Dataset test name' - ); + const card = (await screen.findAllByTestId('card'))[0]; + + expect( + within(card).getByRole('link', { name: 'Dataset test name' }) + ).toHaveAttribute('href', '/browse/investigation/2/dataset/1/datafile'); expect( - wrapper - .find(CardView) - .first() - .find('[data-testid="card-info-data-datasets.datafile_count"]') - .text() - ).toEqual('Calculating...'); + within(card).getByTestId('card-info-data-datasets.datafile_count') + ).toHaveTextContent('Calculating...'); }); - it("renders DLS link correctly and doesn't allow for download", () => { - const wrapper = createWrapper('dls'); + it("renders DLS link correctly and doesn't allow for download", async () => { + renderComponent('dls'); - expect(wrapper.find(CardView).find('a').first().prop('href')).toEqual( + const card = (await screen.findAllByTestId('card'))[0]; + + expect( + within(card).getByRole('link', { name: 'Dataset test name' }) + ).toHaveAttribute( + 'href', '/browse/proposal/Investigation test name/investigation/2/dataset/1/datafile' ); - expect(wrapper.find(CardView).find('a').first().text()).toEqual( - 'Dataset test name' - ); - expect(wrapper.exists('#add-to-cart-btn-dataset-1')).toBe(true); - expect(wrapper.exists('#download-btn-dataset-1')).toBe(false); + + expect( + screen.getByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: 'buttons.download' }) + ).toBeNull(); }); - it('renders ISIS link & file sizes correctly', () => { + it('renders ISIS link & file sizes correctly', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -335,27 +278,28 @@ describe('Dataset - Card View', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); expect(useDatasetSizes).toHaveBeenCalledWith(cardData); expect(useDatasetsDatafileCount).toHaveBeenCalledWith(undefined); - expect(wrapper.find(CardView).find('a').first().prop('href')).toEqual( - `/browse/instrument/4/facilityCycle/6/investigation/2/dataset/1` - ); - expect(wrapper.find(CardView).find('a').first().text()).toEqual( - 'Dataset test name' + const card = (await screen.findAllByTestId('card'))[0]; + + expect( + within(card).getByRole('link', { + name: 'Dataset test name', + }) + ).toHaveAttribute( + 'href', + '/browse/instrument/4/facilityCycle/6/investigation/2/dataset/1' ); + expect( - wrapper - .find(CardView) - .first() - .find('[data-testid="card-info-data-datasets.size"]') - .text() - ).toEqual('1 B'); + within(card).getByTestId('card-info-data-datasets.size') + ).toHaveTextContent('1 B'); }); - it('does not render ISIS link when instrumentId cannot be found', () => { + it('does not render ISIS link when instrumentId cannot be found', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -372,34 +316,36 @@ describe('Dataset - Card View', () => { data: cardData, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find(CardView).first().find('a')).toHaveLength(0); - expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Dataset test name'); + const card = (await screen.findAllByTestId('card'))[0]; + + await waitFor(() => { + expect( + within(card).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + }); + expect(within(card).getByLabelText('card-title')).toHaveTextContent( + 'Dataset test name' + ); }); - it('does not render ISIS link when facilityCycleId cannot be found', () => { - const wrapper = createWrapper('isis'); + it('does not render ISIS link when facilityCycleId cannot be found', async () => { + renderComponent('isis'); - expect(wrapper.find(CardView).first().find('a')).toHaveLength(0); - expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Dataset test name'); + const card = (await screen.findAllByTestId('card'))[0]; + + await waitFor(() => { + expect( + within(card).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + }); + expect(within(card).getByLabelText('card-title')).toHaveTextContent( + 'Dataset test name' + ); }); - it('does not render ISIS link when facilityCycleId has incompatible dates', () => { + it('does not render ISIS link when facilityCycleId has incompatible dates', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -411,60 +357,63 @@ describe('Dataset - Card View', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find(CardView).first().find('a')).toHaveLength(0); - expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Dataset test name'); + const card = (await screen.findAllByTestId('card'))[0]; + + await waitFor(() => { + expect( + within(card).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + }); + expect(within(card).getByLabelText('card-title')).toHaveTextContent( + 'Dataset test name' + ); }); - it('displays only the dataset name when there is no generic investigation to link to', () => { + it('displays only the dataset name when there is no generic investigation to link to', async () => { delete cardData[0].investigation; (useDatasetsPaginated as jest.Mock).mockReturnValue({ data: cardData, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('data'); + renderComponent('data'); - expect(wrapper.find(CardView).first().find('a')).toHaveLength(0); - expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Dataset test name'); + const card = (await screen.findAllByTestId('card'))[0]; + + await waitFor(() => { + expect( + within(card).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + }); + expect(within(card).getByLabelText('card-title')).toHaveTextContent( + 'Dataset test name' + ); }); - it('displays only the dataset name when there is no DLS investigation to link to', () => { + it('displays only the dataset name when there is no DLS investigation to link to', async () => { delete cardData[0].investigation; (useDatasetsPaginated as jest.Mock).mockReturnValue({ data: cardData, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('dls'); + renderComponent('dls'); - expect(wrapper.find(CardView).first().find('a')).toHaveLength(0); - expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Dataset test name'); + const card = (await screen.findAllByTestId('card'))[0]; + + await waitFor(() => { + expect( + within(card).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + }); + expect(within(card).getByLabelText('card-title')).toHaveTextContent( + 'Dataset test name' + ); }); - it('displays only the dataset name when there is no ISIS investigation to link to', () => { + it('displays only the dataset name when there is no ISIS investigation to link to', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -481,42 +430,39 @@ describe('Dataset - Card View', () => { fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); - - expect(wrapper.find(CardView).first().find('a')).toHaveLength(0); - expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Dataset test name'); - }); + renderComponent('isis'); - it('displays generic details panel when expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(DatasetDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); + const card = (await screen.findAllByTestId('card'))[0]; - expect(wrapper.find(DatasetDetailsPanel).exists()).toBeTruthy(); + await waitFor(() => { + expect( + within(card).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + }); + expect(within(card).getByLabelText('card-title')).toHaveTextContent( + 'Dataset test name' + ); }); - it('displays correct details panel for ISIS when expanded', () => { - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); + it('displays generic details panel when expanded', async () => { + renderComponent(); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect(await screen.findByTestId('dataset-details-panel')).toBeTruthy(); + }); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeTruthy(); + it('displays correct details panel for ISIS when expanded', async () => { + renderComponent('isis'); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + await screen.findByTestId('isis-dataset-details-panel') + ).toBeTruthy(); }); - it('can navigate using the details panel for ISIS when there are facility cycles', () => { + it('can navigate using the details panel for ISIS when there are facility cycles', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -528,29 +474,28 @@ describe('Dataset - Card View', () => { ], }); - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); + renderComponent('isis'); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + await screen.findByTestId('isis-dataset-details-panel') + ).toBeTruthy(); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeTruthy(); + await user.click( + await screen.findByRole('tab', { name: 'datasets.details.datafiles' }) + ); - wrapper.find('#dataset-datafiles-tab').last().simulate('click'); expect(history.location.pathname).toBe( '/browse/instrument/4/facilityCycle/4/investigation/2/dataset/1' ); }); - it('displays correct details panel for DLS when expanded', () => { - const wrapper = createWrapper('dls'); - expect(wrapper.find(DLSDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(DLSDatasetDetailsPanel).exists()).toBeTruthy(); + it('displays correct details panel for DLS when expanded', async () => { + renderComponent('dls'); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect(await screen.findByTestId('dataset-details-panel')).toBeTruthy(); }); }); diff --git a/packages/datagateway-search/src/card/investigationSearchCardView.component.test.tsx b/packages/datagateway-search/src/card/investigationSearchCardView.component.test.tsx index a5d8742a7..e7db3dc27 100644 --- a/packages/datagateway-search/src/card/investigationSearchCardView.component.test.tsx +++ b/packages/datagateway-search/src/card/investigationSearchCardView.component.test.tsx @@ -1,36 +1,38 @@ -import React from 'react'; +import * as React from 'react'; import { dGCommonInitialState, + Investigation, + StateType, useAllFacilityCycles, - useLuceneSearch, useInvestigationCount, - useInvestigationsPaginated, useInvestigationsDatasetCount, useInvestigationSizes, - Investigation, - StateType, - AdvancedFilter, - CardView, - InvestigationDetailsPanel, - ISISInvestigationDetailsPanel, - DLSVisitDetailsPanel, + useInvestigationsPaginated, + useLuceneSearch, } from 'datagateway-common'; import InvestigationSearchCardView from './investigationSearchCardView.component'; import { QueryClient, QueryClientProvider } from 'react-query'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { mount, ReactWrapper } from 'enzyme'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import { createMemoryHistory, History } from 'history'; import { initialState as dgSearchInitialState } from '../state/reducers/dgsearch.reducer'; -import { ListItemText } from '@mui/material'; import { applyDatePickerWorkaround, cleanupDatePickerWorkaround, } from '../setupTests'; +import { + render, + RenderResult, + screen, + waitFor, + within, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -52,9 +54,10 @@ describe('Investigation - Card View', () => { let state: StateType; let cardData: Investigation[]; let history: History; + let user: UserEvent; - const createWrapper = (hierarchy?: string): ReactWrapper => { - return mount( + const renderComponent = (hierarchy?: string): RenderResult => + render( @@ -63,7 +66,6 @@ describe('Investigation - Card View', () => { ); - }; beforeEach(() => { cardData = [ @@ -108,8 +110,9 @@ describe('Investigation - Card View', () => { }, ]; history = createMemoryHistory(); - mockStore = configureStore([thunk]); + user = userEvent.setup(); + state = JSON.parse( JSON.stringify({ dgcommon: dGCommonInitialState, @@ -169,60 +172,19 @@ describe('Investigation - Card View', () => { //The below tests are modified from datasetSearchCardView - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('CardView').props()).toMatchSnapshot(); - }); - - it('calls the correct data fetching hooks on load', () => { - (useLuceneSearch as jest.Mock).mockReturnValue({ - data: [1], - }); + it('updates filter query params on text filter', async () => { + renderComponent(); - createWrapper(); + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - expect(useLuceneSearch).toHaveBeenCalledWith('Investigation', { - searchText: '', - startDate: null, - endDate: null, - maxCount: 300, + const filterInput = await screen.findByRole('textbox', { + name: 'Filter by investigations.title', + hidden: true, }); - expect(useInvestigationCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ]); - expect(useInvestigationsPaginated).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - { - filterType: 'include', - filterValue: JSON.stringify({ - investigationInstruments: 'instrument', - }), - }, - ]); - expect(useInvestigationsDatasetCount).toHaveBeenCalledWith(cardData); - expect(useInvestigationSizes).toHaveBeenCalledWith(undefined); - }); - - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); - - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: 'test' } }); + await user.type(filterInput, 'test'); expect(history.location.search).toBe( `?filters=${encodeURIComponent( @@ -230,46 +192,49 @@ describe('Investigation - Card View', () => { )}` ); - advancedFilter - .find('input') - .first() - .simulate('change', { target: { value: '' } }); - + await user.clear(filterInput); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); + + await user.click( + await screen.findByRole('button', { name: 'advanced_filters.show' }) + ); - const advancedFilter = wrapper.find(AdvancedFilter); - advancedFilter.find('button').first().simulate('click'); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '2019-08-06' } }); + await user.type( + await screen.findByRole('textbox', { + name: 'investigations.details.end_date filter to', + }), + '2019-08-06' + ); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"endDate":{"endDate":"2019-08-06"}}')}` ); - advancedFilter - .find('input') - .last() - .simulate('change', { target: { value: '' } }); + await user.clear( + await screen.findByRole('textbox', { + name: 'investigations.details.end_date filter to', + }) + ); expect(history.location.search).toBe('?'); cleanupDatePickerWorkaround(); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - const button = wrapper.find(ListItemText).first(); - expect(button.text()).toEqual('investigations.title'); - button.find('div').simulate('click'); + await user.click( + await screen.findByRole('button', { + name: 'Sort by INVESTIGATIONS.TITLE', + }) + ); expect(history.location.search).toBe( `?sort=${encodeURIComponent('{"title":"asc"}')}` @@ -280,42 +245,48 @@ describe('Investigation - Card View', () => { (useInvestigationCount as jest.Mock).mockReturnValue({}); (useInvestigationsPaginated as jest.Mock).mockReturnValue({}); - expect(() => createWrapper()).not.toThrowError(); + renderComponent(); + + expect(screen.queryAllByTestId('card')).toHaveLength(0); }); - it('renders generic link & pending count correctly', () => { + it('renders generic link & pending count correctly', async () => { (useInvestigationsDatasetCount as jest.Mock).mockImplementation(() => [ { isFetching: true, }, ]); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.find(CardView).find('a').first().prop('href')).toEqual( - `/browse/investigation/1/dataset` + const card = (await screen.findAllByTestId('card'))[0]; + + expect(within(card).getByRole('link', { name: 'Test 1' })).toHaveAttribute( + 'href', + '/browse/investigation/1/dataset' ); - expect(wrapper.find(CardView).find('a').first().text()).toEqual('Test 1'); expect( - wrapper - .find(CardView) - .first() - .find('[data-testid="card-info-data-investigations.dataset_count"]') - .text() - ).toEqual('Calculating...'); + within(card).getByTestId('card-info-data-investigations.dataset_count') + ).toHaveTextContent('Calculating...'); }); - it("renders DLS link correctly and doesn't allow for cart selection or download", () => { - const wrapper = createWrapper('dls'); + it("renders DLS link correctly and doesn't allow for cart selection or download", async () => { + renderComponent('dls'); + + const card = (await screen.findAllByTestId('card'))[0]; - expect(wrapper.find(CardView).find('a').first().prop('href')).toEqual( + expect(within(card).getByRole('link', { name: 'Test 1' })).toHaveAttribute( + 'href', '/browse/proposal/Investigation test name/investigation/1/dataset' ); - expect(wrapper.find(CardView).find('a').first().text()).toEqual('Test 1'); - expect(wrapper.exists('#add-to-cart-btn-1')).toBe(false); - expect(wrapper.exists('#download-btn-1')).toBe(false); + expect( + screen.queryByRole('button', { name: 'buttons.add_to_cart' }) + ).toBeNull(); + expect( + screen.queryByRole('button', { name: 'buttons.download' }) + ).toBeNull(); }); - it('renders ISIS link & file sizes correctly', () => { + it('renders ISIS link & file sizes correctly', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -327,42 +298,33 @@ describe('Investigation - Card View', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); expect(useInvestigationSizes).toHaveBeenCalledWith(cardData); expect(useInvestigationsDatasetCount).toHaveBeenCalledWith(undefined); - expect(wrapper.find(CardView).find('a').first().prop('href')).toEqual( - `/browse/instrument/4/facilityCycle/6/investigation/1/dataset` - ); - expect(wrapper.find(CardView).find('a').first().text()).toEqual('Test 1'); + const card = (await screen.findAllByTestId('card'))[0]; + expect( - wrapper - .find(CardView) - .first() - .find('[data-testid="card-info-data-investigations.size"]') - .text() - ).toEqual('1 B'); + within(card).getByRole('link', { + name: 'Test 1', + }) + ).toHaveAttribute( + 'href', + '/browse/instrument/4/facilityCycle/6/investigation/1/dataset' + ); }); - it('displays DOI and renders the expected Link ', () => { - const wrapper = createWrapper(); - expect( - wrapper - .find('[data-testid="investigation-search-card-doi-link"]') - .first() - .text() - ).toEqual('doi 1'); + it('displays DOI and renders the expected Link ', async () => { + renderComponent(); - expect( - wrapper - .find('[data-testid="investigation-search-card-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/doi 1'); + expect(await screen.findByRole('link', { name: 'doi 1' })).toHaveAttribute( + 'href', + 'https://doi.org/doi 1' + ); }); - it('does not render ISIS link when instrumentId cannot be found', () => { + it('does not render ISIS link when instrumentId cannot be found', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -379,60 +341,53 @@ describe('Investigation - Card View', () => { data: cardData, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find(CardView).first().find('a')).toHaveLength(1); - expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Test 1'); + const card = (await screen.findAllByTestId('card'))[0]; + + await waitFor(() => { + expect(within(card).queryByRole('link', { name: 'Test 1' })).toBeNull(); + }); + expect(within(card).getByLabelText('card-title')).toHaveTextContent( + 'Test 1' + ); }); - it('displays only the dataset name when there is no generic investigation to link to', () => { + it('displays only the dataset name when there is no generic investigation to link to', async () => { delete cardData[0].investigation; (useInvestigationsPaginated as jest.Mock).mockReturnValue({ data: cardData, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('data'); + renderComponent('data'); + + const card = (await screen.findAllByTestId('card'))[0]; - expect(wrapper.find(CardView).first().find('a')).toHaveLength(2); + expect(within(card).getAllByRole('link')).toHaveLength(2); expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Test 1'); + within(card).getByRole('link', { name: 'Test 1' }) + ).toBeInTheDocument(); }); - it('displays only the dataset name when there is no DLS investigation to link to', () => { + it('displays only the dataset name when there is no DLS investigation to link to', async () => { delete cardData[0].investigation; (useInvestigationsPaginated as jest.Mock).mockReturnValue({ data: cardData, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('dls'); + renderComponent('dls'); - expect(wrapper.find(CardView).first().find('a')).toHaveLength(2); + const card = (await screen.findAllByTestId('card'))[0]; + + expect(within(card).getAllByRole('link')).toHaveLength(2); expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Test 1'); + within(card).getByRole('link', { name: 'Test 1' }) + ).toBeInTheDocument(); }); - it('displays only the dataset name when there is no ISIS investigation to link to', () => { + it('displays only the dataset name when there is no ISIS investigation to link to', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -449,42 +404,37 @@ describe('Investigation - Card View', () => { fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); + + const card = (await screen.findAllByTestId('card'))[0]; - expect(wrapper.find(CardView).first().find('a')).toHaveLength(2); + expect(within(card).getAllByRole('link')).toHaveLength(2); expect( - wrapper - .find(CardView) - .first() - .find('[aria-label="card-title"]') - .last() - .text() - ).toEqual('Test 1'); + within(card).getByRole('link', { name: 'Test 1' }) + ).toBeInTheDocument(); }); - it('displays generic details panel when expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(InvestigationDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(InvestigationDetailsPanel).exists()).toBeTruthy(); + it('displays generic details panel when expanded', async () => { + renderComponent(); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + await screen.findByTestId('investigation-details-panel') + ).toBeTruthy(); }); - it('displays correct details panel for ISIS when expanded', () => { - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeTruthy(); + it('displays correct details panel for ISIS when expanded', async () => { + renderComponent('isis'); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + await screen.findByTestId('isis-investigation-details-panel') + ).toBeTruthy(); }); - it('can navigate using the details panel for ISIS when there are facility cycles', () => { + it('can navigate using the details panel for ISIS when there are facility cycles', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -496,29 +446,30 @@ describe('Investigation - Card View', () => { ], }); - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); + renderComponent('isis'); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect( + await screen.findByTestId('isis-investigation-details-panel') + ).toBeTruthy(); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeTruthy(); + await user.click( + await screen.findByRole('tab', { + name: 'investigations.details.datasets', + }) + ); - wrapper.find('#investigation-datasets-tab').last().simulate('click'); expect(history.location.pathname).toBe( '/browse/instrument/4/facilityCycle/4/investigation/1/dataset' ); }); - it('displays correct details panel for DLS when expanded', () => { - const wrapper = createWrapper('dls'); - expect(wrapper.find(DLSVisitDetailsPanel).exists()).toBeFalsy(); - wrapper - .find('[aria-label="card-more-info-expand"]') - .last() - .simulate('click'); - - expect(wrapper.find(DLSVisitDetailsPanel).exists()).toBeTruthy(); + it('displays correct details panel for DLS when expanded', async () => { + renderComponent('dls'); + await user.click( + await screen.findByRole('button', { name: 'card-more-info-expand' }) + ); + expect(await screen.findByTestId('visit-details-panel')).toBeTruthy(); }); }); diff --git a/packages/datagateway-search/src/search/__snapshots__/advancedHelpDialogue.component.test.tsx.snap b/packages/datagateway-search/src/search/__snapshots__/advancedHelpDialogue.component.test.tsx.snap deleted file mode 100644 index 9f81da43f..000000000 --- a/packages/datagateway-search/src/search/__snapshots__/advancedHelpDialogue.component.test.tsx.snap +++ /dev/null @@ -1,273 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Advanced help dialogue component tests renders correctly 1`] = ` - - See all - - - search options - - . - - - Advanced Search Tips - - - - - - advanced_search_help.description - - - - advanced_search_help.exact_phrase.title - - - - Use quotation marks around a phrase to search for a precise sequence of words e.g. - - - "neutron scattering" - - . - - - - - - advanced_search_help.logic_operators.title - - - - Find all data containing 'neutron' and 'scattering' with ' - - neutron AND scattering - - '. Find all data containing either neutron or scattering with ' - - - neutron OR scattering - - '. Find all data that contains the phrase 'scattering' but exclude those containing 'elastic' with ' - - - scattering NOT elastic - - '. Use brackets around phrases to construct more complicated searches e.g. ' - - - scattering NOT (elastic OR neutron) - - '. - - - - - - advanced_search_help.wildcards.title - - - - Use wildcards to take the place of one or more characters in a phrase. A question mark '?' can be used to search for a phrase with one or more character missing e.g. ' - - - te?t - - ' will return results containing 'test' or 'text'. An asterix '*' can be used to replace zero or more characters e.g. ' - - *ium - - ' will return results containing words like 'sodium' and 'vanadium'. - - - - - - advanced_search_help.limited_search_results.title - - - advanced_search_help.limited_search_results.description {maxNumResults:{tabs:{datasetTab:false,datafileTab:false,investigationTab:false},selectAllSetting:true,settingsLoaded:false,sideLayout:false,searchableEntities:[investigation,dataset,datafile],maxNumResults:300}} - - - - - Further information on searching can be found - - - here - - . - - - - -`; diff --git a/packages/datagateway-search/src/search/__snapshots__/datePicker.component.test.tsx.snap b/packages/datagateway-search/src/search/__snapshots__/datePicker.component.test.tsx.snap index 6a32301a1..1df6eeb4f 100644 --- a/packages/datagateway-search/src/search/__snapshots__/datePicker.component.test.tsx.snap +++ b/packages/datagateway-search/src/search/__snapshots__/datePicker.component.test.tsx.snap @@ -1,18 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DatePicker component tests CustomClearButton renders correctly 1`] = ` - - Clear - + + + `; diff --git a/packages/datagateway-search/src/search/advancedHelpDialogue.component.test.tsx b/packages/datagateway-search/src/search/advancedHelpDialogue.component.test.tsx index 597970f75..a12361758 100644 --- a/packages/datagateway-search/src/search/advancedHelpDialogue.component.test.tsx +++ b/packages/datagateway-search/src/search/advancedHelpDialogue.component.test.tsx @@ -1,13 +1,20 @@ -import React from 'react'; +import * as React from 'react'; import AdvancedHelpDialogue from './advancedHelpDialogue.component'; -import { Provider, useSelector } from 'react-redux'; +import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { dGCommonInitialState } from 'datagateway-common'; import { initialState as dgSearchInitialState } from '../state/reducers/dgsearch.reducer'; import configureStore from 'redux-mock-store'; -import { mount, ReactWrapper, shallow } from 'enzyme'; import { StateType } from '../state/app.types'; import { MemoryRouter } from 'react-router-dom'; +import { + render, + type RenderResult, + screen, + waitFor, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), @@ -17,9 +24,11 @@ jest.mock('react-redux', () => ({ describe('Advanced help dialogue component tests', () => { let mockStore; let state: StateType; + let user: UserEvent; beforeEach(() => { mockStore = configureStore([thunk]); + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ dgcommon: dGCommonInitialState, @@ -28,46 +37,34 @@ describe('Advanced help dialogue component tests', () => { ); }); - const createWrapper = (): ReactWrapper => { - return mount( + const renderComponent = (): RenderResult => + render( ); - }; - it('renders correctly', () => { - useSelector.mockImplementation(() => { - return dgSearchInitialState; - }); + it('can open and close help dialogue', async () => { + renderComponent(); - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); + await user.click( + await screen.findByRole('button', { + name: 'advanced_search_help.search_options_arialabel', + }) + ); + + expect(await screen.findByText('Advanced Search Tips')).toBeInTheDocument(); - it('can open and close help dialogue', () => { - const wrapper = createWrapper(); - wrapper - .find('[aria-label="advanced_search_help.search_options_arialabel"]') - .last() - .simulate('click'); - expect( - wrapper - .find('[aria-labelledby="advanced-search-dialog-title"]') - .first() - .prop('open') - ).toBe(true); - wrapper - .find('[aria-label="advanced_search_help.close_button_arialabel"]') - .last() - .simulate('click'); - expect( - wrapper - .find('[aria-labelledby="advanced-search-dialog-title"]') - .first() - .prop('open') - ).toBe(false); + await user.click( + await screen.findByRole('button', { + name: 'advanced_search_help.close_button_arialabel', + }) + ); + + await waitFor(() => { + expect(screen.queryByText('Advanced Search Tips')).toBeNull(); + }); }); }); diff --git a/packages/datagateway-search/src/search/checkBoxes.component.test.tsx b/packages/datagateway-search/src/search/checkBoxes.component.test.tsx index 3eafd1305..9aa1ee062 100644 --- a/packages/datagateway-search/src/search/checkBoxes.component.test.tsx +++ b/packages/datagateway-search/src/search/checkBoxes.component.test.tsx @@ -1,13 +1,15 @@ -import React from 'react'; +import * as React from 'react'; import { StateType } from '../state/app.types'; import { Provider } from 'react-redux'; -import { mount, ReactWrapper } from 'enzyme'; import configureStore from 'redux-mock-store'; import CheckBoxesGroup from './checkBoxes.component'; import thunk from 'redux-thunk'; import { initialState } from '../state/reducers/dgsearch.reducer'; import { createMemoryHistory, History } from 'history'; import { Router } from 'react-router-dom'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; +import { render, type RenderResult, screen } from '@testing-library/react'; jest.mock('loglevel'); @@ -17,19 +19,20 @@ describe('Checkbox component tests', () => { let testStore; let history: History; let pushSpy; + let user: UserEvent; - const createWrapper = (h: History = history): ReactWrapper => { - return mount( + const renderComponent = (h: History = history): RenderResult => + render( ); - }; beforeEach(() => { history = createMemoryHistory(); + user = userEvent.setup(); pushSpy = jest.spyOn(history, 'push'); state = JSON.parse(JSON.stringify({ dgsearch: initialState })); @@ -58,148 +61,109 @@ describe('Checkbox component tests', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { + it('renders correctly', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.find('#search-entities-menu').last().text()).toEqual( - 'searchBox.checkboxes.types (2)' - ); - - wrapper - .find('#search-entities-menu') - .find('[role="button"]') - .last() - .simulate('mousedown', { button: 0 }); + // open the dropdown + await user.click(await screen.findByRole('button')); - const investigationCheckbox = wrapper.find( - '[aria-label="searchBox.checkboxes.investigation_arialabel"]' - ); - expect(investigationCheckbox.exists()); - investigationCheckbox.find('input').forEach((node) => { - expect(node.props().checked).toEqual(false); + const investigationCheckbox = await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.investigation_arialabel', }); + expect(investigationCheckbox).not.toBeChecked(); - const datasetCheckbox = wrapper.find( - '[aria-label="searchBox.checkboxes.dataset_arialabel"]' - ); - expect(datasetCheckbox.exists()); - datasetCheckbox.find('input').forEach((node) => { - expect(node.props().checked).toEqual(true); + const datasetCheckbox = await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.dataset_arialabel', }); + expect(datasetCheckbox).toBeChecked(); - const datafileCheckbox = wrapper.find( - '[aria-label="searchBox.checkboxes.datafile_arialabel"]' - ); - expect(datafileCheckbox.exists()).toEqual(true); - datafileCheckbox.find('input').forEach((node) => { - expect(node.props().checked).toEqual(true); + const datafileCheckbox = await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.datafile_arialabel', }); + expect(datafileCheckbox).toBeChecked(); }); - it('renders correctly when datafiles are not searchable', () => { + it('renders correctly when datafiles are not searchable', async () => { state.dgsearch.searchableEntities = ['investigation', 'dataset']; history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - - expect(wrapper.find('#search-entities-menu').last().text()).toEqual( - 'searchBox.checkboxes.types (1)' - ); + renderComponent(); - wrapper - .find('#search-entities-menu') - .find('[role="button"]') - .last() - .simulate('mousedown', { button: 0 }); + // open the dropdown + await user.click(await screen.findByRole('button')); - const investigationCheckbox = wrapper.find( - '[aria-label="searchBox.checkboxes.investigation_arialabel"]' - ); - expect(investigationCheckbox.exists()); - investigationCheckbox.find('input').forEach((node) => { - expect(node.props().checked).toEqual(false); + const investigationCheckbox = await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.investigation_arialabel', }); + expect(investigationCheckbox).not.toBeChecked(); - const datasetCheckbox = wrapper.find( - '[aria-label="searchBox.checkboxes.dataset_arialabel"]' - ); - expect(datasetCheckbox.exists()); - datasetCheckbox.find('input').forEach((node) => { - expect(node.props().checked).toEqual(true); + const datasetCheckbox = await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.dataset_arialabel', }); + expect(datasetCheckbox).toBeChecked(); expect( - wrapper - .find('[aria-label="searchBox.checkboxes.datafile_arialabel"]') - .exists() - ).toEqual(false); + screen.queryByRole('checkbox', { + name: 'searchBox.checkboxes.datafile_arialabel', + }) + ).toBeNull(); }); - it('renders an error message when nothing is selected', () => { + it('renders an error message when nothing is selected', async () => { history.replace( '/?searchText=&investigation=false&dataset=false&datafile=false' ); - const wrapper = createWrapper(); + renderComponent(); expect( - wrapper.find('#search-entities-checkbox-label').first().text() - ).toContain('searchBox.checkboxes.types'); - - expect(wrapper.find('.MuiFormHelperText-root').last().text()).toEqual( - 'searchBox.checkboxes.types_error' - ); + await screen.findByText('searchBox.checkboxes.types_error') + ).toBeInTheDocument(); }); - it('pushes URL with new dataset value when user clicks checkbox', () => { + it('pushes URL with new dataset value when user clicks checkbox', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); + renderComponent(); - wrapper - .find('#search-entities-menu') - .find('[role="button"]') - .last() - .simulate('mousedown', { button: 0 }); + // open the dropdown + await user.click(await screen.findByRole('button')); - wrapper - .find('[aria-label="searchBox.checkboxes.dataset_arialabel"]') - .last() - .simulate('click'); + await user.click( + await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.dataset_arialabel', + }) + ); expect(pushSpy).toHaveBeenCalledWith('?dataset=false&investigation=false'); }); - it('pushes URL with new datafile value when user clicks checkbox', () => { + it('pushes URL with new datafile value when user clicks checkbox', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); + renderComponent(); - wrapper - .find('#search-entities-menu') - .find('[role="button"]') - .last() - .simulate('mousedown', { button: 0 }); + // open the dropdown + await user.click(await screen.findByRole('button')); - wrapper - .find('[aria-label="searchBox.checkboxes.datafile_arialabel"]') - .last() - .simulate('click'); + await user.click( + await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.datafile_arialabel', + }) + ); expect(pushSpy).toHaveBeenCalledWith('?datafile=false&investigation=false'); }); - it('pushes URL with new investigation value when user clicks checkbox', () => { + it('pushes URL with new investigation value when user clicks checkbox', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - - wrapper - .find('#search-entities-menu') - .find('[role="button"]') - .last() - .simulate('mousedown', { button: 0 }); - - wrapper - .find('[aria-label="searchBox.checkboxes.investigation_arialabel"]') - .last() - .simulate('click'); + renderComponent(); + + // open the dropdown + await user.click(await screen.findByRole('button')); + + await user.click( + await screen.findByRole('checkbox', { + name: 'searchBox.checkboxes.investigation_arialabel', + }) + ); expect(pushSpy).toHaveBeenCalledWith('?'); }); diff --git a/packages/datagateway-search/src/search/datePicker.component.test.tsx b/packages/datagateway-search/src/search/datePicker.component.test.tsx index 3766a2071..380bbe6fb 100644 --- a/packages/datagateway-search/src/search/datePicker.component.test.tsx +++ b/packages/datagateway-search/src/search/datePicker.component.test.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { StateType } from '../state/app.types'; import { Provider } from 'react-redux'; -import { mount, ReactWrapper, shallow } from 'enzyme'; import configureStore from 'redux-mock-store'; import SelectDates, { CustomClearButton } from './datePicker.component'; import thunk from 'redux-thunk'; @@ -13,6 +12,9 @@ import { cleanupDatePickerWorkaround, } from '../setupTests'; import { PickersActionBarProps } from '@mui/x-date-pickers/PickersActionBar'; +import { render, type RenderResult, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; jest.mock('loglevel'); @@ -25,15 +27,14 @@ describe('DatePicker component tests', () => { const testInitiateSearch = jest.fn(); - const createWrapper = (h: History = history): ReactWrapper => { - return mount( + const renderComponent = (h: History = history): RenderResult => + render( ); - }; beforeEach(() => { applyDatePickerWorkaround(); @@ -68,23 +69,23 @@ describe('DatePicker component tests', () => { cleanupDatePickerWorkaround(); }); - it('renders correctly', () => { + it('renders correctly', async () => { history.replace( '/?searchText=&investigation=false&startDate=2021-10-26&endDate=2021-10-28' ); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - expect(startDateInput.exists()); - expect(startDateInput.instance().value).toEqual('2021-10-26'); - - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - expect(endDateInput.exists()); - expect(endDateInput.instance().value).toEqual('2021-10-28'); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + expect(startDateInput).toBeInTheDocument(); + expect(startDateInput).toHaveValue('2021-10-26'); + + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + expect(endDateInput).toBeInTheDocument(); + expect(endDateInput).toHaveValue('2021-10-28'); }); describe('CustomClearButton', () => { @@ -98,247 +99,287 @@ describe('DatePicker component tests', () => { }); it('renders correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); }); - it('calls onClear when button clicked', () => { - const wrapper = shallow(); - const button = wrapper.find('[role="button"]'); - button.simulate('click'); + it('calls onClear when button clicked', async () => { + const user = userEvent.setup(); + render(); + await user.click(await screen.findByRole('button')); expect(onClear).toHaveBeenCalled(); }); }); describe('Start date box', () => { - it('pushes URL with new start date value when user types number into Start Date input', () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + + it('pushes URL with new start date value when user types number into Start Date input', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.instance().value = '2012 01 01'; - startDateInput.simulate('change'); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + + await user.type(startDateInput, '2012 01 01'); expect(pushSpy).toHaveBeenCalledWith('?startDate=2012-01-01'); }); - it('initiates search with valid start and end dates', () => { + it('initiates search with valid start and end dates', async () => { history.replace( '/?searchText=&investigation=false&startDate=2012-01-01&endDate=2013-01-01' ); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.simulate('keydown', { key: 'Enter' }); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + + await user.type(startDateInput, '{enter}'); + expect(testInitiateSearch).toHaveBeenCalled(); }); - it('initiates search with valid start date and empty end date', () => { + it('initiates search with valid start date and empty end date', async () => { history.replace('/?searchText=&investigation=false&startDate=2012-01-01'); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.simulate('keydown', { key: 'Enter' }); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + + await user.type(startDateInput, '{enter}'); + expect(testInitiateSearch).toHaveBeenCalled(); }); - it('initiates search with valid end date and empty start date', () => { + it('initiates search with valid end date and empty start date', async () => { history.replace('/?searchText=&investigation=false&endDate=2012-01-01'); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.simulate('keydown', { key: 'Enter' }); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + + await user.type(startDateInput, '{enter}'); + expect(testInitiateSearch).toHaveBeenCalled(); }); - it('initiates search with empty start and end dates', () => { + it('initiates search with empty start and end dates', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + + await user.type(startDateInput, '{enter}'); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.simulate('keydown', { key: 'Enter' }); expect(testInitiateSearch).toHaveBeenCalled(); }); - it('displays error message when an invalid date is entered', () => { + it('displays error message when an invalid date is entered', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.instance().value = '2012 01 35'; - startDateInput.simulate('change'); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); - expect(wrapper.find('.MuiFormHelperText-filled').first().text()).toEqual( - 'searchBox.invalid_date_message' - ); + await user.type(startDateInput, '2012 01 35'); + + expect( + await screen.findByText('searchBox.invalid_date_message') + ).toBeInTheDocument(); }); - it('displays error message when a date after the maximum date is entered', () => { + it('displays error message when a date after the maximum date is entered', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.instance().value = '3000 01 01'; - startDateInput.simulate('change'); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); - expect(wrapper.find('.MuiFormHelperText-filled').first().text()).toEqual( - 'searchBox.invalid_date_message' - ); + await user.type(startDateInput, '3000 01 01'); + + expect( + await screen.findByText('searchBox.invalid_date_message') + ).toBeInTheDocument(); }); - it('displays error message when a date after the end date is entered', () => { + it('displays error message when a date after the end date is entered', async () => { history.replace('/?searchText=&investigation=false&endDate=2011-11-21'); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - startDateInput.instance().value = '2012 01 01'; - startDateInput.simulate('change'); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + + await user.type(startDateInput, '2012 01 01'); - expect(wrapper.find('.MuiFormHelperText-filled').first().text()).toEqual( + const errorMessages = await screen.findAllByText( 'searchBox.invalid_date_range_message' ); + expect(errorMessages).toHaveLength(2); + + for (const msg of errorMessages) { + expect(msg).toBeInTheDocument(); + } }); - it('invalid date in URL is ignored', () => { + it('invalid date in URL is ignored', async () => { history.replace('/?searchText=&investigation=false&startDate=2011-14-21'); - const wrapper = createWrapper(); - const startDateInput = wrapper - .find('[aria-label="searchBox.start_date_arialabel"]') - .last(); - expect(startDateInput.instance().value).toEqual(''); + renderComponent(); + const startDateInput = await screen.findByRole('textbox', { + name: 'searchBox.start_date_arialabel', + }); + + expect(startDateInput).toHaveValue(''); }); }); describe('End date box', () => { - it('pushes URL with new end date value when user types number into Start Date input', () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + + it('pushes URL with new end date value when user types number into Start Date input', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.instance().value = '2000 01 01'; - endDateInput.simulate('change'); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + + await user.type(endDateInput, '2000 01 01'); + expect(pushSpy).toHaveBeenCalledWith('?endDate=2000-01-01'); }); - it('initiates search with valid start and end dates', () => { + it('initiates search with valid start and end dates', async () => { history.replace( '/?searchText=&investigation=false&startDate=2012-01-01&endDate=2013-01-01' ); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.simulate('keydown', { key: 'Enter' }); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + + await user.type(endDateInput, '{enter}'); + expect(testInitiateSearch).toHaveBeenCalled(); }); - it('initiates search with valid start date and empty end date', () => { + it('initiates search with valid start date and empty end date', async () => { history.replace('/?searchText=&investigation=false&startDate=2012-01-01'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.simulate('keydown', { key: 'Enter' }); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + + await user.type(endDateInput, '{enter}'); + expect(testInitiateSearch).toHaveBeenCalled(); }); - it('initiates search with valid end date and empty start date', () => { + it('initiates search with valid end date and empty start date', async () => { history.replace('/?searchText=&investigation=false&endDate=2012-01-01'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.simulate('keydown', { key: 'Enter' }); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + + await user.type(endDateInput, '{enter}'); + expect(testInitiateSearch).toHaveBeenCalled(); }); - it('initiates search with empty start and end dates', () => { + it('initiates search with empty start and end dates', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.simulate('keydown', { key: 'Enter' }); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + + await user.type(endDateInput, '{enter}'); + expect(testInitiateSearch).toHaveBeenCalled(); }); - it('displays error message when an invalid date is entered', () => { + it('displays error message when an invalid date is entered', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.instance().value = '2012 01 35'; - endDateInput.simulate('change'); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); - expect(wrapper.find('.MuiFormHelperText-filled').last().text()).toEqual( - 'searchBox.invalid_date_message' - ); + await user.type(endDateInput, '2012 01 35'); + + expect( + await screen.findByText('searchBox.invalid_date_message') + ).toBeInTheDocument(); }); - it('displays error message when a date before the minimum date is entered', () => { + it('displays error message when a date before the minimum date is entered', async () => { history.replace('/?searchText=&investigation=false'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.instance().value = '1203 01 01'; - endDateInput.simulate('change'); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); - expect(wrapper.find('.MuiFormHelperText-filled').last().text()).toEqual( - 'searchBox.invalid_date_message' - ); + await user.type(endDateInput, '1203 01 01'); + + expect( + await screen.findByText('searchBox.invalid_date_message') + ).toBeInTheDocument(); }); - it('displays error message when a date before the start date is entered', () => { + it('displays error message when a date before the start date is entered', async () => { history.replace('/?searchText=&investigation=false&startDate=2011-11-21'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - endDateInput.instance().value = '2010 01 01'; - endDateInput.simulate('change'); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + + await user.type(endDateInput, '2010 01 01'); - expect(wrapper.find('.MuiFormHelperText-filled').last().text()).toEqual( + const errorMessages = await screen.findAllByText( 'searchBox.invalid_date_range_message' ); + expect(errorMessages).toHaveLength(2); + + for (const msg of errorMessages) { + expect(msg).toBeInTheDocument(); + } }); - it('invalid date in URL is ignored', () => { + it('invalid date in URL is ignored', async () => { history.replace('/?searchText=&investigation=false&endDate=2011-14-21'); - const wrapper = createWrapper(); - const endDateInput = wrapper - .find('[aria-label="searchBox.end_date_arialabel"]') - .last(); - expect(endDateInput.instance().value).toEqual(''); + renderComponent(); + const endDateInput = await screen.findByRole('textbox', { + name: 'searchBox.end_date_arialabel', + }); + + expect(endDateInput).toHaveValue(''); }); }); }); diff --git a/packages/datagateway-search/src/searchBoxContainer.component.test.tsx b/packages/datagateway-search/src/searchBoxContainer.component.test.tsx index 9cf6e9f78..37d306311 100644 --- a/packages/datagateway-search/src/searchBoxContainer.component.test.tsx +++ b/packages/datagateway-search/src/searchBoxContainer.component.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; -import { ShallowWrapper, shallow } from 'enzyme'; +import * as React from 'react'; import SearchBoxContainer from './searchBoxContainer.component'; import SearchBoxContainerSide from './searchBoxContainerSide.component'; -import { useSelector } from 'react-redux'; +import { Provider, useSelector } from 'react-redux'; import { initialState } from './state/reducers/dgsearch.reducer'; +import { render, type RenderResult, screen } from '@testing-library/react'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { MemoryRouter } from 'react-router-dom'; jest.mock('loglevel'); @@ -14,15 +17,22 @@ jest.mock('react-redux', () => ({ })); describe('SearchBoxContainer - Tests', () => { - const createWrapper = (): ShallowWrapper => { - return shallow( - + const renderComponent = (): RenderResult => + render( + + + + + ); - }; beforeEach(() => { useSelector.mockImplementation(() => { @@ -31,24 +41,48 @@ describe('SearchBoxContainer - Tests', () => { }); it('renders searchBoxContainer correctly', () => { - const wrapper = createWrapper(); - expect(wrapper).toMatchSnapshot(); + renderComponent(); + + // check that search box is shown + expect( + screen.getByRole('searchbox', { name: 'searchBox.search_text_arialabel' }) + ).toHaveValue('initial search text'); + + // check that links are shown correctly + expect( + screen.getByRole('link', { name: '"instrument calibration"' }) + ).toHaveAttribute('href', '/searchBox.examples_label_link1'); + expect( + screen.getByRole('link', { name: 'neutron AND scattering' }) + ).toHaveAttribute('href', '/searchBox.examples_label_link2'); + + // check that limited results message is shown + expect( + screen.getByText('searchBox.limited_results_message', { exact: false }) + ).toBeInTheDocument(); }); }); describe('SearchBoxContainerSide - Tests', () => { - const createWrapper = (): ShallowWrapper => { - return shallow( - + const renderComponent = (): RenderResult => + render( + + + + + ); - }; it('renders searchBoxContainerSide correctly', () => { - const wrapper = createWrapper(); - expect(wrapper).toMatchSnapshot(); + const { asFragment } = renderComponent(); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/datagateway-search/src/searchBoxContainer.component.tsx b/packages/datagateway-search/src/searchBoxContainer.component.tsx index b540a7c69..790f27185 100644 --- a/packages/datagateway-search/src/searchBoxContainer.component.tsx +++ b/packages/datagateway-search/src/searchBoxContainer.component.tsx @@ -37,7 +37,7 @@ const SearchBoxContainer = ( ); return ( - + { let state: StateType; let queryClient: QueryClient; let history: History; - let pushSpy; + let holder: HTMLElement; + let cartItems: DownloadCartItem[]; const localStorageGetItemMock = jest.spyOn( window.localStorage.__proto__, 'getItem' ); - const createWrapper = ( - h: History = history, - client: QueryClient = queryClient - ): ReactWrapper => { - const mockStore = configureStore([thunk]); - return mount( - - - + function renderComponent(): RenderResult { + return render( + + + ); - }; + } beforeEach(() => { + cartItems = []; queryClient = new QueryClient(); history = createMemoryHistory({ initialEntries: ['/search/data'], }); - pushSpy = jest.spyOn(history, 'push'); + delete window.location; + window.location = new URL(`http://localhost/search/data`); + + // below code keeps window.location in sync with history changes + // (needed because useUpdateQueryParam uses window.location not history) + const historyReplace = history.replace; + const historyReplaceSpy = jest.spyOn(history, 'replace'); + historyReplaceSpy.mockImplementation((args) => { + historyReplace(args); + if (typeof args === 'string') { + window.location = new URL(`http://localhost${args}`); + } else { + window.location = new URL(`http://localhost${createPath(args)}`); + } + }); + const historyPush = history.push; + const historyPushSpy = jest.spyOn(history, 'push'); + historyPushSpy.mockImplementation((args) => { + historyPush(args); + if (typeof args === 'string') { + window.location = new URL(`http://localhost${args}`); + } else { + window.location = new URL(`http://localhost${createPath(args)}`); + } + }); window.localStorage.__proto__.removeItem = jest.fn(); window.localStorage.__proto__.setItem = jest.fn(); @@ -120,38 +132,53 @@ describe('SearchPageContainer - Tests', () => { }, }; + holder = document.createElement('div'); + holder.setAttribute('id', 'datagateway-search'); + document.body.appendChild(holder); + (axios.get as jest.Mock).mockImplementation((url) => { + if (url.includes('/user/cart')) { + return Promise.resolve({ data: { cartItems } }); + } + if (url.includes('count')) { return Promise.resolve({ data: 0 }); - } else { - return Promise.resolve({ data: [] }); } + + return Promise.resolve({ data: [] }); }); }); afterEach(() => { + document.body.removeChild(holder); jest.clearAllMocks(); }); it('renders searchPageContainer correctly', () => { - const mockStore = configureStore([thunk]); - const wrapper = render( - - - - - - - - ); + history.replace({ key: 'testKey', pathname: '/' }); + + renderComponent(); - expect(wrapper.asFragment()).toMatchSnapshot(); + expect(screen.getByRole('link', { name: 'Search data' })).toHaveAttribute( + 'href', + '/search/data' + ); }); - it('renders correctly at /search/data route', () => { - const wrapper = createWrapper(); + it('renders initial layout at /search/data route', () => { + renderComponent(); - expect(wrapper.exists('SearchBoxContainer')).toBeTruthy(); + expect(screen.getByTestId('search-box-container')).toBeInTheDocument(); + // no search results yet, so view button, clear filter button and tabs should be hidden + expect( + screen.queryByRole('button', { name: 'page view app.view_cards' }) + ).toBeNull(); + expect( + screen.queryByRole('button', { name: 'app.clear_filters' }) + ).toBeNull(); + expect( + screen.queryByRole('tablist', { name: 'searchPageTable.tabs_arialabel' }) + ).toBeNull(); }); it('renders side layout correctly', () => { @@ -165,350 +192,69 @@ describe('SearchPageContainer - Tests', () => { }) ); - const wrapper = createWrapper(); + renderComponent(); - expect(wrapper.exists('SearchBoxContainerSide')).toBeTruthy(); + expect(screen.getByTestId('search-box-container-side')).toBeInTheDocument(); + // no search results yet, so view button, clear filter button and tabs should be hidden + expect( + screen.queryByRole('button', { name: 'page view app.view_cards' }) + ).toBeNull(); + expect( + screen.queryByRole('button', { name: 'app.clear_filters' }) + ).toBeNull(); + expect( + screen.queryByRole('tablist', { name: 'searchPageTable.tabs_arialabel' }) + ).toBeNull(); }); - it('display search table container when search request sent', async () => { - const wrapper = createWrapper(); + it('display search results when search request sent', async () => { + const user = userEvent.setup(); - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); + renderComponent(); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); + await user.click( + screen.getByRole('button', { name: 'searchBox.search_button_arialabel' }) + ); - expect(wrapper.exists('#container-search-table')).toBeTruthy(); - expect(wrapper.exists(LinearProgress)).toBeFalsy(); + expect( + await screen.findByRole('tablist', { + name: 'searchPageTable.tabs_arialabel', + }) + ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'page view app.view_cards' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'app.clear_filters' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.investigation 0' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.dataset 0' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.datafile 0' }) + ).toBeInTheDocument(); }); it('display loading bar when loading true', async () => { + const user = userEvent.setup(); (axios.get as jest.Mock).mockImplementation( () => - new Promise((resolve, reject) => { + new Promise((_) => { // do nothing, simulating pending promise // to test loading state }) ); - const wrapper = createWrapper(); - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - - expect(wrapper.exists(LinearProgress)).toBeTruthy(); - }); - - it('builds correct parameters for datafile request if date and search text properties are in use', () => { - history.replace( - '/search/data?searchText=hello&startDate=2013-11-11&endDate=2016-11-11' - ); - - const wrapper = createWrapper(); + renderComponent(); - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Datafile', - lower: '201311110000', - text: 'hello', - upper: '201611112359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for dataset request if date and search text properties are in use', () => { - history.replace( - '/search/data?searchText=hello&datafile=false&investigation=false&startDate=2013-11-11&endDate=2016-11-11' + await user.click( + screen.getByRole('button', { name: 'searchBox.search_button_arialabel' }) ); - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Dataset', - lower: '201311110000', - text: 'hello', - upper: '201611112359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for investigation request if date and search text properties are in use', () => { - history.replace( - '/search/data?searchText=hello&dataset=false&datafile=false&startDate=2013-11-11&endDate=2016-11-11' - ); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Investigation', - lower: '201311110000', - text: 'hello', - upper: '201611112359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for datafile request if only start date is in use', () => { - history.replace( - '/search/data?dataset=false&investigation=false&startDate=2013-11-11' - ); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Datafile', - lower: '201311110000', - upper: '9000012312359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for dataset request if only start date is in use', () => { - history.replace( - '/search/data?datafile=false&investigation=false&startDate=2013-11-11' - ); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Dataset', - lower: '201311110000', - upper: '9000012312359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for investigation request if only start date is in use', () => { - history.replace( - '/search/data?dataset=false&datafile=false&startDate=2013-11-11' - ); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Investigation', - lower: '201311110000', - upper: '9000012312359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for datafile request if only end date is in use', () => { - history.replace( - '/search/data?dataset=false&investigation=false&endDate=2016-11-11' - ); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Datafile', - lower: '0000001010000', - upper: '201611112359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for dataset request if only end date is in use', () => { - history.replace( - '/search/data?datafile=false&investigation=false&endDate=2016-11-11' - ); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Dataset', - lower: '0000001010000', - upper: '201611112359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for investigation request if only end date is in use', () => { - history.replace( - '/search/data?dataset=false&datafile=false&endDate=2016-11-11' - ); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Investigation', - lower: '0000001010000', - upper: '201611112359', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for datafile request if date and search text properties are not in use', () => { - history.replace('/search/data?dataset=false&investigation=false'); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Datafile', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for dataset request if date and search text properties are not in use', () => { - history.replace('/search/data?datafile=false&investigation=false'); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Dataset', - }, - sessionId: null, - }, - } - ); - }); - - it('builds correct parameters for investigation request if date and search text properties are not in use', () => { - history.replace('/search/data?dataset=false&datafile=false'); - - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Investigation', - }, - sessionId: null, - }, - } - ); + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); }); it('gets the filters stored in the local storage', () => { @@ -532,50 +278,34 @@ describe('SearchPageContainer - Tests', () => { }); }); - it('display clear filters button and clear for filters onClick', async () => { - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - - await act(async () => { - await flushPromises(); - wrapper.update(); - }); + it('clears filters when clear filters button is clicked', async () => { + const user = userEvent.setup(); history.replace( - `/search/data?filters=%7B"title"%3A%7B"value"%3A"spend"%2C"type"%3A"include"%7D%7D` + '/search/data?searchText=test&filters=%7B"title"%3A%7B"value"%3A"spend"%2C"type"%3A"include"%7D%7D' ); - wrapper.update(); - - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(false); - - wrapper - .find('[data-testid="clear-filters-button"]') - .last() - .simulate('click'); + renderComponent(); - wrapper.update(); + await user.click( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ); - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(true); - expect(history.location.search).toEqual('?'); + expect(history.location.search).toEqual('?searchText=test'); }); - it('display disabled clear filters button', async () => { - const wrapper = createWrapper(); + it('disables clear filters button when there is no filter applied', async () => { + const user = userEvent.setup(); - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); + renderComponent(); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); + await user.click( + screen.getByRole('button', { name: 'searchBox.search_button_arialabel' }) + ); - expect(wrapper.find(ClearFiltersButton).prop('disabled')).toEqual(true); + expect( + screen.getByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); }); it('gets the page stored in the local storage', () => { @@ -623,95 +353,63 @@ describe('SearchPageContainer - Tests', () => { expect(localStorage.setItem).toBeCalledWith('investigationResults', '20'); }); - it('sends actions to update tabs when user clicks search button', async () => { + it('should hide tabs when the corresponding search type is disabled', async () => { + const user = userEvent.setup(); + history.replace( '/search/data?searchText=test&dataset=false&datafile=false' ); - const mockStore = configureStore([thunk]); - const testStore = mockStore(state); - const wrapper = mount( - - - - - - - - ); - wrapper.update(); + renderComponent(); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); + await user.click( + screen.getByRole('button', { name: 'searchBox.search_button_arialabel' }) + ); - expect(testStore.getActions()[0]).toEqual(setDatafileTab(false)); - expect(testStore.getActions()[1]).toEqual(setDatasetTab(false)); - expect(testStore.getActions()[2]).toEqual(setInvestigationTab(true)); + expect( + screen.getByRole('tab', { name: 'tabs.investigation 0' }) + ).toBeInTheDocument(); + expect(screen.queryByRole('tab', { name: 'tabs.dataset' })).toBeNull(); + expect(screen.queryByRole('tab', { name: 'tabs.datafile' })).toBeNull(); }); it('search text state is updated when text is changed and pushes when search initiated', async () => { - const wrapper = createWrapper(); + const user = userEvent.setup(); - wrapper - .find('[aria-label="searchBox.search_text_arialabel"] input') - .simulate('change', { target: { value: 'test' } }); + renderComponent(); - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - - await act(async () => { - await flushPromises(); - wrapper.update(); - }); + await user.type( + screen.getByRole('searchbox', { + name: 'searchBox.search_text_arialabel', + }), + 'test' + ); + await user.click( + screen.getByRole('button', { name: 'searchBox.search_button_arialabel' }) + ); - expect(pushSpy).toHaveBeenCalledWith('?searchText=test'); + expect(history.location.search).toEqual('?searchText=test'); }); it('shows SelectionAlert banner when item selected', async () => { - (useCart as jest.Mock).mockReturnValue({ - data: [ - { - entityId: 1, - entityType: 'dataset', - id: 1, - name: 'Test 1', - parentEntities: [], - }, - ], - }); - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); - - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - - expect(wrapper.exists('[aria-label="selection-alert"]')).toBeTruthy(); - }); - - it('does not show SelectionAlert banner when no items are selected', async () => { - (useCart as jest.Mock).mockReturnValue({ - data: [], - }); - const wrapper = createWrapper(); + cartItems = [ + { + entityId: 1, + entityType: 'dataset', + id: 1, + name: 'Test 1', + parentEntities: [], + }, + ]; + const user = userEvent.setup(); - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); + renderComponent(); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); + await user.click( + screen.getByRole('button', { name: 'searchBox.search_button_arialabel' }) + ); - expect(wrapper.exists('[aria-label="selection-alert"]')).toBeFalsy(); + expect(await screen.findByLabelText('selection-alert')).toBeInTheDocument(); }); it('initiates search when visiting a direct url', async () => { @@ -719,44 +417,59 @@ describe('SearchPageContainer - Tests', () => { '/search/data?searchText=hello&startDate=2013-11-11&endDate=2016-11-11' ); - const wrapper = createWrapper(); - wrapper.update(); + renderComponent(); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Datafile', - lower: '201311110000', - text: 'hello', - upper: '201611112359', - }, - sessionId: null, - }, - } - ); + expect( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); + expect( + screen.getByRole('button', { name: 'page view app.view_cards' }) + ).toBeInTheDocument(); + + expect( + screen.getByRole('searchbox', { + name: 'searchBox.search_text_arialabel', + }) + ).toHaveValue('hello'); + + expect( + screen.getByRole('tab', { name: 'tabs.investigation 0' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.dataset 0' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.datafile 0' }) + ).toBeInTheDocument(); }); it('initiates search when visiting a direct url with empty search text', async () => { history.replace('/search/data?searchText='); - const wrapper = createWrapper(); - wrapper.update(); + renderComponent(); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Datafile', - }, - sessionId: null, - }, - } - ); + expect( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); + expect( + screen.getByRole('button', { name: 'page view app.view_cards' }) + ).toBeInTheDocument(); + + expect( + screen.getByRole('searchbox', { + name: 'searchBox.search_text_arialabel', + }) + ).toHaveValue(''); + + expect( + screen.getByRole('tab', { name: 'tabs.investigation 0' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.dataset 0' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.datafile 0' }) + ).toBeInTheDocument(); }); it('does not search for non-searchable entities when visiting a direct url', async () => { @@ -764,8 +477,7 @@ describe('SearchPageContainer - Tests', () => { history.replace('/search/data?searchText=hello&datafiles=true'); - const wrapper = createWrapper(); - wrapper.update(); + renderComponent(); expect(axios.get).toHaveBeenCalledWith( 'https://example.com/icat/lucene/data', @@ -811,66 +523,52 @@ describe('SearchPageContainer - Tests', () => { }); it('initiates search when the URL is changed', async () => { - const wrapper = createWrapper(); - wrapper.update(); - (axios.get as jest.Mock).mockClear(); + renderComponent(); history.push('?searchText=neutron+AND+scattering'); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - expect(axios.get).toHaveBeenCalledWith( - 'https://example.com/icat/lucene/data', - { - params: { - maxCount: 300, - query: { - target: 'Investigation', - text: 'neutron AND scattering', - }, - sessionId: null, - }, - } - ); - }); - - it('switches view button display name when clicked', async () => { - const wrapper = createWrapper(); - - wrapper - .find('button[aria-label="searchBox.search_button_arialabel"]') - .simulate('click'); + expect( + await screen.findByRole('button', { name: 'app.clear_filters' }) + ).toBeDisabled(); + expect( + screen.getByRole('button', { name: 'page view app.view_cards' }) + ).toBeInTheDocument(); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); + expect( + screen.getByRole('searchbox', { + name: 'searchBox.search_text_arialabel', + }) + ).toHaveValue('neutron AND scattering'); expect( - wrapper.find('[aria-label="page view app.view_cards"]').exists() - ).toBeTruthy(); + screen.getByRole('tab', { name: 'tabs.investigation 0' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('tab', { name: 'tabs.dataset 0' }) + ).toBeInTheDocument(); expect( - wrapper.find('[aria-label="page view app.view_cards"]').first().text() - ).toEqual('app.view_cards'); + screen.getByRole('tab', { name: 'tabs.datafile 0' }) + ).toBeInTheDocument(); + }); + + it('switches view button display name when clicked', async () => { + const user = userEvent.setup(); - // Click view button - wrapper - .find('[aria-label="page view app.view_cards"]') - .last() - .simulate('click'); - wrapper.update(); + renderComponent(); + + await user.click( + screen.getByRole('button', { name: 'searchBox.search_button_arialabel' }) + ); - await act(async () => { - await flushPromises(); - wrapper.update(); + const viewCardsButton = await screen.findByRole('button', { + name: 'page view app.view_cards', }); + expect(viewCardsButton).toBeInTheDocument(); + await user.click(viewCardsButton); - // Check that the text on the button has changed expect( - wrapper.find('[aria-label="page view app.view_table"]').first().text() - ).toEqual('app.view_table'); + await screen.findByRole('button', { name: 'page view app.view_table' }) + ).toBeInTheDocument(); }); it('defaults to dataset when investigation is false ', async () => { @@ -883,9 +581,8 @@ describe('SearchPageContainer - Tests', () => { }, }; - const wrapper = createWrapper(); + renderComponent(); - wrapper.update(); expect(history.location.search).toEqual('?currentTab=dataset'); }); @@ -899,9 +596,8 @@ describe('SearchPageContainer - Tests', () => { }, }; - const wrapper = createWrapper(); + renderComponent(); - wrapper.update(); expect(history.location.search).toEqual('?currentTab=datafile'); }); @@ -915,9 +611,8 @@ describe('SearchPageContainer - Tests', () => { }, }; - const wrapper = createWrapper(); + renderComponent(); - wrapper.update(); // '' i.e default value is investigation it set in the searchPageContainer expect(history.location.search).toEqual(''); }); diff --git a/packages/datagateway-search/src/searchPageContainer.component.tsx b/packages/datagateway-search/src/searchPageContainer.component.tsx index 8cd07032a..214945cd2 100644 --- a/packages/datagateway-search/src/searchPageContainer.component.tsx +++ b/packages/datagateway-search/src/searchPageContainer.component.tsx @@ -149,15 +149,11 @@ const getToggle = (pathname: string, view: ViewsType): boolean => { return getPathMatch(pathname) ? view ? view === 'card' - ? true - : false : getView() === 'card' - ? true - : false : false; }; -const TopSearchBoxPaper = styled(Paper)(({ theme }) => ({ +const TopSearchBoxPaper = styled(Paper)({ height: '100%', // make width of box bigger on smaller screens to prevent overflow // decreasing the space for the search results @@ -166,17 +162,17 @@ const TopSearchBoxPaper = styled(Paper)(({ theme }) => ({ width: '98%', }, margin: '0 auto', -})); +}); -const SideSearchBoxPaper = styled(Paper)(({ theme }) => ({ +const SideSearchBoxPaper = styled(Paper)({ height: '100%', width: '100%', -})); +}); const DataViewPaper = styled(Paper, { shouldForwardProp: (prop) => prop !== 'view' && prop !== 'containerHeight', })<{ view: ViewsType; containerHeight: string }>( - ({ theme, view, containerHeight }) => ({ + ({ view, containerHeight }) => ({ // Only use height for the paper component if the view is table. // also ensure we account for the potential horizontal scrollbar height: view !== 'card' ? containerHeight : 'auto', @@ -465,7 +461,7 @@ const SearchPageContainer: React.FC = ( const username = readSciGatewayToken().username; const loggedInAnonymously = username === null || username === 'anon/anon'; - const disabled = Object.keys(queryParams.filters).length !== 0 ? false : true; + const disabled = Object.keys(queryParams.filters).length === 0; const pushFilters = useUpdateQueryParam('filters', 'push'); diff --git a/packages/datagateway-search/src/setupTests.ts b/packages/datagateway-search/src/setupTests.ts index 444d01f6a..f39931b30 100644 --- a/packages/datagateway-search/src/setupTests.ts +++ b/packages/datagateway-search/src/setupTests.ts @@ -1,10 +1,12 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +import '@testing-library/jest-dom'; import Enzyme from 'enzyme'; import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; import { Action } from 'redux'; import { StateType } from './state/app.types'; import { dGCommonInitialState } from 'datagateway-common'; import { initialState as dgSearchInitialState } from './state/reducers/dgsearch.reducer'; +import { screen, within } from '@testing-library/react'; jest.setTimeout(15000); @@ -73,3 +75,65 @@ export const applyDatePickerWorkaround = (): void => { export const cleanupDatePickerWorkaround = (): void => { delete window.matchMedia; }; + +/** + * Finds the index of the column with the given name. + */ +export const findColumnIndexByName = async ( + columnName: string +): Promise => { + const columnHeaders = await screen.findAllByRole('columnheader'); + return columnHeaders.findIndex( + (header) => within(header).queryByText(columnName) !== null + ); +}; + +/** + * Find the header row of the table currently rendered. + * This assumes that the first row is always the header row. + */ +export const findColumnHeaderByName = async ( + name: string +): Promise => { + const columnHeaders = await screen.findAllByRole('columnheader'); + const header = columnHeaders.find( + (header) => within(header).queryByText(name) !== null + ); + if (!header) { + throw new Error(`Cannot find column header by name: ${name}`); + } + return header; +}; + +/** + * Finds all table rows except the header row. + */ +export const findAllRows = async (): Promise => + (await screen.findAllByRole('row')).slice(1); + +/** + * Find the table row at the given index. This assumes the first table row is always the header row. + * + * @param index The index of the table row, igoring the header row. For example, if the table has 2 rows and the first row is the header row, + * the actual row that contains the data is considered the first row, and has an index of 0. + */ +export const findRowAt = async (index: number): Promise => { + const rows = await screen.findAllByRole('row'); + const row = rows[index + 1]; + if (!row) { + throw new Error(`Cannot find row at index ${index}`); + } + return row; +}; + +export const findCellInRow = ( + row: HTMLElement, + { columnIndex }: { columnIndex: number } +): HTMLElement => { + const cells = within(row).getAllByRole('gridcell'); + const cell = cells[columnIndex]; + if (!cell) { + throw new Error(`Cannot find cell in row.`); + } + return cell; +}; diff --git a/packages/datagateway-search/src/table/__snapshots__/datafileSearchTable.component.test.tsx.snap b/packages/datagateway-search/src/table/__snapshots__/datafileSearchTable.component.test.tsx.snap deleted file mode 100644 index 0d80b25f1..000000000 --- a/packages/datagateway-search/src/table/__snapshots__/datafileSearchTable.component.test.tsx.snap +++ /dev/null @@ -1,109 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Datafile search table component renders correctly 1`] = ` -Object { - "allIds": Array [ - 1, - ], - "columns": Array [ - Object { - "cellContentRenderer": [Function], - "dataKey": "name", - "filterComponent": [Function], - "label": "datafiles.name", - }, - Object { - "dataKey": "location", - "filterComponent": [Function], - "label": "datafiles.location", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "fileSize", - "label": "datafiles.size", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "dataset.name", - "filterComponent": [Function], - "label": "datafiles.dataset", - }, - Object { - "dataKey": "modTime", - "filterComponent": [Function], - "label": "datafiles.modified_time", - }, - ], - "data": Array [ - Object { - "createTime": "2019-07-23", - "dataset": Object { - "createTime": "2019-07-23", - "endDate": "2019-07-25", - "id": 2, - "investigation": Object { - "doi": "doi 1", - "endDate": "2019-06-11", - "facility": Object { - "id": 8, - "name": "facility name", - }, - "id": 3, - "investigationInstruments": Array [ - Object { - "id": 4, - "instrument": Object { - "id": 5, - "name": "LARMOR", - }, - }, - ], - "name": "Investigation test name", - "size": 1, - "startDate": "2019-06-10", - "studyInvestigations": Array [ - Object { - "id": 6, - "investigation": Object { - "id": 3, - "name": "Investigation test name", - "title": "Investigation test title", - "visitId": "1", - }, - "study": Object { - "createTime": "2019-06-10", - "id": 7, - "modTime": "2019-06-10", - "name": "study name", - "pid": "study pid", - }, - }, - ], - "summary": "foo bar", - "title": "Investigation test title", - "visitId": "1", - }, - "modTime": "2019-07-23", - "name": "Dataset test name", - "size": 1, - "startDate": "2019-07-24", - }, - "fileSize": 1, - "id": 1, - "location": "/datafiletest", - "modTime": "2019-07-23", - "name": "Datafile test name", - }, - ], - "detailsPanel": [Function], - "disableSelectAll": false, - "loadMoreRows": [Function], - "loading": false, - "onCheck": [MockFunction], - "onSort": [Function], - "onUncheck": [MockFunction], - "selectedRows": Array [], - "sort": Object {}, - "totalRowCount": 0, -} -`; diff --git a/packages/datagateway-search/src/table/__snapshots__/datasetSearchTable.component.test.tsx.snap b/packages/datagateway-search/src/table/__snapshots__/datasetSearchTable.component.test.tsx.snap deleted file mode 100644 index f5045da0c..000000000 --- a/packages/datagateway-search/src/table/__snapshots__/datasetSearchTable.component.test.tsx.snap +++ /dev/null @@ -1,111 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dataset table component renders Dataset title as a link 1`] = ` - - Dataset test name - -`; - -exports[`Dataset table component renders correctly 1`] = ` -Object { - "allIds": Array [ - 1, - ], - "columns": Array [ - Object { - "cellContentRenderer": [Function], - "dataKey": "name", - "filterComponent": [Function], - "label": "datasets.name", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "datafileCount", - "disableSort": true, - "label": "datasets.datafile_count", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "investigation.title", - "filterComponent": [Function], - "label": "datasets.investigation", - }, - Object { - "dataKey": "createTime", - "filterComponent": [Function], - "label": "datasets.create_time", - }, - Object { - "dataKey": "modTime", - "filterComponent": [Function], - "label": "datasets.modified_time", - }, - ], - "data": Array [ - Object { - "createTime": "2019-07-23", - "endDate": "2019-07-25", - "id": 1, - "investigation": Object { - "doi": "doi 1", - "endDate": "2019-06-11", - "facility": Object { - "id": 7, - "name": "facility name", - }, - "id": 2, - "investigationInstruments": Array [ - Object { - "id": 3, - "instrument": Object { - "id": 4, - "name": "LARMOR", - }, - }, - ], - "name": "Investigation test name", - "size": 1, - "startDate": "2019-06-10", - "studyInvestigations": Array [ - Object { - "id": 5, - "investigation": Object { - "id": 2, - "name": "Investigation test name", - "title": "Investigation test title", - "visitId": "1", - }, - "study": Object { - "createTime": "2019-06-10", - "id": 6, - "modTime": "2019-06-10", - "name": "study name", - "pid": "study pid", - }, - }, - ], - "summary": "foo bar", - "title": "Investigation test title", - "visitId": "1", - }, - "modTime": "2019-07-23", - "name": "Dataset test name", - "size": 1, - "startDate": "2019-07-24", - }, - ], - "detailsPanel": [Function], - "disableSelectAll": false, - "loadMoreRows": [Function], - "loading": false, - "onCheck": [MockFunction], - "onSort": [Function], - "onUncheck": [MockFunction], - "selectedRows": Array [], - "sort": Object {}, - "totalRowCount": 0, -} -`; diff --git a/packages/datagateway-search/src/table/__snapshots__/investigationSearchTable.component.test.tsx.snap b/packages/datagateway-search/src/table/__snapshots__/investigationSearchTable.component.test.tsx.snap deleted file mode 100644 index 79af9d5d8..000000000 --- a/packages/datagateway-search/src/table/__snapshots__/investigationSearchTable.component.test.tsx.snap +++ /dev/null @@ -1,130 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Investigation Search Table component renders correctly 1`] = ` -Object { - "allIds": Array [ - 1, - ], - "columns": Array [ - Object { - "cellContentRenderer": [Function], - "dataKey": "title", - "filterComponent": [Function], - "label": "investigations.title", - }, - Object { - "dataKey": "visitId", - "filterComponent": [Function], - "label": "investigations.visit_id", - }, - Object { - "dataKey": "name", - "filterComponent": [Function], - "label": "investigations.name", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "doi", - "filterComponent": [Function], - "label": "investigations.doi", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "datasetCount", - "disableSort": true, - "label": "investigations.dataset_count", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "investigationInstruments.instrument.fullName", - "filterComponent": [Function], - "label": "investigations.instrument", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "startDate", - "filterComponent": [Function], - "label": "investigations.start_date", - }, - Object { - "cellContentRenderer": [Function], - "dataKey": "endDate", - "filterComponent": [Function], - "label": "investigations.end_date", - }, - ], - "data": Array [ - Object { - "doi": "doi 1", - "endDate": "2019-06-11", - "facility": Object { - "id": 2, - "name": "facility name", - }, - "id": 1, - "investigationInstruments": Array [ - Object { - "id": 1, - "instrument": Object { - "id": 3, - "name": "LARMOR", - }, - }, - ], - "name": "Test Name 1", - "size": 1, - "startDate": "2019-06-10", - "studyInvestigations": Array [ - Object { - "id": 6, - "investigation": Object { - "id": 1, - "name": "Test Name 1", - "title": "Test Title 1", - "visitId": "1", - }, - "study": Object { - "createTime": "2019-06-10", - "id": 7, - "modTime": "2019-06-10", - "name": "study name", - "pid": "study pid", - }, - }, - ], - "summary": "foo bar", - "title": "Test Title 1", - "visitId": "1", - }, - ], - "detailsPanel": [Function], - "disableSelectAll": false, - "loadMoreRows": [Function], - "loading": false, - "onCheck": [MockFunction], - "onSort": [Function], - "onUncheck": [MockFunction], - "selectedRows": Array [], - "sort": Object {}, - "totalRowCount": 0, -} -`; - -exports[`Investigation Search Table component renders title and DOI as links 1`] = ` - - Test Title 1 - -`; - -exports[`Investigation Search Table component renders title and DOI as links 2`] = ` - - doi 1 - -`; diff --git a/packages/datagateway-search/src/table/datafileSearchTable.component.test.tsx b/packages/datagateway-search/src/table/datafileSearchTable.component.test.tsx index 785e829a5..5c8d0c937 100644 --- a/packages/datagateway-search/src/table/datafileSearchTable.component.test.tsx +++ b/packages/datagateway-search/src/table/datafileSearchTable.component.test.tsx @@ -1,33 +1,45 @@ -import React from 'react'; +import * as React from 'react'; import DatafileSearchTable from './datafileSearchTable.component'; import { initialState as dgSearchInitialState } from '../state/reducers/dgsearch.reducer'; import configureStore from 'redux-mock-store'; import { StateType } from '../state/app.types'; import { Datafile, + dGCommonInitialState, useAddToCart, + useAllFacilityCycles, useCart, useDatafileCount, useDatafilesInfinite, useIds, useLuceneSearch, useRemoveFromCart, - useAllFacilityCycles, - ISISDatafileDetailsPanel, - DatafileDetailsPanel, - DLSDatafileDetailsPanel, - dGCommonInitialState, } from 'datagateway-common'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { Router } from 'react-router-dom'; import { createMemoryHistory, History } from 'history'; -import { mount, ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { applyDatePickerWorkaround, cleanupDatePickerWorkaround, } from '../setupTests'; +import { + render, + type RenderResult, + screen, + waitFor, + within, +} from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; +import { + findAllRows, + findCellInRow, + findColumnHeaderByName, + findColumnIndexByName, + findRowAt, +} from '../setupTests'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -51,11 +63,12 @@ describe('Datafile search table component', () => { const mockStore = configureStore([thunk]); let state: StateType; let history: History; + let user: UserEvent; let rowData: Datafile[] = []; - const createWrapper = (hierarchy?: string): ReactWrapper => { - return mount( + const renderComponent = (hierarchy?: string): RenderResult => + render( @@ -64,10 +77,10 @@ describe('Datafile search table component', () => { ); - }; beforeEach(() => { history = createMemoryHistory(); + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ @@ -173,126 +186,109 @@ describe('Datafile search table component', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('VirtualizedTable').props()).toMatchSnapshot(); + it('renders correctly', async () => { + renderComponent(); + + // check that column headers are shown correctly. + expect(await findColumnHeaderByName('datafiles.name')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.location') + ).toBeInTheDocument(); + expect(await findColumnHeaderByName('datafiles.size')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.dataset') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datafiles.modified_time') + ).toBeInTheDocument(); + + const rows = await findAllRows(); + // should have 1 row in the table + expect(rows).toHaveLength(1); + + const row = rows[0]; + + // each cell in the row should contain the correct value + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datafiles.name'), + }) + ).getByText('Datafile test name') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datafiles.location'), + }) + ).getByText('/datafiletest') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datafiles.size'), + }) + ).getByText('1 B') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datafiles.dataset'), + }) + ).getByText('Dataset test name') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datafiles.modified_time'), + }) + ).getByText('2019-07-23') + ).toBeInTheDocument(); }); - it('calls the correct data fetching hooks on load', () => { - (useLuceneSearch as jest.Mock).mockReturnValue({ - data: [1], - }); - - createWrapper(); + it('updates filter query params on text filter', async () => { + renderComponent(); - expect(useCart).toHaveBeenCalled(); - expect(useLuceneSearch).toHaveBeenCalledWith('Datafile', { - searchText: '', - startDate: null, - endDate: null, - maxCount: 300, + const filterInput = await screen.findByRole('textbox', { + name: 'Filter by datafiles.name', + hidden: true, }); - expect(useDatafileCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ]); - expect(useDatafilesInfinite).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - { - filterType: 'include', - filterValue: JSON.stringify({ - dataset: { - investigation: { investigationInstruments: 'instrument' }, - }, - }), - }, - ]); - expect(useIds).toHaveBeenCalledWith( - 'datafile', - [ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ], - true - ); - - expect(useAddToCart).toHaveBeenCalledWith('datafile'); - expect(useRemoveFromCart).toHaveBeenCalledWith('datafile'); - }); + await user.type(filterInput, 'test'); - it('calls fetchNextPage function of useDatafilesInfinite when loadMoreRows is called', () => { - const fetchNextPage = jest.fn(); - (useDatafilesInfinite as jest.Mock).mockReturnValue({ - data: { pages: [rowData] }, - fetchNextPage, - }); - const wrapper = createWrapper(); - - wrapper.find('VirtualizedTable').prop('loadMoreRows')({ - startIndex: 50, - stopIndex: 74, - }); - - expect(fetchNextPage).toHaveBeenCalledWith({ - pageParam: { startIndex: 50, stopIndex: 74 }, - }); - }); - - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); - - const filterInput = wrapper - .find('[aria-label="Filter by datafiles.name"]') - .last(); - filterInput.instance().value = 'test'; - filterInput.simulate('change'); - - expect(history.length).toBe(2); + // user.type inputs the given string character by character to simulate user typing + // each keystroke of user.type creates a new entry in the history stack + // so the initial entry + 4 characters in "test" = 5 entries + expect(history.length).toBe(5); expect(history.location.search).toBe( `?filters=${encodeURIComponent( '{"name":{"value":"test","type":"include"}}' )}` ); - filterInput.instance().value = ''; - filterInput.simulate('change'); + await user.clear(filterInput); - expect(history.length).toBe(3); + expect(history.length).toBe(6); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); - const filterInput = wrapper.find( - 'input[id="datafiles.modified_time filter to"]' - ); - filterInput.instance().value = '2019-08-06'; - filterInput.simulate('change'); + const filterInput = await screen.findByRole('textbox', { + name: 'datafiles.modified_time filter to', + }); + + await user.type(filterInput, '2019-08-06'); expect(history.length).toBe(2); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"modTime":{"endDate":"2019-08-06"}}')}` ); - filterInput.instance().value = ''; - filterInput.simulate('change'); + await user.clear(filterInput); expect(history.length).toBe(3); expect(history.location.search).toBe('?'); @@ -300,13 +296,12 @@ describe('Datafile search table component', () => { cleanupDatePickerWorkaround(); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - wrapper - .find('[role="columnheader"] span[role="button"]') - .first() - .simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'datafiles.name' }) + ); expect(history.length).toBe(2); expect(history.location.search).toBe( @@ -314,20 +309,22 @@ describe('Datafile search table component', () => { ); }); - it('calls addToCart mutate function on unchecked checkbox click', () => { + it('calls addToCart mutate function on unchecked checkbox click', async () => { const addToCart = jest.fn(); (useAddToCart as jest.Mock).mockReturnValue({ mutate: addToCart, loading: false, }); - const wrapper = createWrapper(); + renderComponent(); - wrapper.find('[aria-label="select row 0"]').last().simulate('click'); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 0' }) + ); expect(addToCart).toHaveBeenCalledWith([1]); }); - it('calls removeFromCart mutate function on checked checkbox click', () => { + it('calls removeFromCart mutate function on checked checkbox click', async () => { (useCart as jest.Mock).mockReturnValue({ data: [ { @@ -347,14 +344,16 @@ describe('Datafile search table component', () => { loading: false, }); - const wrapper = createWrapper(); + renderComponent(); - wrapper.find('[aria-label="select row 0"]').last().simulate('click'); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 0' }) + ); expect(removeFromCart).toHaveBeenCalledWith([1]); }); - it('selected rows only considers relevant cart items', () => { + it('selected rows only considers relevant cart items', async () => { (useCart as jest.Mock).mockReturnValue({ data: [ { @@ -375,54 +374,60 @@ describe('Datafile search table component', () => { isLoading: false, }); - const wrapper = createWrapper(); + renderComponent(); - const selectAllCheckbox = wrapper - .find('[aria-label="select all rows"]') - .first(); + const selectAllCheckbox = await screen.findByRole('checkbox', { + name: 'select all rows', + }); - expect(selectAllCheckbox.prop('checked')).toEqual(false); - expect(selectAllCheckbox.prop('data-indeterminate')).toEqual(false); + expect(selectAllCheckbox).not.toBeChecked(); + expect(selectAllCheckbox).toHaveAttribute('data-indeterminate', 'false'); }); - it('no select all checkbox appears and no fetchAllIds sent if selectAllSetting is false', () => { + it('no select all checkbox appears and no fetchAllIds sent if selectAllSetting is false', async () => { state.dgsearch.selectAllSetting = false; + renderComponent(); + await waitFor(() => { + expect( + screen.queryByRole('checkbox', { name: 'select all rows' }) + ).toBeNull(); + }); + }); - const wrapper = createWrapper(); + it('displays generic details panel when expanded', async () => { + renderComponent(); - expect(useIds).toHaveBeenCalledWith('datafile', expect.anything(), false); - expect(useIds).not.toHaveBeenCalledWith( - 'datafile', - expect.anything(), - true - ); - expect(wrapper.find('[aria-label="select all rows"]')).toHaveLength(0); + const row = await findRowAt(0); + await user.click(within(row).getByRole('button', { name: 'Show details' })); + + expect( + await screen.findByTestId('datafile-details-panel') + ).toBeInTheDocument(); }); - it('displays generic details panel when expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(DatafileDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); + it('displays correct details panel for ISIS when expanded', async () => { + renderComponent('isis'); - expect(wrapper.find(DatafileDetailsPanel).exists()).toBeTruthy(); - }); + const row = await findRowAt(0); + await user.click(within(row).getByRole('button', { name: 'Show details' })); - it('displays correct details panel for ISIS when expanded', () => { - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISDatafileDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); - expect(wrapper.find(ISISDatafileDetailsPanel).exists()).toBeTruthy(); + expect( + await screen.findByTestId('isis-datafile-details-panel') + ).toBeInTheDocument(); }); - it('displays correct details panel for DLS when expanded', () => { - const wrapper = createWrapper('dls'); - expect(wrapper.find(DLSDatafileDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); + it('displays correct details panel for DLS when expanded', async () => { + renderComponent('dls'); + + const row = await findRowAt(0); + await user.click(within(row).getByRole('button', { name: 'Show details' })); - expect(wrapper.find(DLSDatafileDetailsPanel).exists()).toBeTruthy(); + expect( + await screen.findByTestId('dls-datafile-details-panel') + ).toBeInTheDocument(); }); - it('renders fine with incomplete data', () => { + it('renders fine with incomplete data', async () => { // this can happen when navigating between tables and the previous table's state still exists rowData = [ { @@ -439,32 +444,45 @@ describe('Datafile search table component', () => { fetchNextPage: jest.fn(), }); - expect(() => createWrapper()).not.toThrowError(); + renderComponent(); + + expect(await findAllRows()).toHaveLength(1); }); - it('renders generic link correctly', () => { - const wrapper = createWrapper('data'); + it('renders generic link correctly', async () => { + renderComponent('data'); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( - `/browse/investigation/3/dataset/2/datafile` - ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); + const datasetColIndex = await findColumnIndexByName('datafiles.dataset'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + expect( + within(datasetLinkCell).getByRole('link', { name: 'Dataset test name' }) + ).toHaveAttribute('href', '/browse/investigation/3/dataset/2/datafile'); }); - it('renders DLS link correctly', () => { - const wrapper = createWrapper('dls'); + it('renders DLS link correctly', async () => { + renderComponent('dls'); + + const datasetColIndex = await findColumnIndexByName('datafiles.name'); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + expect( + within(datasetLinkCell).getByRole('link', { name: 'Datafile test name' }) + ).toHaveAttribute( + 'href', '/browse/proposal/Dataset test name/investigation/3/dataset/2/datafile' ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); }); - it('renders ISIS link correctly', () => { + it('renders ISIS link correctly', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -476,17 +494,24 @@ describe('Datafile search table component', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( - `/browse/instrument/5/facilityCycle/4/investigation/3/dataset/2/datafile` - ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' + const datasetColIndex = await findColumnIndexByName('datafiles.name'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + expect( + within(datasetLinkCell).getByRole('link', { name: 'Datafile test name' }) + ).toHaveAttribute( + 'href', + '/browse/instrument/5/facilityCycle/4/investigation/3/dataset/2/datafile' ); }); - it('does not render ISIS link when instrumentId cannot be found', () => { + it('does not render ISIS link when instrumentId cannot be found', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -502,24 +527,52 @@ describe('Datafile search table component', () => { data: { pages: [rowData] }, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); + const datasetColIndex = await findColumnIndexByName('datafiles.name'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + await waitFor(() => { + expect( + within(datasetLinkCell).queryByRole('link', { + name: 'Datafile test name', + }) + ).toBeNull(); + }); + + expect( + within(datasetLinkCell).getByText('Datafile test name') + ).toBeInTheDocument(); }); - it('does not render ISIS link when facilityCycleId cannot be found', () => { - const wrapper = createWrapper('isis'); + it('does not render ISIS link when facilityCycleId cannot be found', async () => { + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); + const datasetColIndex = await findColumnIndexByName('datafiles.name'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + await waitFor(() => { + expect( + within(datasetLinkCell).queryByRole('link', { + name: 'Datafile test name', + }) + ).toBeNull(); + }); + + expect( + within(datasetLinkCell).getByText('Datafile test name') + ).toBeInTheDocument(); }); - it('does not render ISIS link when facilityCycleId has incompatible dates', () => { + it('does not render ISIS link when facilityCycleId has incompatible dates', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -531,15 +584,29 @@ describe('Datafile search table component', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); + const datasetColIndex = await findColumnIndexByName('datafiles.name'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + await waitFor(() => { + expect( + within(datasetLinkCell).queryByRole('link', { + name: 'Datafile test name', + }) + ).toBeNull(); + }); + + expect( + within(datasetLinkCell).getByText('Datafile test name') + ).toBeInTheDocument(); }); - it('displays only the datafile name when there is no generic dataset to link to', () => { + it('displays only the datafile name when there is no generic dataset to link to', async () => { rowData = [ { id: 1, @@ -555,15 +622,29 @@ describe('Datafile search table component', () => { fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('data'); + renderComponent('data'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); + const datasetColIndex = await findColumnIndexByName('datafiles.name'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + await waitFor(() => { + expect( + within(datasetLinkCell).queryByRole('link', { + name: 'Datafile test name', + }) + ).toBeNull(); + }); + + expect( + within(datasetLinkCell).getByText('Datafile test name') + ).toBeInTheDocument(); }); - it('displays only the datafile name when there is no DLS dataset to link to', () => { + it('displays only the datafile name when there is no DLS dataset to link to', async () => { rowData = [ { id: 1, @@ -579,15 +660,29 @@ describe('Datafile search table component', () => { fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('dls'); + renderComponent('dls'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); + const datasetColIndex = await findColumnIndexByName('datafiles.name'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + await waitFor(() => { + expect( + within(datasetLinkCell).queryByRole('link', { + name: 'Datafile test name', + }) + ).toBeNull(); + }); + + expect( + within(datasetLinkCell).getByText('Datafile test name') + ).toBeInTheDocument(); }); - it('displays only the datafile name when there is no ISIS investigation to link to', () => { + it('displays only the datafile name when there is no ISIS investigation to link to', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -613,11 +708,25 @@ describe('Datafile search table component', () => { fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Datafile test name' - ); + const datasetColIndex = await findColumnIndexByName('datafiles.name'); + + const row = await findRowAt(0); + const datasetLinkCell = await findCellInRow(row, { + columnIndex: datasetColIndex, + }); + + await waitFor(() => { + expect( + within(datasetLinkCell).queryByRole('link', { + name: 'Datafile test name', + }) + ).toBeNull(); + }); + + expect( + within(datasetLinkCell).getByText('Datafile test name') + ).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-search/src/table/datasetSearchTable.component.test.tsx b/packages/datagateway-search/src/table/datasetSearchTable.component.test.tsx index 6aa2b4511..8c877c950 100644 --- a/packages/datagateway-search/src/table/datasetSearchTable.component.test.tsx +++ b/packages/datagateway-search/src/table/datasetSearchTable.component.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import DatasetSearchTable from './datasetSearchTable.component'; import { initialState } from '../state/reducers/dgsearch.reducer'; import configureStore from 'redux-mock-store'; @@ -6,9 +6,6 @@ import { StateType } from '../state/app.types'; import { Dataset, dGCommonInitialState, - DatasetDetailsPanel, - ISISDatasetDetailsPanel, - DLSDatasetDetailsPanel, useAddToCart, useAllFacilityCycles, useCart, @@ -22,15 +19,29 @@ import { } from 'datagateway-common'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; -import { mount, ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { createMemoryHistory, History } from 'history'; import { Router } from 'react-router-dom'; -import { render, RenderResult } from '@testing-library/react'; +import { + render, + type RenderResult, + screen, + waitFor, + within, +} from '@testing-library/react'; import { applyDatePickerWorkaround, cleanupDatePickerWorkaround, } from '../setupTests'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import userEvent from '@testing-library/user-event'; +import { + findAllRows, + findCellInRow, + findColumnHeaderByName, + findColumnIndexByName, + findRowAt, +} from '../setupTests'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -55,22 +66,11 @@ describe('Dataset table component', () => { const mockStore = configureStore([thunk]); let state: StateType; let history: History; + let user: UserEvent; let rowData: Dataset[] = []; - const createWrapper = (hierarchy?: string): ReactWrapper => { - return mount( - - - - - - - - ); - }; - - const createRTLWrapper = (hierarchy?: string): RenderResult => { + const renderComponent = (hierarchy?: string): RenderResult => { return render( @@ -84,6 +84,7 @@ describe('Dataset table component', () => { beforeEach(() => { history = createMemoryHistory(); + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ dgcommon: dGCommonInitialState, dgsearch: initialState }) @@ -200,126 +201,169 @@ describe('Dataset table component', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('VirtualizedTable').props()).toMatchSnapshot(); + it('renders correctly', async () => { + renderComponent(); + + const rows = await findAllRows(); + // should have 1 row in the table + expect(rows).toHaveLength(1); + + const row = rows[0]; + + // check that column headers are shown correctly + expect(await findColumnHeaderByName('datasets.name')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.datafile_count') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.investigation') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.create_time') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.modified_time') + ).toBeInTheDocument(); + + // each cell in the row should contain the correct value + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.name'), + }) + ).getByText('Dataset test name') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.datafile_count'), + }) + ).getByText('1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.investigation'), + }) + ).getByText('Investigation test title') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.create_time'), + }) + ).getByText('2019-07-23') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.modified_time'), + }) + ).getByText('2019-07-23') + ).toBeInTheDocument(); }); - it('calls the correct data fetching hooks on load', () => { - (useLuceneSearch as jest.Mock).mockReturnValue({ - data: [1], - }); - - createWrapper(); - - expect(useCart).toHaveBeenCalled(); - expect(useLuceneSearch).toHaveBeenCalledWith('Dataset', { - searchText: '', - startDate: null, - endDate: null, - maxCount: 300, - }); - - expect(useDatasetCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ]); - expect(useDatasetsInfinite).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - { - filterType: 'include', - filterValue: JSON.stringify({ - investigation: { investigationInstruments: 'instrument' }, - }), - }, - ]); - expect(useIds).toHaveBeenCalledWith( - 'dataset', - [ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ], - true - ); - - expect(useAddToCart).toHaveBeenCalledWith('dataset'); - expect(useRemoveFromCart).toHaveBeenCalledWith('dataset'); - expect(useDatasetsDatafileCount).toHaveBeenCalledWith({ pages: [rowData] }); - expect(useDatasetSizes).toHaveBeenCalledWith(undefined); + it('renders correctly for isis', async () => { + renderComponent('isis'); + + const rows = await findAllRows(); + // should have 1 row in the table + expect(rows).toHaveLength(1); + + const row = rows[0]; + + // check that column headers are shown correctly + expect(await findColumnHeaderByName('datasets.name')).toBeInTheDocument(); + expect(await findColumnHeaderByName('datasets.size')).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.investigation') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.create_time') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('datasets.modified_time') + ).toBeInTheDocument(); + + // each cell in the row should contain the correct value + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.name'), + }) + ).getByText('Dataset test name') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.size'), + }) + ).getByText('1 B') + ).toBeInTheDocument(); + expect( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.investigation'), + }) + ).toHaveTextContent(''); // expect empty text content because facility cycle is not provided + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.create_time'), + }) + ).getByText('2019-07-23') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('datasets.modified_time'), + }) + ).getByText('2019-07-23') + ).toBeInTheDocument(); }); - it('calls fetchNextPage function of useDatafilesInfinite when loadMoreRows is called', () => { - const fetchNextPage = jest.fn(); - (useDatasetsInfinite as jest.Mock).mockReturnValue({ - data: { pages: [rowData] }, - fetchNextPage, - }); - const wrapper = createWrapper(); + it('updates filter query params on text filter', async () => { + renderComponent(); - wrapper.find('VirtualizedTable').prop('loadMoreRows')({ - startIndex: 50, - stopIndex: 74, + const filterInput = await screen.findByRole('textbox', { + name: 'Filter by datasets.name', + hidden: true, }); - expect(fetchNextPage).toHaveBeenCalledWith({ - pageParam: { startIndex: 50, stopIndex: 74 }, - }); - }); - - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + await user.type(filterInput, 'test'); - const filterInput = wrapper - .find('[aria-label="Filter by datasets.name"]') - .last(); - filterInput.instance().value = 'test'; - filterInput.simulate('change'); - - expect(history.length).toBe(2); + // user.type inputs the given string character by character to simulate user typing + // each keystroke of user.type creates a new entry in the history stack + // so the initial entry + 4 characters in "test" = 5 entries + expect(history.length).toBe(5); expect(history.location.search).toBe( `?filters=${encodeURIComponent( '{"name":{"value":"test","type":"include"}}' )}` ); - filterInput.instance().value = ''; - filterInput.simulate('change'); + await user.clear(filterInput); - expect(history.length).toBe(3); + expect(history.length).toBe(6); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); - const filterInput = wrapper.find( - 'input[id="datasets.modified_time filter to"]' - ); - filterInput.instance().value = '2019-08-06'; - filterInput.simulate('change'); + const filterInput = await screen.findByRole('textbox', { + name: 'datasets.modified_time filter to', + }); + + await user.type(filterInput, '2019-08-06'); expect(history.length).toBe(2); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"modTime":{"endDate":"2019-08-06"}}')}` ); - filterInput.instance().value = ''; - filterInput.simulate('change'); + await user.clear(filterInput); expect(history.length).toBe(3); expect(history.location.search).toBe('?'); @@ -327,13 +371,12 @@ describe('Dataset table component', () => { cleanupDatePickerWorkaround(); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - wrapper - .find('[role="columnheader"] span[role="button"]') - .first() - .simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'datasets.name' }) + ); expect(history.length).toBe(2); expect(history.location.search).toBe( @@ -341,20 +384,22 @@ describe('Dataset table component', () => { ); }); - it('calls addToCart mutate function on unchecked checkbox click', () => { + it('calls addToCart mutate function on unchecked checkbox click', async () => { const addToCart = jest.fn(); (useAddToCart as jest.Mock).mockReturnValue({ mutate: addToCart, loading: false, }); - const wrapper = createWrapper(); + renderComponent(); - wrapper.find('[aria-label="select row 0"]').last().simulate('click'); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 0' }) + ); expect(addToCart).toHaveBeenCalledWith([1]); }); - it('calls removeFromCart mutate function on checked checkbox click', () => { + it('calls removeFromCart mutate function on checked checkbox click', async () => { (useCart as jest.Mock).mockReturnValue({ data: [ { @@ -374,14 +419,16 @@ describe('Dataset table component', () => { loading: false, }); - const wrapper = createWrapper(); + renderComponent(); - wrapper.find('[aria-label="select row 0"]').last().simulate('click'); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 0' }) + ); expect(removeFromCart).toHaveBeenCalledWith([1]); }); - it('selected rows only considers relevant cart items', () => { + it('selected rows only considers relevant cart items', async () => { (useCart as jest.Mock).mockReturnValue({ data: [ { @@ -402,42 +449,51 @@ describe('Dataset table component', () => { isLoading: false, }); - const wrapper = createWrapper(); + renderComponent(); - const selectAllCheckbox = wrapper - .find('[aria-label="select all rows"]') - .first(); + const selectAllCheckbox = await screen.findByRole('checkbox', { + name: 'select all rows', + }); - expect(selectAllCheckbox.prop('checked')).toEqual(false); - expect(selectAllCheckbox.prop('data-indeterminate')).toEqual(false); + expect(selectAllCheckbox).not.toBeChecked(); + expect(selectAllCheckbox).toHaveAttribute('data-indeterminate', 'false'); }); - it('no select all checkbox appears and no fetchAllIds sent if selectAllSetting is false', () => { + it('no select all checkbox appears and no fetchAllIds sent if selectAllSetting is false', async () => { state.dgsearch.selectAllSetting = false; + renderComponent(); + await waitFor(() => { + expect( + screen.queryByRole('checkbox', { name: 'select all rows' }) + ).toBeNull(); + }); + }); - const wrapper = createWrapper(); + it('displays generic details panel when expanded', async () => { + renderComponent(); - expect(useIds).toHaveBeenCalledWith('dataset', expect.anything(), false); - expect(useIds).not.toHaveBeenCalledWith('dataset', expect.anything(), true); - expect(wrapper.find('[aria-label="select all rows"]')).toHaveLength(0); - }); + const row = await findRowAt(0); - it('displays generic details panel when expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(DatasetDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); + await user.click(within(row).getByRole('button', { name: 'Show details' })); - expect(wrapper.find(DatasetDetailsPanel).exists()).toBeTruthy(); + expect( + await screen.findByTestId('dataset-details-panel') + ).toBeInTheDocument(); }); - it('displays correct details panel for ISIS when expanded', () => { - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeTruthy(); + it('displays correct details panel for ISIS when expanded', async () => { + renderComponent('isis'); + + const row = await findRowAt(0); + + await user.click(within(row).getByRole('button', { name: 'Show details' })); + + expect( + await screen.findByTestId('isis-dataset-details-panel') + ).toBeInTheDocument(); }); - it('can navigate using the details panel for ISIS when there are facility cycles', () => { + it('can navigate using the details panel for ISIS when there are facility cycles', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -449,33 +505,54 @@ describe('Dataset table component', () => { ], }); - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); + renderComponent('isis'); + + const row = await findRowAt(0); - expect(wrapper.find(ISISDatasetDetailsPanel).exists()).toBeTruthy(); + await user.click(within(row).getByRole('button', { name: 'Show details' })); + + expect( + await screen.findByTestId('isis-dataset-details-panel') + ).toBeInTheDocument(); + + await user.click( + await screen.findByRole('tab', { name: 'datasets.details.datafiles' }) + ); - wrapper.find('#dataset-datafiles-tab').last().simulate('click'); expect(history.location.pathname).toBe( '/browse/instrument/4/facilityCycle/4/investigation/2/dataset/1' ); }); - it('displays correct details panel for DLS when expanded', () => { - const wrapper = createWrapper('dls'); - expect(wrapper.find(DLSDatasetDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); + it('displays correct details panel for DLS when expanded', async () => { + renderComponent('dls'); - expect(wrapper.find(DLSDatasetDetailsPanel).exists()).toBeTruthy(); + const row = await findRowAt(0); + + await user.click(within(row).getByRole('button', { name: 'Show details' })); + + expect( + await screen.findByTestId('dataset-details-panel') + ).toBeInTheDocument(); }); - it('renders Dataset title as a link', () => { - const wrapper = createRTLWrapper(); + it('renders Dataset title as a link', async () => { + renderComponent(); + + // find the title cell - expect(wrapper.getByText('Dataset test name')).toMatchSnapshot(); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + + expect( + within(titleCell).getByRole('link', { name: 'Dataset test name' }) + ).toHaveAttribute('href', '/browse/investigation/2/dataset/1/datafile'); }); - it('renders fine with incomplete data', () => { + it('renders fine with incomplete data', async () => { // this can happen when navigating between tables and the previous table's state still exists rowData = [ { @@ -492,40 +569,57 @@ describe('Dataset table component', () => { fetchNextPage: jest.fn(), }); - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); - expect(() => createWrapper()).not.toThrowError(); - expect(consoleSpy).not.toHaveBeenCalled(); + renderComponent(); + + expect(await findAllRows()).toHaveLength(1); }); - it('renders generic link & pending count correctly', () => { + it('renders generic link & pending count correctly', async () => { (useDatasetsDatafileCount as jest.Mock).mockImplementation(() => [ { isFetching: true, }, ]); - const wrapper = createWrapper('data'); + renderComponent('data'); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( - `/browse/investigation/2/dataset/1/datafile` + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const datasetSizeColIndex = await findColumnIndexByName( + 'datasets.datafile_count' ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); - expect(wrapper.find('[aria-colindex=4]').text()).toEqual('Calculating...'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + const fileCountCell = await findCellInRow(row, { + columnIndex: datasetSizeColIndex, + }); + + expect( + within(titleCell).getByRole('link', { name: 'Dataset test name' }) + ).toHaveAttribute('href', '/browse/investigation/2/dataset/1/datafile'); + expect( + within(fileCountCell).getByText('Calculating...') + ).toBeInTheDocument(); }); - it('renders DLS link correctly', () => { - const wrapper = createWrapper('dls'); + it('renders DLS link correctly', async () => { + renderComponent('dls'); + + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( + expect( + within(titleCell).getByRole('link', { name: 'Dataset test name' }) + ).toHaveAttribute( + 'href', '/browse/proposal/Investigation test name/investigation/2/dataset/1/datafile' ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); }); - it('renders ISIS link & file sizes correctly', () => { + it('renders ISIS link & file sizes correctly', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -537,21 +631,28 @@ describe('Dataset table component', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(useDatasetSizes).toHaveBeenCalledWith({ pages: [rowData] }); - expect(useDatasetsDatafileCount).toHaveBeenCalledWith(undefined); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const datasetSizeColIndex = await findColumnIndexByName('datasets.size'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + const sizeCell = await findCellInRow(row, { + columnIndex: datasetSizeColIndex, + }); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( - `/browse/instrument/4/facilityCycle/6/investigation/2/dataset/1` - ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' + expect( + within(titleCell).getByRole('link', { name: 'Dataset test name' }) + ).toHaveAttribute( + 'href', + '/browse/instrument/4/facilityCycle/6/investigation/2/dataset/1' ); - expect(wrapper.find('[aria-colindex=4]').text()).toEqual('1 B'); + expect(within(sizeCell).getByText('1 B')).toBeInTheDocument(); }); - it('does not render ISIS link when instrumentId cannot be found', () => { + it('does not render ISIS link when instrumentId cannot be found', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -568,24 +669,40 @@ describe('Dataset table component', () => { data: { pages: [rowData] }, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + + expect( + within(titleCell).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + expect( + within(titleCell).getByText('Dataset test name') + ).toBeInTheDocument(); }); - it('does not render ISIS link when facilityCycleId cannot be found', () => { - const wrapper = createWrapper('isis'); + it('does not render ISIS link when facilityCycleId cannot be found', async () => { + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + + expect( + within(titleCell).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + expect( + within(titleCell).getByText('Dataset test name') + ).toBeInTheDocument(); }); - it('does not render ISIS link when facilityCycleId has incompatible dates', () => { + it('does not render ISIS link when facilityCycleId has incompatible dates', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -597,45 +714,69 @@ describe('Dataset table component', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + + expect( + within(titleCell).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + expect( + within(titleCell).getByText('Dataset test name') + ).toBeInTheDocument(); }); - it('displays only the dataset name when there is no generic investigation to link to', () => { + it('displays only the dataset name when there is no generic investigation to link to', async () => { delete rowData[0].investigation; (useDatasetsInfinite as jest.Mock).mockReturnValue({ data: { pages: [rowData] }, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('data'); + renderComponent('data'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + + expect( + within(titleCell).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + expect( + within(titleCell).getByText('Dataset test name') + ).toBeInTheDocument(); }); - it('displays only the dataset name when there is no DLS investigation to link to', () => { + it('displays only the dataset name when there is no DLS investigation to link to', async () => { delete rowData[0].investigation; (useDatasetsInfinite as jest.Mock).mockReturnValue({ data: { pages: [rowData] }, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('dls'); + renderComponent('dls'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + + expect( + within(titleCell).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + expect( + within(titleCell).getByText('Dataset test name') + ).toBeInTheDocument(); }); - it('displays only the dataset name when there is no ISIS investigation to link to', () => { + it('displays only the dataset name when there is no ISIS investigation to link to', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -652,11 +793,19 @@ describe('Dataset table component', () => { fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual( - 'Dataset test name' - ); + const datasetNameColIndex = await findColumnIndexByName('datasets.name'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { + columnIndex: datasetNameColIndex, + }); + + expect( + within(titleCell).queryByRole('link', { name: 'Dataset test name' }) + ).toBeNull(); + expect( + within(titleCell).getByText('Dataset test name') + ).toBeInTheDocument(); }); }); diff --git a/packages/datagateway-search/src/table/investigationSearchTable.component.test.tsx b/packages/datagateway-search/src/table/investigationSearchTable.component.test.tsx index 035c570f7..145d1cb12 100644 --- a/packages/datagateway-search/src/table/investigationSearchTable.component.test.tsx +++ b/packages/datagateway-search/src/table/investigationSearchTable.component.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { initialState } from '../state/reducers/dgsearch.reducer'; import configureStore from 'redux-mock-store'; import { StateType } from '../state/app.types'; @@ -15,22 +15,33 @@ import { useInvestigationSizes, useLuceneSearch, useRemoveFromCart, - ISISInvestigationDetailsPanel, - InvestigationDetailsPanel, - DLSVisitDetailsPanel, } from 'datagateway-common'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; -import { mount, ReactWrapper } from 'enzyme'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { createMemoryHistory, History } from 'history'; import { Router } from 'react-router-dom'; import InvestigationSearchTable from './investigationSearchTable.component'; -import { render, RenderResult } from '@testing-library/react'; +import { + render, + type RenderResult, + screen, + waitFor, + within, +} from '@testing-library/react'; import { applyDatePickerWorkaround, cleanupDatePickerWorkaround, } from '../setupTests'; +import userEvent from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/setup/setup'; +import { + findAllRows, + findCellInRow, + findColumnHeaderByName, + findColumnIndexByName, + findRowAt, +} from '../setupTests'; jest.mock('datagateway-common', () => { const originalModule = jest.requireActual('datagateway-common'); @@ -56,22 +67,11 @@ describe('Investigation Search Table component', () => { const mockStore = configureStore([thunk]); let state: StateType; let history: History; + let user: UserEvent; let rowData: Investigation[] = []; - const createWrapper = (hierarchy?: string): ReactWrapper => { - return mount( - - - - - - - - ); - }; - - const createRTLWrapper = (hierarchy?: string): RenderResult => { + const renderComponent = (hierarchy?: string): RenderResult => { return render( @@ -85,6 +85,7 @@ describe('Investigation Search Table component', () => { beforeEach(() => { history = createMemoryHistory(); + user = userEvent.setup(); state = JSON.parse( JSON.stringify({ @@ -107,6 +108,7 @@ describe('Investigation Search Table component', () => { instrument: { id: 3, name: 'LARMOR', + fullName: 'LARMORLARMOR', }, }, ], @@ -196,145 +198,248 @@ describe('Investigation Search Table component', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { - const wrapper = createWrapper(); - expect(wrapper.find('VirtualizedTable').props()).toMatchSnapshot(); - }); - - it('calls the correct data fetching hooks on load', () => { - (useLuceneSearch as jest.Mock).mockReturnValue({ - data: [1], - }); + it('renders correctly', async () => { + renderComponent(); - createWrapper(); + const rows = await findAllRows(); + // should have 1 row in the table + expect(rows).toHaveLength(1); - expect(useCart).toHaveBeenCalled(); - expect(useLuceneSearch).toHaveBeenCalledWith('Investigation', { - searchText: '', - startDate: null, - endDate: null, - maxCount: 300, - }); + const row = rows[0]; - expect(useInvestigationCount).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ]); - expect(useInvestigationsInfinite).toHaveBeenCalledWith([ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - { - filterType: 'include', - filterValue: JSON.stringify({ - investigationInstruments: 'instrument', - }), - }, - ]); - expect(useIds).toHaveBeenCalledWith( - 'investigation', - [ - { - filterType: 'where', - filterValue: JSON.stringify({ - id: { in: [1] }, - }), - }, - ], - true - ); + // check that column headers are shown correctly + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.visit_id') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.doi') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.dataset_count') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.instrument') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); - expect(useAddToCart).toHaveBeenCalledWith('investigation'); - expect(useRemoveFromCart).toHaveBeenCalledWith('investigation'); - expect(useInvestigationsDatasetCount).toHaveBeenCalledWith({ - pages: [rowData], - }); - expect(useInvestigationSizes).toHaveBeenCalledWith(undefined); + // each cell in the row should contain the correct value + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.title'), + }) + ).getByText('Test Title 1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.visit_id'), + }) + ).getByText('1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.name'), + }) + ).getByText('Test Name 1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.doi'), + }) + ).getByText('doi 1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName( + 'investigations.dataset_count' + ), + }) + ).getByText('1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.instrument'), + }) + ).getByText('LARMORLARMOR') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.start_date'), + }) + ).getByText('2019-06-10') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.end_date'), + }) + ).getByText('2019-06-11') + ).toBeInTheDocument(); }); - it('calls fetchNextPage function of useDatafilesInfinite when loadMoreRows is called', () => { - const fetchNextPage = jest.fn(); - (useInvestigationsInfinite as jest.Mock).mockReturnValue({ - data: { pages: [rowData] }, - fetchNextPage, - }); - const wrapper = createWrapper(); + it('renders correctly for isis', async () => { + renderComponent('isis'); - wrapper.find('VirtualizedTable').prop('loadMoreRows')({ - startIndex: 50, - stopIndex: 74, - }); + const rows = await findAllRows(); + // should have 1 row in the table + expect(rows).toHaveLength(1); - expect(fetchNextPage).toHaveBeenCalledWith({ - pageParam: { startIndex: 50, stopIndex: 74 }, - }); - }); + const row = rows[0]; - it('displays DOI and renders the expected Link ', () => { - const wrapper = createWrapper(); + // check that column headers are shown correctly + expect( + await findColumnHeaderByName('investigations.title') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.visit_id') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.name') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.doi') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.size') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.instrument') + ).toBeInTheDocument(); expect( - wrapper - .find('[data-testid="investigation-search-table-doi-link"]') - .first() - .text() - ).toEqual('doi 1'); + await findColumnHeaderByName('investigations.start_date') + ).toBeInTheDocument(); + expect( + await findColumnHeaderByName('investigations.end_date') + ).toBeInTheDocument(); + // each cell in the row should contain the correct value + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.title'), + }) + ).getByText('Test Title 1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.visit_id'), + }) + ).getByText('1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.name'), + }) + ).getByText('Test Name 1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.doi'), + }) + ).getByText('doi 1') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.size'), + }) + ).getByText('1 B') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.instrument'), + }) + ).getByText('LARMORLARMOR') + ).toBeInTheDocument(); + expect( + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.start_date'), + }) + ).getByText('2019-06-10') + ).toBeInTheDocument(); expect( - wrapper - .find('[data-testid="investigation-search-table-doi-link"]') - .first() - .prop('href') - ).toEqual('https://doi.org/doi 1'); + within( + findCellInRow(row, { + columnIndex: await findColumnIndexByName('investigations.end_date'), + }) + ).getByText('2019-06-11') + ).toBeInTheDocument(); }); - it('updates filter query params on text filter', () => { - const wrapper = createWrapper(); + it('displays DOI and renders the expected Link ', async () => { + renderComponent(); - const filterInput = wrapper - .find('[aria-label="Filter by investigations.title"]') - .last(); - filterInput.instance().value = 'test'; - filterInput.simulate('change'); + expect(await screen.findByRole('link', { name: 'doi 1' })).toHaveAttribute( + 'href', + 'https://doi.org/doi 1' + ); + }); - expect(history.length).toBe(2); + it('updates filter query params on text filter', async () => { + renderComponent(); + + const filterInput = await screen.findByRole('textbox', { + name: 'Filter by investigations.title', + hidden: true, + }); + + await user.type(filterInput, 'test'); + + // user.type inputs the given string character by character to simulate user typing + // each keystroke of user.type creates a new entry in the history stack + // so the initial entry + 4 characters in "test" = 5 entries + expect(history.length).toBe(5); expect(history.location.search).toBe( `?filters=${encodeURIComponent( '{"title":{"value":"test","type":"include"}}' )}` ); - filterInput.instance().value = ''; - filterInput.simulate('change'); + await user.clear(filterInput); - expect(history.length).toBe(3); + expect(history.length).toBe(6); expect(history.location.search).toBe('?'); }); - it('updates filter query params on date filter', () => { + it('updates filter query params on date filter', async () => { applyDatePickerWorkaround(); - const wrapper = createWrapper(); + renderComponent(); - const filterInput = wrapper.find( - 'input[id="investigations.end_date filter to"]' - ); - filterInput.instance().value = '2019-08-06'; - filterInput.simulate('change'); + const filterInput = await screen.findByRole('textbox', { + name: 'investigations.end_date filter to', + }); + + await user.type(filterInput, '2019-08-06'); expect(history.length).toBe(2); expect(history.location.search).toBe( `?filters=${encodeURIComponent('{"endDate":{"endDate":"2019-08-06"}}')}` ); - filterInput.instance().value = ''; - filterInput.simulate('change'); + await user.clear(filterInput); expect(history.length).toBe(3); expect(history.location.search).toBe('?'); @@ -342,13 +447,12 @@ describe('Investigation Search Table component', () => { cleanupDatePickerWorkaround(); }); - it('updates sort query params on sort', () => { - const wrapper = createWrapper(); + it('updates sort query params on sort', async () => { + renderComponent(); - wrapper - .find('[role="columnheader"] span[role="button"]') - .first() - .simulate('click'); + await user.click( + await screen.findByRole('button', { name: 'investigations.title' }) + ); expect(history.length).toBe(2); expect(history.location.search).toBe( @@ -356,20 +460,22 @@ describe('Investigation Search Table component', () => { ); }); - it('calls addToCart mutate function on unchecked checkbox click', () => { + it('calls addToCart mutate function on unchecked checkbox click', async () => { const addToCart = jest.fn(); (useAddToCart as jest.Mock).mockReturnValue({ mutate: addToCart, loading: false, }); - const wrapper = createWrapper(); + renderComponent(); - wrapper.find('[aria-label="select row 0"]').last().simulate('click'); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 0' }) + ); expect(addToCart).toHaveBeenCalledWith([1]); }); - it('calls removeFromCart mutate function on checked checkbox click', () => { + it('calls removeFromCart mutate function on checked checkbox click', async () => { (useCart as jest.Mock).mockReturnValue({ data: [ { @@ -389,14 +495,16 @@ describe('Investigation Search Table component', () => { loading: false, }); - const wrapper = createWrapper(); + renderComponent(); - wrapper.find('[aria-label="select row 0"]').last().simulate('click'); + await user.click( + await screen.findByRole('checkbox', { name: 'select row 0' }) + ); expect(removeFromCart).toHaveBeenCalledWith([1]); }); - it('selected rows only considers relevant cart items', () => { + it('selected rows only considers relevant cart items', async () => { (useCart as jest.Mock).mockReturnValue({ data: [ { @@ -417,50 +525,51 @@ describe('Investigation Search Table component', () => { isLoading: false, }); - const wrapper = createWrapper(); + renderComponent(); - const selectAllCheckbox = wrapper - .find('[aria-label="select all rows"]') - .first(); + const selectAllCheckbox = await screen.findByRole('checkbox', { + name: 'select all rows', + }); - expect(selectAllCheckbox.prop('checked')).toEqual(false); - expect(selectAllCheckbox.prop('data-indeterminate')).toEqual(false); + expect(selectAllCheckbox).not.toBeChecked(); + expect(selectAllCheckbox).toHaveAttribute('data-indeterminate', 'false'); }); - it('no select all checkbox appears and no fetchAllIds sent if selectAllSetting is false', () => { + it('no select all checkbox appears and no fetchAllIds sent if selectAllSetting is false', async () => { state.dgsearch.selectAllSetting = false; - const wrapper = createWrapper(); + renderComponent(); - expect(useIds).toHaveBeenCalledWith( - 'investigation', - expect.anything(), - false - ); - expect(useIds).not.toHaveBeenCalledWith( - 'investigation', - expect.anything(), - true - ); - expect(wrapper.find('[aria-label="select all rows"]')).toHaveLength(0); + await waitFor(() => { + expect( + screen.queryByRole('checkbox', { name: 'select all rows' }) + ).toBeNull(); + }); }); - it('displays generic details panel when expanded', () => { - const wrapper = createWrapper(); - expect(wrapper.find(InvestigationDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); + it('displays generic details panel when expanded', async () => { + renderComponent(); + + const row = await findRowAt(0); + await user.click(within(row).getByRole('button', { name: 'Show details' })); - expect(wrapper.find(InvestigationDetailsPanel).exists()).toBeTruthy(); + expect( + await screen.findByTestId('investigation-details-panel') + ).toBeInTheDocument(); }); - it('displays correct details panel for ISIS when expanded', () => { - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeTruthy(); + it('displays correct details panel for ISIS when expanded', async () => { + renderComponent('isis'); + + const row = await findRowAt(0); + await user.click(within(row).getByRole('button', { name: 'Show details' })); + + expect( + await screen.findByTestId('isis-investigation-details-panel') + ).toBeInTheDocument(); }); - it('can navigate using the details panel for ISIS when there are facility cycles', () => { + it('can navigate using the details panel for ISIS when there are facility cycles', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -472,33 +581,65 @@ describe('Investigation Search Table component', () => { ], }); - const wrapper = createWrapper('isis'); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); - expect(wrapper.find(ISISInvestigationDetailsPanel).exists()).toBeTruthy(); + renderComponent('isis'); + + const row = await findRowAt(0); + await user.click(within(row).getByRole('button', { name: 'Show details' })); + + await user.click( + await screen.findByRole('tab', { + name: 'investigations.details.datasets', + }) + ); - wrapper.find('#investigation-datasets-tab').last().simulate('click'); expect(history.location.pathname).toBe( '/browse/instrument/3/facilityCycle/4/investigation/1/dataset' ); }); - it('displays correct details panel for DLS when expanded', () => { - const wrapper = createWrapper('dls'); - expect(wrapper.find(DLSVisitDetailsPanel).exists()).toBeFalsy(); - wrapper.find('[aria-label="Show details"]').last().simulate('click'); - expect(wrapper.find(DLSVisitDetailsPanel).exists()).toBeTruthy(); + it('displays correct details panel for DLS when expanded', async () => { + renderComponent('dls'); + + const row = await findRowAt(0); + await user.click(within(row).getByRole('button', { name: 'Show details' })); + + expect( + await screen.findByTestId('visit-details-panel') + ).toBeInTheDocument(); }); - it('renders title and DOI as links', () => { - const wrapper = createRTLWrapper(); + it('renders title and DOI as links', async () => { + renderComponent(); - expect(wrapper.getByText('Test Title 1')).toMatchSnapshot(); + const titleColIndex = await findColumnIndexByName('investigations.title'); + const nameColIndex = await findColumnIndexByName('investigations.name'); + const doiColIndex = await findColumnIndexByName('investigations.doi'); + const visitIdColIndex = await findColumnIndexByName( + 'investigations.visit_id' + ); + + const row = await findRowAt(0); - expect(wrapper.getByText('doi 1')).toMatchSnapshot(); + const titleCell = await findCellInRow(row, { columnIndex: titleColIndex }); + const nameCell = await findCellInRow(row, { + columnIndex: nameColIndex, + }); + const doiCell = await findCellInRow(row, { columnIndex: doiColIndex }); + const visitIdCell = await findCellInRow(row, { + columnIndex: visitIdColIndex, + }); + + expect( + within(titleCell).getByRole('link', { name: 'Test Title 1' }) + ).toBeInTheDocument(); + expect(within(nameCell).getByText('Test Name 1')).toBeInTheDocument(); + expect( + within(doiCell).getByRole('link', { name: 'doi 1' }) + ).toBeInTheDocument(); + expect(within(visitIdCell).getByText('1')); }); - it('renders fine with incomplete data', () => { + it('renders fine with incomplete data', async () => { // this can happen when navigating between tables and the previous table's state still exists // also tests that empty arrays are fine for investigationInstruments rowData = [ @@ -517,35 +658,54 @@ describe('Investigation Search Table component', () => { fetchNextPage: jest.fn(), }); - expect(() => createWrapper()).not.toThrowError(); + renderComponent(); + + expect(await findAllRows()).toHaveLength(1); }); - it('renders generic link correctly & pending count correctly', () => { + it('renders generic link correctly & pending count correctly', async () => { (useInvestigationsDatasetCount as jest.Mock).mockImplementation(() => [ { isFetching: true, }, ]); - const wrapper = createWrapper('data'); + renderComponent('data'); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( - '/browse/investigation/1/dataset' + const titleColIndex = await findColumnIndexByName('investigations.title'); + const datafileCountColIndex = await findColumnIndexByName( + 'investigations.dataset_count' ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual('Test Title 1'); - expect(wrapper.find('[aria-colindex=7]').text()).toEqual('Calculating...'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { columnIndex: titleColIndex }); + const datafileCountCell = await findCellInRow(row, { + columnIndex: datafileCountColIndex, + }); + + expect( + within(titleCell).getByRole('link', { name: 'Test Title 1' }) + ).toHaveAttribute('href', '/browse/investigation/1/dataset'); + expect( + within(datafileCountCell).getByText('Calculating...') + ).toBeInTheDocument(); }); - it("renders DLS link correctly and doesn't allow for cart selection", () => { - const wrapper = createWrapper('dls'); + it("renders DLS link correctly and doesn't allow for cart selection", async () => { + renderComponent('dls'); + + const titleColIndex = await findColumnIndexByName('investigations.title'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { columnIndex: titleColIndex }); - expect(wrapper.find('[aria-colindex=2]').find('a').prop('href')).toEqual( + expect( + within(titleCell).getByRole('link', { name: 'Test Title 1' }) + ).toHaveAttribute( + 'href', '/browse/proposal/Test Name 1/investigation/1/dataset' ); - expect(wrapper.find('[aria-colindex=2]').text()).toEqual('Test Title 1'); - expect(wrapper.find('[aria-label="select row 0"]')).toHaveLength(0); + expect(screen.queryByRole('checkbox', { name: 'select row 0' })).toBeNull(); }); - it('renders ISIS link & file sizes correctly', () => { + it('renders ISIS link & file sizes correctly', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -557,19 +717,24 @@ describe('Investigation Search Table component', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); - expect(useInvestigationSizes).toHaveBeenCalledWith({ pages: [rowData] }); - expect(useInvestigationsDatasetCount).toHaveBeenCalledWith(undefined); + const titleColIndex = await findColumnIndexByName('investigations.title'); + const sizeColIndex = await findColumnIndexByName('investigations.size'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { columnIndex: titleColIndex }); + const sizeCell = await findCellInRow(row, { columnIndex: sizeColIndex }); - expect(wrapper.find('[aria-colindex=3]').find('a').prop('href')).toEqual( + expect( + within(titleCell).getByRole('link', { name: 'Test Title 1' }) + ).toHaveAttribute( + 'href', '/browse/instrument/3/facilityCycle/2/investigation/1/dataset' ); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual('Test Title 1'); - expect(wrapper.find('[aria-colindex=7]').text()).toEqual('1 B'); + expect(within(sizeCell).getByText('1 B')).toBeInTheDocument(); }); - it('does not render ISIS link when instrumentId cannot be found', () => { + it('does not render ISIS link when instrumentId cannot be found', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -586,20 +751,32 @@ describe('Investigation Search Table component', () => { data: { pages: [rowData] }, fetchNextPage: jest.fn(), }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); + + const titleColIndex = await findColumnIndexByName('investigations.title'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { columnIndex: titleColIndex }); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual('Test Title 1'); + expect( + within(titleCell).queryByRole('link', { name: 'Test Title 1' }) + ).toBeNull(); + expect(within(titleCell).getByText('Test Title 1')).toBeInTheDocument(); }); - it('does not render ISIS link when facilityCycleId cannot be found', () => { - const wrapper = createWrapper('isis'); + it('does not render ISIS link when facilityCycleId cannot be found', async () => { + renderComponent('isis'); + + const titleColIndex = await findColumnIndexByName('investigations.title'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { columnIndex: titleColIndex }); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual('Test Title 1'); + expect( + within(titleCell).queryByRole('link', { name: 'Test Title 1' }) + ).toBeNull(); + expect(within(titleCell).getByText('Test Title 1')).toBeInTheDocument(); }); - it('does not render ISIS link when facilityCycleId has incompatible dates', () => { + it('does not render ISIS link when facilityCycleId has incompatible dates', async () => { (useAllFacilityCycles as jest.Mock).mockReturnValue({ data: [ { @@ -611,9 +788,15 @@ describe('Investigation Search Table component', () => { ], }); - const wrapper = createWrapper('isis'); + renderComponent('isis'); + + const titleColIndex = await findColumnIndexByName('investigations.title'); + const row = await findRowAt(0); + const titleCell = await findCellInRow(row, { columnIndex: titleColIndex }); - expect(wrapper.find('[aria-colindex=3]').find('a')).toHaveLength(0); - expect(wrapper.find('[aria-colindex=3]').text()).toEqual('Test Title 1'); + expect( + within(titleCell).queryByRole('link', { name: 'Test Title 1' }) + ).toBeNull(); + expect(within(titleCell).getByText('Test Title 1')).toBeInTheDocument(); }); }); diff --git a/yarn.lock b/yarn.lock index a4326d762..3c5600177 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3019,7 +3019,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/jest-dom@npm:5.16.5": +"@testing-library/jest-dom@npm:5.16.5, @testing-library/jest-dom@npm:^5.16.5": version: 5.16.5 resolution: "@testing-library/jest-dom@npm:5.16.5" dependencies: @@ -3090,7 +3090,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/user-event@npm:14.4.3": +"@testing-library/user-event@npm:14.4.3, @testing-library/user-event@npm:^14.4.3": version: 14.4.3 resolution: "@testing-library/user-event@npm:14.4.3" peerDependencies: @@ -6391,7 +6391,6 @@ __metadata: "@testing-library/react": 12.1.3 "@testing-library/react-hooks": 8.0.1 "@testing-library/user-event": 14.4.1 - "@types/enzyme": 3.10.10 "@types/jest": 29.4.0 "@types/lodash.debounce": 4.0.6 "@types/node": 18.11.9 @@ -6400,15 +6399,12 @@ __metadata: "@types/react-virtualized": 9.21.10 "@typescript-eslint/eslint-plugin": 5.49.0 "@typescript-eslint/parser": 5.49.0 - "@wojtekmaj/enzyme-adapter-react-17": 0.6.6 axios: 0.27.2 babel-eslint: 10.1.0 connected-react-router: 6.9.1 date-fns: 2.29.1 - enzyme: 3.11.0 - enzyme-to-json: 3.6.1 eslint: 8.32.0 - eslint-config-prettier: 8.6.0 + eslint-config-prettier: 8.5.0 eslint-config-react-app: 7.0.0 eslint-plugin-cypress: 2.12.1 eslint-plugin-prettier: 4.2.1 @@ -6456,8 +6452,8 @@ __metadata: "@mui/material": 5.11.0 "@testing-library/jest-dom": 5.16.5 "@testing-library/react": 12.1.3 + "@testing-library/react-hooks": 8.0.1 "@testing-library/user-event": 14.4.3 - "@types/enzyme": 3.10.10 "@types/history": 4.7.11 "@types/jest": 29.4.0 "@types/jsrsasign": 10.5.2 @@ -6474,7 +6470,6 @@ __metadata: "@typescript-eslint/eslint-plugin": 5.49.0 "@typescript-eslint/parser": 5.49.0 "@welldone-software/why-did-you-render": 7.0.1 - "@wojtekmaj/enzyme-adapter-react-17": 0.6.6 axios: 0.27.2 babel-eslint: 10.1.0 blob-polyfill: 7.0.20220408 @@ -6485,8 +6480,6 @@ __metadata: cypress-failed-log: 2.10.0 datagateway-common: ^1.1.1 date-fns: 2.29.1 - enzyme: 3.11.0 - enzyme-to-json: 3.6.1 eslint: 8.32.0 eslint-config-prettier: 8.6.0 eslint-config-react-app: 7.0.0 @@ -6599,7 +6592,9 @@ __metadata: "@mui/icons-material": 5.11.0 "@mui/material": 5.11.0 "@mui/x-date-pickers": 5.0.9 + "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": 12.1.3 + "@testing-library/user-event": ^14.4.3 "@types/enzyme": 3.10.10 "@types/history": 4.7.11 "@types/jest": 29.4.0 @@ -7549,6 +7544,17 @@ __metadata: languageName: node linkType: hard +"eslint-config-prettier@npm:8.5.0": + version: 8.5.0 + resolution: "eslint-config-prettier@npm:8.5.0" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: 0d0f5c32e7a0ad91249467ce71ca92394ccd343178277d318baf32063b79ea90216f4c81d1065d60f96366fdc60f151d4d68ae7811a58bd37228b84c2083f893 + languageName: node + linkType: hard + "eslint-config-prettier@npm:8.6.0": version: 8.6.0 resolution: "eslint-config-prettier@npm:8.6.0"