diff --git a/src/components/DashboardsBar/NavigationMenu/NavigationMenu.js b/src/components/DashboardsBar/NavigationMenu/NavigationMenu.js index 8bcd94e1d..27373590f 100644 --- a/src/components/DashboardsBar/NavigationMenu/NavigationMenu.js +++ b/src/components/DashboardsBar/NavigationMenu/NavigationMenu.js @@ -67,7 +67,7 @@ export const NavigationMenu = () => {
{filteredDashboards.length === 0 ? ( -
  • +
  • {i18n.t('No dashboards found')}
  • ) : ( diff --git a/src/components/DashboardsBar/NavigationMenu/NavigationMenuItem.js b/src/components/DashboardsBar/NavigationMenu/NavigationMenuItem.js index b644b7391..20ea4c2f7 100644 --- a/src/components/DashboardsBar/NavigationMenu/NavigationMenuItem.js +++ b/src/components/DashboardsBar/NavigationMenu/NavigationMenuItem.js @@ -37,11 +37,17 @@ export const NavigationMenuItem = ({ displayName, id, starred }) => { key={id} label={ - {starred && } - {displayName} + {starred && ( + + )} + {displayName} {!!lastUpdated && } } + ariaLabel={displayName} className={id === selectedId ? styles.selectedItem : undefined} /> ) diff --git a/src/components/DashboardsBar/NavigationMenu/__tests__/NavigationMenu.spec.js b/src/components/DashboardsBar/NavigationMenu/__tests__/NavigationMenu.spec.js new file mode 100644 index 000000000..7dfb0a38d --- /dev/null +++ b/src/components/DashboardsBar/NavigationMenu/__tests__/NavigationMenu.spec.js @@ -0,0 +1,86 @@ +import { render } from '@testing-library/react' +import { createMemoryHistory } from 'history' +import React from 'react' +import { Provider } from 'react-redux' +import { Router } from 'react-router-dom' +import { createStore } from 'redux' +import { NavigationMenu } from '../NavigationMenu.js' + +jest.mock('../NavigationMenuItem.js', () => ({ + NavigationMenuItem: ({ displayName }) => ( +
  • {displayName}
  • + ), +})) +const baseState = { + dashboards: { + nghVC4wtyzi: { + id: 'nghVC4wtyzi', + displayName: 'Antenatal Care', + starred: true, + }, + rmPiJIPFL4U: { + displayName: 'Antenatal Care data', + id: 'rmPiJIPFL4U', + starred: false, + }, + JW7RlN5xafN: { + displayName: 'Cases Malaria', + id: 'JW7RlN5xafN', + starred: false, + }, + iMnYyBfSxmM: { + displayName: 'Delivery', + id: 'iMnYyBfSxmM', + starred: false, + }, + vqh4MBWOTi4: { + displayName: 'Disease Surveillance', + id: 'vqh4MBWOTi4', + starred: false, + }, + }, + dashboardsFilter: '', +} + +const createMockStore = (state) => + createStore(() => Object.assign({}, baseState, state)) + +test('renders a list of dashboard menu items', () => { + const mockStore = createMockStore({}) + const { getAllByRole } = render( + + + + + + ) + expect(getAllByRole('menu-item')).toHaveLength(5) +}) + +test('renders a notification if no dashboards are available', () => { + const mockStore = createMockStore({ dashboards: {} }) + const { getByText } = render( + + + + + + ) + + expect(getByText('No dashboards available.')).toBeTruthy() + expect(getByText('Create a new dashboard using the + button.')).toBeTruthy() +}) + +test('renders a placeholder list item if no dashboards meet the filter criteria', () => { + const filterStr = 'xxxxxxxxxxxxx' + const mockStore = createMockStore({ dashboardsFilter: filterStr }) + const { getByText, getByPlaceholderText } = render( + + + + + + ) + expect(getByPlaceholderText('Search for a dashboard').value).toBe(filterStr) + expect(getByText('No dashboards found')).toBeTruthy() +}) diff --git a/src/components/DashboardsBar/NavigationMenu/__tests__/NavigationMenuItem.spec.js b/src/components/DashboardsBar/NavigationMenu/__tests__/NavigationMenuItem.spec.js new file mode 100644 index 000000000..2c76bffa1 --- /dev/null +++ b/src/components/DashboardsBar/NavigationMenu/__tests__/NavigationMenuItem.spec.js @@ -0,0 +1,126 @@ +import { useCacheableSection } from '@dhis2/app-runtime' +import { render, fireEvent } from '@testing-library/react' +import { createMemoryHistory } from 'history' +import React from 'react' +import { Provider } from 'react-redux' +import { Router, useHistory } from 'react-router-dom' +import { createStore } from 'redux' +import { NavigationMenuItem } from '../NavigationMenuItem.js' + +jest.mock('@dhis2/app-runtime', () => ({ + useDhis2ConnectionStatus: () => ({ isConnected: true }), + useCacheableSection: jest.fn(), + useDataEngine: jest.fn(), +})) + +jest.mock('@dhis2/analytics', () => ({ + useCachedDataQuery: () => ({ + currentUser: { + username: 'rainbowDash', + id: 'r3nb0d5h', + }, + }), +})) + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: jest.fn(), +})) + +const mockOfflineDashboard = { + lastUpdated: 'Jan 10', +} + +const mockNonOfflineDashboard = { + lastUpdated: null, +} + +const defaultProps = { + starred: false, + displayName: 'Rainbow Dash', + id: 'rainbowdash', +} + +const selectedId = 'theselectedid' + +const defaultStoreFn = () => ({ + selected: { + id: selectedId, + }, +}) + +test('renders an inactive MenuItem for a dashboard', () => { + useCacheableSection.mockImplementation(() => mockNonOfflineDashboard) + const mockStore = createStore(defaultStoreFn) + const { container } = render( + + + + + + ) + expect(container.querySelector('.container').childNodes).toHaveLength(1) +}) + +test('renders an inactive MenuItem with a star icon, for a starred dashboard', () => { + useCacheableSection.mockImplementation(() => mockNonOfflineDashboard) + const mockStore = createStore(defaultStoreFn) + const { container, getByTestId } = render( + + + + + + ) + + expect(container.querySelector('.container').childNodes).toHaveLength(2) + expect(getByTestId('starred-dashboard')).toBeTruthy() +}) + +test('renders an inactive MenuItem with an offline icon for a cached dashboard', () => { + useCacheableSection.mockImplementation(() => mockOfflineDashboard) + const mockStore = createStore(defaultStoreFn) + const { getByTestId, container } = render( + + + + + + ) + expect(container.querySelector('.container').childNodes).toHaveLength(2) + expect(getByTestId('dashboard-saved-offline')).toBeTruthy() +}) + +test('renders an active MenuItem for the currently selected dashboard', () => { + useCacheableSection.mockImplementation(() => mockNonOfflineDashboard) + const mockStore = createStore(defaultStoreFn) + const { getByTestId, container } = render( + + + + + + ) + + expect(container.querySelector('.container').childNodes).toHaveLength(1) + expect(getByTestId('dhis2-uicore-menuitem')).toBeTruthy() +}) + +test('Navigates to the related menu item when an item is clicked', () => { + useCacheableSection.mockImplementation(() => mockNonOfflineDashboard) + const historyPushMock = jest.fn() + useHistory.mockImplementation(() => ({ + push: historyPushMock, + })) + const mockStore = createStore(defaultStoreFn) + const { getByText } = render( + + + + + + ) + fireEvent.click(getByText(defaultProps.displayName)) + expect(historyPushMock).toHaveBeenCalledTimes(1) + expect(historyPushMock).toHaveBeenCalledWith(`/${defaultProps.id}`) +}) diff --git a/src/components/DashboardsBar/__tests__/Chip.spec.js b/src/components/DashboardsBar/__tests__/Chip.spec.js deleted file mode 100644 index d9cbb2844..000000000 --- a/src/components/DashboardsBar/__tests__/Chip.spec.js +++ /dev/null @@ -1,141 +0,0 @@ -import { useCacheableSection } from '@dhis2/app-runtime' -import { render } from '@testing-library/react' -import { createMemoryHistory } from 'history' -import React from 'react' -import { Router } from 'react-router-dom' -import Chip from '../../DashboardsBar/Chip.js' - -/* eslint-disable react/prop-types */ -jest.mock('@dhis2/ui', () => { - const originalModule = jest.requireActual('@dhis2/ui') - - return { - __esModule: true, - ...originalModule, - Chip: function Mock({ children, icon, selected }) { - const componentProps = { - starred: icon ? 'yes' : 'no', - isselected: selected ? 'yes' : 'no', - } - - return ( -
    - {children} -
    - ) - }, - } -}) -/* eslint-enable react/prop-types */ - -jest.mock('@dhis2/app-runtime', () => ({ - useDhis2ConnectionStatus: () => ({ isConnected: true }), - useCacheableSection: jest.fn(), - useDataEngine: jest.fn(), -})) - -jest.mock('@dhis2/analytics', () => ({ - useCachedDataQuery: () => ({ - currentUser: { - username: 'rainbowDash', - id: 'r3nb0d5h', - }, - }), -})) - -const mockOfflineDashboard = { - lastUpdated: 'Jan 10', -} - -const mockNonOfflineDashboard = { - lastUpdated: null, -} - -const defaultProps = { - starred: false, - selected: false, - onClick: jest.fn(), - label: 'Rainbow Dash', - dashboardId: 'rainbowdash', - classes: { - icon: 'iconClass', - selected: 'selectedClass', - unselected: 'unselectedClass', - }, -} - -test('renders an unstarred, unselected chip for a non-cached dashboard', () => { - useCacheableSection.mockImplementation(() => mockNonOfflineDashboard) - const { container } = render( - - - - ) - - expect(container).toMatchSnapshot() -}) - -test('renders an unstarred, unselected chip for cached dashboard', () => { - useCacheableSection.mockImplementation(() => mockOfflineDashboard) - const { container } = render( - - - - ) - - expect(container).toMatchSnapshot() -}) - -test('renders a starred, unselected chip for a non-cached dashboard', () => { - useCacheableSection.mockImplementation(() => mockNonOfflineDashboard) - const props = Object.assign({}, defaultProps, { starred: true }) - const { container } = render( - - - - ) - - expect(container).toMatchSnapshot() -}) - -test('renders a starred, unselected chip for a cached dashboard', () => { - useCacheableSection.mockImplementation(() => mockOfflineDashboard) - const props = Object.assign({}, defaultProps, { starred: true }) - const { container } = render( - - - - ) - - expect(container).toMatchSnapshot() -}) - -test('renders a starred, selected chip for non-cached dashboard', () => { - useCacheableSection.mockImplementation(() => mockNonOfflineDashboard) - const props = Object.assign({}, defaultProps, { - starred: true, - selected: true, - }) - const { container } = render( - - - - ) - - expect(container).toMatchSnapshot() -}) - -test('renders a starred, selected chip for a cached dashboard', () => { - useCacheableSection.mockImplementation(() => mockOfflineDashboard) - const props = Object.assign({}, defaultProps, { - starred: true, - selected: true, - }) - const { container } = render( - - - - ) - - expect(container).toMatchSnapshot() -}) diff --git a/src/components/DashboardsBar/__tests__/ClearButton.spec.js b/src/components/DashboardsBar/__tests__/ClearButton.spec.js deleted file mode 100644 index 86f7c3014..000000000 --- a/src/components/DashboardsBar/__tests__/ClearButton.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render } from '@testing-library/react' -import React from 'react' -import ClearButton from '../../DashboardsBar/ClearButton.js' - -test('ClearButton renders a button', () => { - const { container } = render() - expect(container).toMatchSnapshot() -}) diff --git a/src/components/IconOfflineSaved.js b/src/components/IconOfflineSaved.js index 138d8df2b..f94b9766d 100644 --- a/src/components/IconOfflineSaved.js +++ b/src/components/IconOfflineSaved.js @@ -6,6 +6,7 @@ export const IconOfflineSaved = () => ( viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" + data-test="dashboard-saved-offline" >