diff --git a/components/webfield/AreaChairConsole.js b/components/webfield/AreaChairConsole.js index 341adc160..1d0a691cc 100644 --- a/components/webfield/AreaChairConsole.js +++ b/components/webfield/AreaChairConsole.js @@ -4,7 +4,6 @@ import { useContext, useEffect, useState } from 'react' import { useRouter } from 'next/router' import WebFieldContext from '../WebFieldContext' import BasicHeader from './BasicHeader' -import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs' import Table from '../Table' import ErrorDisplay from '../ErrorDisplay' import NoteSummary from './NoteSummary' @@ -31,6 +30,7 @@ import LoadingSpinner from '../LoadingSpinner' import ConsoleTaskList from './ConsoleTaskList' import { getProfileLink } from '../../lib/webfield-utils' import { formatProfileContent } from '../../lib/edge-utils' +import ConsoleTabs from './ConsoleTabs' const SelectAllCheckBox = ({ selectedNoteIds, setSelectedNoteIds, allNoteIds }) => { const allNotesSelected = selectedNoteIds.length === allNoteIds?.length @@ -181,9 +181,7 @@ const AreaChairConsole = ({ appContext }) => { const { setBannerContent } = appContext const [acConsoleData, setAcConsoleData] = useState({}) const [selectedNoteIds, setSelectedNoteIds] = useState([]) - const [activeTabId, setActiveTabId] = useState( - decodeURIComponent(window.location.hash) || `#assigned-${pluralizeString(submissionName)}` - ) + const [activeTabId, setActiveTabId] = useState(null) const [sacLinkText, setSacLinkText] = useState('') const edgeBrowserUrl = proposedAssignmentTitle @@ -789,19 +787,6 @@ const AreaChairConsole = ({ appContext }) => { getSACLinkText() }, [acConsoleData.sacProfiles]) - useEffect(() => { - const validTabIds = [ - `#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`, - ...(secondaryAreaChairName ? [`#${secondaryAreaChairUrlFormat}-assignments`] : []), - `#${areaChairUrlFormat}-tasks`, - ] - if (!validTabIds.includes(activeTabId)) { - setActiveTabId(`#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`) - return - } - router.replace(activeTabId) - }, [activeTabId]) - const missingConfig = Object.entries({ header, group, @@ -834,62 +819,30 @@ const AreaChairConsole = ({ appContext }) => { title={header?.title} instructions={`${headerInstructions}${sacLinkText}`} /> - - - - - setActiveTabId(`#assigned-${pluralizeString(submissionName).toLowerCase()}`) - } - > - Assigned {pluralizeString(submissionName)} - - {secondaryAreaChairName && ( - setActiveTabId(`#${secondaryAreaChairUrlFormat}-assignments`)} - > - {getSingularRoleName(prettyField(secondaryAreaChairName))} Assignments - - )} - setActiveTabId(`#${areaChairUrlFormat}-tasks`)} - > - {getSingularRoleName(prettyField(areaChairName))} Tasks - - - - - - {activeTabId === `#assigned-${pluralizeString(submissionName).toLowerCase()}` && - renderTable()} - - {secondaryAreaChairName && ( - - {activeTabId === `#${secondaryAreaChairUrlFormat}-assignments` && - renderTripletACTable()} - - )} - - {activeTabId === `#${areaChairUrlFormat}-tasks` && ( - - )} - - - + , + visible: true, + }, + ]} + updateActiveTabId={setActiveTabId} + /> ) } diff --git a/components/webfield/ConsoleTabs.js b/components/webfield/ConsoleTabs.js new file mode 100644 index 000000000..95a104189 --- /dev/null +++ b/components/webfield/ConsoleTabs.js @@ -0,0 +1,61 @@ +import { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs' + +const ConsoleTabs = ({ defaultActiveTabId, tabs = [], updateActiveTabId }) => { + const validTabIds = tabs.flatMap((tab) => (tab.visible ? tab.id : [])) + const [activeTabId, setActiveTabId] = useState( + decodeURIComponent(window.location.hash.substring(1)) || + defaultActiveTabId || + validTabIds[0] + ) + const router = useRouter() + + useEffect(() => { + if (!validTabIds.includes(activeTabId)) { + setActiveTabId(defaultActiveTabId) + updateActiveTabId?.(`#${defaultActiveTabId}`) + return + } + updateActiveTabId?.(`#${activeTabId}`) + router.replace(`#${activeTabId}`).catch((e) => { + if (!e.cancelled) { + throw e + } + }) + }, [activeTabId]) + + return ( + + + {tabs.map((tab) => { + const { id, label, visible } = tab + if (!visible) return null + return ( + setActiveTabId(id)} + > + {label} + + ) + })} + + + {tabs.map((tab) => { + const { id, content, visible } = tab + if (!visible || activeTabId !== `${id}`) return null + return ( + + {content} + + ) + })} + + + ) +} + +export default ConsoleTabs diff --git a/components/webfield/EthicsChairConsole.js b/components/webfield/EthicsChairConsole.js index f1199211a..1c333ea1b 100644 --- a/components/webfield/EthicsChairConsole.js +++ b/components/webfield/EthicsChairConsole.js @@ -1,9 +1,8 @@ /* globals promptError: false */ -import { useContext, useEffect, useState } from 'react' +import { useContext, useEffect } from 'react' import { useRouter } from 'next/router' import useUser from '../../hooks/useUser' import useQuery from '../../hooks/useQuery' -import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs' import { referrerLink, venueHomepageLink } from '../../lib/banner-links' import WebFieldContext from '../WebFieldContext' import BasicHeader from './BasicHeader' @@ -12,6 +11,7 @@ import EthicsChairOverview from './EthicsChairConsole/EthicsChairOverview' import PaperStatus from './EthicsChairConsole/EthicsChairPaperStatus' import EthicsChairTasks from './EthicsChairConsole/EthicsChairTasks' import { getRoleHashFragment } from '../../lib/utils' +import ConsoleTabs from './ConsoleTabs' const EthicsChairConsole = ({ appContext }) => { const { @@ -31,17 +31,9 @@ const EthicsChairConsole = ({ appContext }) => { const { setBannerContent } = appContext const router = useRouter() const query = useQuery() - const [activeTabId, setActiveTabId] = useState( - decodeURIComponent(window.location.hash) || '#overview' - ) const { user, userLoading } = useUser() const ethicsChairsUrlFormat = getRoleHashFragment(ethicsChairsName) - const validTabIds = [ - '#overview', - `#${submissionName.toLowerCase()}-status`, - `#${ethicsChairsUrlFormat}-tasks`, - ] useEffect(() => { if (!query) return @@ -53,14 +45,6 @@ const EthicsChairConsole = ({ appContext }) => { } }, [query, venueId]) - useEffect(() => { - if (!validTabIds.includes(activeTabId)) { - setActiveTabId(validTabIds[0]) - return - } - router.replace(activeTabId) - }, [activeTabId]) - const missingConfig = Object.entries({ header, entity: group, @@ -85,45 +69,29 @@ const EthicsChairConsole = ({ appContext }) => { return ( <> - - - setActiveTabId('#overview')} - > - Overview - - setActiveTabId(`#${submissionName.toLowerCase()}-status`)} - > - {submissionName} Status - - setActiveTabId(`#${ethicsChairsUrlFormat}-tasks`)} - > - Ethics Chair Tasks - - - - - - - - - {activeTabId === `#${submissionName.toLowerCase()}-status` && } - - - {activeTabId === `#${ethicsChairsUrlFormat}-tasks` && } - - - + , + visible: true, + }, + { + id: `${submissionName.toLowerCase()}-status`, + label: `${submissionName} Status`, + content: , + visible: true, + }, + { + id: `${ethicsChairsUrlFormat}-tasks`, + label: 'Ethics Chair Tasks', + content: , + visible: true, + }, + ]} + /> ) } diff --git a/components/webfield/ProgramChairConsole.js b/components/webfield/ProgramChairConsole.js index a760fd064..4c95e51e4 100644 --- a/components/webfield/ProgramChairConsole.js +++ b/components/webfield/ProgramChairConsole.js @@ -1,10 +1,8 @@ /* globals promptError: false */ import { useContext, useEffect, useState } from 'react' -import { useRouter } from 'next/router' import groupBy from 'lodash/groupBy' import useUser from '../../hooks/useUser' import useQuery from '../../hooks/useQuery' -import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs' import { referrerLink, venueHomepageLink } from '../../lib/banner-links' import api from '../../lib/api-client' import WebFieldContext from '../WebFieldContext' @@ -29,6 +27,7 @@ import ReviewerStatusTab from './ProgramChairConsole/ReviewerStatus' import ErrorDisplay from '../ErrorDisplay' import RejectedWithdrawnPapers from './ProgramChairConsole/RejectedWithdrawnPapers' import { formatProfileContent } from '../../lib/edge-utils' +import ConsoleTabs from './ConsoleTabs' const ProgramChairConsole = ({ appContext, extraTabs = [] }) => { const { @@ -88,11 +87,7 @@ const ProgramChairConsole = ({ appContext, extraTabs = [] }) => { } = useContext(WebFieldContext) const { setBannerContent } = appContext const { user, accessToken, userLoading } = useUser() - const router = useRouter() const query = useQuery() - const [activeTabId, setActiveTabId] = useState( - decodeURIComponent(window.location.hash) || '#venue-configuration' - ) const [pcConsoleData, setPcConsoleData] = useState({}) const [isLoadingData, setIsLoadingData] = useState(false) @@ -1034,31 +1029,6 @@ const ProgramChairConsole = ({ appContext, extraTabs = [] }) => { loadData() }, [user, userLoading, group]) - useEffect(() => { - const validTabIds = [ - '#venue-configuration', - `#${submissionName.toLowerCase()}-status`, - `#${reviewerUrlFormat}-status`, - `#${areaChairUrlFormat}-status`, - `#${seniorAreaChairUrlFormat}-status`, - '#deskrejectwithdrawn-status', - ] - - if (submissionContentFields.length > 0) { - submissionContentFields.forEach((fieldAttrs) => validTabIds.push(`#${fieldAttrs.field}`)) - } - - if (extraTabs.length > 0) { - extraTabs.forEach((tabAttrs) => validTabIds.push(`#${tabAttrs.tabId}`)) - } - - if (!validTabIds.includes(activeTabId)) { - setActiveTabId('#venue-configuration') - return - } - router.replace(activeTabId) - }, [activeTabId]) - const missingConfig = Object.entries({ header, entity: group, @@ -1087,146 +1057,93 @@ const ProgramChairConsole = ({ appContext, extraTabs = [] }) => { return ( <> - - - setActiveTabId('#venue-configuration')} - > - Overview - - setActiveTabId(`#${submissionName.toLowerCase()}-status`)} - > - {submissionName} Status - - setActiveTabId(`#${reviewerUrlFormat}-status`)} - > - {getSingularRoleName(prettyField(reviewerName))} Status - - {areaChairsId && ( - setActiveTabId(`#${areaChairUrlFormat}-status`)} - > - {getSingularRoleName(prettyField(areaChairName))} Status - - )} - {seniorAreaChairsId && ( - setActiveTabId(`#${seniorAreaChairUrlFormat}-status`)} - > - {getSingularRoleName(prettyField(seniorAreaChairName))} Status - - )} - {(withdrawnVenueId || deskRejectedVenueId) && ( - setActiveTabId('#deskrejectwithdrawn-status')} - > - Desk Rejected/Withdrawn {pluralizeString(submissionName)} - - )} - {submissionContentFields.length > 0 && - submissionContentFields.map((fieldAttrs) => ( - setActiveTabId(`#${fieldAttrs.field}`)} - > - {prettyField(fieldAttrs.field)} - - ))} - {extraTabs.length > 0 && - extraTabs.map((tabAttrs) => ( - setActiveTabId(`#${tabAttrs.tabId}`)} - > - {tabAttrs.tabName} - - ))} - - - - - - - - {activeTabId === `#${submissionName.toLowerCase()}-status` && ( + , + visible: true, + }, + { + id: `${submissionName.toLowerCase()}-status`, + label: `${submissionName} Status`, + content: ( - )} - - - - - {areaChairsId && activeTabId === `#${areaChairUrlFormat}-status` && ( - + ), + visible: true, + }, + { + id: `${reviewerUrlFormat}-status`, + label: `${getSingularRoleName(prettyField(reviewerName))} Status`, + content: ( + + ), + visible: true, + }, + { + id: `${areaChairUrlFormat}-status`, + label: `${getSingularRoleName(prettyField(areaChairName))} Status`, + content: ( - - )} - {seniorAreaChairsId && activeTabId === `#${seniorAreaChairUrlFormat}-status` && ( - + ), + visible: areaChairsId, + }, + { + id: `${seniorAreaChairUrlFormat}-status`, + label: `${getSingularRoleName(prettyField(seniorAreaChairName))} Status`, + content: ( - - )} - - {activeTabId === '#deskrejectwithdrawn-status' && ( - - )} - - {submissionContentFields.length > 0 && - submissionContentFields.map((fieldAttrs) => ( - - {activeTabId === `#${fieldAttrs.field}` && ( + ), + visible: seniorAreaChairsId, + }, + { + id: 'deskrejectwithdrawn-status', + label: `Desk Rejected/Withdrawn ${pluralizeString(submissionName)}`, + content: , + visible: withdrawnVenueId || deskRejectedVenueId, + }, + ...(submissionContentFields.length > 0 + ? submissionContentFields.map((fieldAttrs) => ({ + id: fieldAttrs.field, + label: prettyField(fieldAttrs.field), + content: ( - )} - - ))} - {extraTabs.length > 0 && - extraTabs.map((tabAttrs) => ( - - {activeTabId === `#${tabAttrs.tabId}` && tabAttrs.renderTab()} - - ))} - - + ), + visible: true, + })) + : []), + ...(extraTabs.length > 0 + ? extraTabs.map((tabAttrs) => ({ + id: tabAttrs.tabId, + label: tabAttrs.tabName, + content: tabAttrs.renderTab(), + visible: true, + })) + : []), + ]} + /> ) } diff --git a/components/webfield/ProgramChairConsole/ReviewerStatus.js b/components/webfield/ProgramChairConsole/ReviewerStatus.js index 286807573..464d2f7b3 100644 --- a/components/webfield/ProgramChairConsole/ReviewerStatus.js +++ b/components/webfield/ProgramChairConsole/ReviewerStatus.js @@ -292,7 +292,6 @@ const ReviewerStatusTab = ({ pcConsoleData, loadReviewMetaReviewData, loadRegistrationNoteMap, - showContent, }) => { const [reviewerStatusTabData, setReviewerStatusTabData] = useState({}) const { @@ -456,7 +455,7 @@ const ReviewerStatusTab = ({ } useEffect(() => { - if (!pcConsoleData.reviewers || !showContent) return + if (!pcConsoleData.reviewers) return if (!pcConsoleData.registrationNoteMap) { loadRegistrationNoteMap() } else { @@ -466,7 +465,6 @@ const ReviewerStatusTab = ({ pcConsoleData.reviewers, pcConsoleData.noteNumberReviewMetaReviewMap, pcConsoleData.registrationNoteMap, - showContent, ]) useEffect(() => { diff --git a/components/webfield/ReviewerConsole.js b/components/webfield/ReviewerConsole.js index 33a4eb723..b183f6015 100644 --- a/components/webfield/ReviewerConsole.js +++ b/components/webfield/ReviewerConsole.js @@ -5,7 +5,6 @@ import Link from 'next/link' import { chunk } from 'lodash' import api from '../../lib/api-client' import Table from '../Table' -import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs' import WebFieldContext from '../WebFieldContext' import BasicHeader from './BasicHeader' import { ReviewerConsoleNoteReviewStatus } from './NoteReviewStatus' @@ -28,6 +27,7 @@ import ReviewerConsoleMenuBar from './ReviewerConsoleMenuBar' import LoadingSpinner from '../LoadingSpinner' import ConsoleTaskList from './ConsoleTaskList' import { getProfileLink } from '../../lib/webfield-utils' +import ConsoleTabs from './ConsoleTabs' const AreaChairInfo = ({ areaChairName, areaChairIds }) => (
@@ -284,10 +284,7 @@ const ReviewerConsole = ({ appContext }) => { const { setBannerContent } = appContext const [reviewerConsoleData, setReviewerConsoleData] = useState({}) const [enablePaperRanking, setEnablePaperRanking] = useState(true) - const [activeTabId, setActiveTabId] = useState( - decodeURIComponent(window.location.hash) || - `#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}` - ) + const [activeTabId, setActiveTabId] = useState(null) const paperRankingId = `${venueId}/${reviewerName}/-/Paper_Ranking` const reviewerUrlFormat = reviewerName ? getRoleHashFragment(reviewerName) : null @@ -603,20 +600,6 @@ const ReviewerConsole = ({ appContext }) => { } }, [reviewerConsoleData.notes]) - useEffect(() => { - if (user && !userLoading) { - const validTabIds = [ - `#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`, - `#${reviewerUrlFormat}-tasks`, - ] - if (!validTabIds.includes(activeTabId)) { - setActiveTabId(`#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`) - return - } - router.replace(activeTabId) - } - }, [activeTabId, user, userLoading]) - const missingConfig = Object.entries({ header, group, @@ -651,48 +634,31 @@ const ReviewerConsole = ({ appContext }) => { customLoad={reviewerConsoleData.customLoad} submissionName={submissionName} /> - - - - setActiveTabId(`#assigned-${pluralizeString(submissionName).toLowerCase()}`) - } - > - Assigned {pluralizeString(submissionName)} - - setActiveTabId(`#${reviewerUrlFormat}-tasks`)} - > - {getSingularRoleName(prettyField(reviewerName))} Tasks - - - - - - {activeTabId === `#assigned-${pluralizeString(submissionName).toLowerCase()}` && - renderTable()} - - - - {activeTabId === `#${reviewerUrlFormat}-tasks` && ( + - )} - - - + ), + visible: true, + }, + ]} + updateActiveTabId={setActiveTabId} + /> ) } diff --git a/components/webfield/SeniorAreaChairConsole.js b/components/webfield/SeniorAreaChairConsole.js index 9615d289a..fc9db079f 100644 --- a/components/webfield/SeniorAreaChairConsole.js +++ b/components/webfield/SeniorAreaChairConsole.js @@ -2,7 +2,6 @@ import { useContext, useEffect, useState } from 'react' import { useRouter } from 'next/router' import WebFieldContext from '../WebFieldContext' -import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs' import BasicHeader from './BasicHeader' import AreaChairStatus from './SeniorAreaChairConsole/AreaChairStatus' import PaperStatus from './SeniorAreaChairConsole/PaperStatus' @@ -25,6 +24,7 @@ import { } from '../../lib/utils' import { formatProfileContent } from '../../lib/edge-utils' import RejectedWithdrawnPapers from './ProgramChairConsole/RejectedWithdrawnPapers' +import ConsoleTabs from './ConsoleTabs' const SeniorAreaChairConsole = ({ appContext }) => { const { @@ -68,9 +68,6 @@ const SeniorAreaChairConsole = ({ appContext }) => { const [isLoadingData, setIsLoadingData] = useState(false) const router = useRouter() const query = useQuery() - const [activeTabId, setActiveTabId] = useState( - decodeURIComponent(window.location.hash) || `#${submissionName ?? ''.toLowerCase()}-status` - ) const seniorAreaChairUrlFormat = getRoleHashFragment(seniorAreaChairName) const areaChairUrlFormat = getRoleHashFragment(areaChairName) @@ -657,21 +654,6 @@ const SeniorAreaChairConsole = ({ appContext }) => { loadData() }, [user, userLoading, group]) - useEffect(() => { - // if (!activeTabId) return - const validTabIds = [ - `#${(submissionName ?? '').toLowerCase()}-status`, - `#${areaChairUrlFormat}-status`, - '#deskrejectwithdrawn-status', - `#${seniorAreaChairUrlFormat}-tasks`, - ] - if (!validTabIds.includes(activeTabId)) { - setActiveTabId(`#${(submissionName ?? '').toLowerCase()}-status`) - return - } - router.replace(activeTabId) - }, [activeTabId]) - const missingConfig = Object.entries({ header, entity: group, @@ -693,72 +675,43 @@ const SeniorAreaChairConsole = ({ appContext }) => { return ( <> - - - - setActiveTabId(`#${submissionName.toLowerCase()}-status`)} - > - {submissionName} Status - - setActiveTabId(`#${areaChairUrlFormat}-status`)} - hidden={!assignmentInvitation} - > - {getSingularRoleName(prettyField(areaChairName))} Status - - {(withdrawnVenueId || deskRejectedVenueId) && ( - setActiveTabId('#deskrejectwithdrawn-status')} - > - Desk Rejected/Withdrawn {pluralizeString(submissionName)} - - )} - setActiveTabId(`#${seniorAreaChairUrlFormat}-tasks`)} - > - {getSingularRoleName(prettyField(seniorAreaChairName))} Tasks - - - - - - {activeTabId === `#${submissionName.toLowerCase()}-status` && ( - - )} - - {activeTabId === `#${areaChairUrlFormat}-status` && ( - + , + visible: true, + }, + { + id: `${areaChairUrlFormat}-status`, + label: `${getSingularRoleName(prettyField(areaChairName))} Status`, + content: ( - - )} - {activeTabId === '#deskrejectwithdrawn-status' && ( - + ), + visible: assignmentInvitation, + }, + { + id: 'deskrejectwithdrawn-status', + label: `Desk Rejected/Withdrawn ${pluralizeString(submissionName)}`, + content: ( - - )} - - {activeTabId === `#${seniorAreaChairUrlFormat}-tasks` && ( - - - - )} - - + ), + visible: withdrawnVenueId || deskRejectedVenueId, + }, + { + id: `${seniorAreaChairUrlFormat}-tasks`, + label: `${getSingularRoleName(prettyField(seniorAreaChairName))} Tasks`, + content: , + visible: true, + }, + ]} + /> ) } diff --git a/lib/utils.js b/lib/utils.js index d69802d56..ce99fbccb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -662,7 +662,7 @@ export function pluralizeString(word) { * @param {string} roleName - role name to convert */ export function getSingularRoleName(roleName) { - return roleName.endsWith('s') ? roleName.slice(0, -1) : roleName + return roleName?.endsWith('s') ? roleName.slice(0, -1) : roleName } /** diff --git a/unitTests/AreaChairConsole.test.js b/unitTests/AreaChairConsole.test.js index 34f67a4af..931b3f695 100644 --- a/unitTests/AreaChairConsole.test.js +++ b/unitTests/AreaChairConsole.test.js @@ -12,10 +12,12 @@ let noteReviewStatusProps jest.mock('next/router', () => ({ useRouter: () => ({ - replace: (params) => { + replace: jest.fn((params) => { routerParams = params - return jest.fn() - }, + return { + catch: jest.fn(), + } + }), }), })) jest.mock('../hooks/useUser', () => () => useUserReturnValue) @@ -45,25 +47,6 @@ beforeEach(() => { }) describe('AreaChairConsole', () => { - test('default to assigned papers tab when window.location does not contain any hash', async () => { - const providerProps = { value: { submissionName: 'Submissions' } } - renderWithWebFieldContext( - , - providerProps - ) - expect(routerParams).toEqual('#assigned-submissions') - }) - - test('default to assigned papers tab when window.location.hash does not match any tab', async () => { - window.location.hash = '#some-unknown-tab' - const providerProps = { value: { submissionName: 'Submissions' } } - renderWithWebFieldContext( - , - providerProps - ) - expect(routerParams).toEqual('#assigned-submissions') - }) - test('show error when config is not complete', async () => { const providerProps = { value: { areaChairName: undefined } } const { rerender } = renderWithWebFieldContext( diff --git a/unitTests/ConsoleTabs.test.js b/unitTests/ConsoleTabs.test.js new file mode 100644 index 000000000..e51c48d12 --- /dev/null +++ b/unitTests/ConsoleTabs.test.js @@ -0,0 +1,110 @@ +import '@testing-library/jest-dom' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import ConsoleTabs from '../components/webfield/ConsoleTabs' + +let routerParams + +jest.mock('next/router', () => ({ + useRouter: () => ({ + replace: jest.fn((params) => { + routerParams = params + return { + catch: jest.fn(), + } + }), + }), +})) + +beforeEach(() => { + routerParams = null + window.location.hash = '' +}) + +describe('ConsoleTabs', () => { + test('render first tab by default', () => { + const tabs = [ + { id: 'tab1Id', label: 'Tab One', visible: true, content: tab 1 content }, + { id: 'tab2Id', label: 'Tab Two', visible: true, content: tab 2 content }, + { id: 'tab3Id', label: 'Tab Three', visible: true, content: tab 3 content }, + ] + render() + + expect(screen.getAllByRole('tab').length).toBe(3) + expect(screen.getAllByRole('tabpanel').length).toBe(1) + expect(screen.getByText('tab 1 content')).toBeInTheDocument() + }) + + test('render specified default tab in properties', () => { + const tabs = [ + { id: 'tab1Id', label: 'Tab One', visible: true, content: tab 1 content }, + { id: 'tab2Id', label: 'Tab Two', visible: true, content: tab 2 content }, + { id: 'tab3Id', label: 'Tab Three', visible: true, content: tab 3 content }, + ] + render() + + expect(screen.getAllByRole('tab').length).toBe(3) + expect(screen.getAllByRole('tabpanel').length).toBe(1) + expect(screen.getByText('tab 3 content')).toBeInTheDocument() + }) + + test('render tab specified in url', () => { + window.location.hash = '#tab2Id' // higher priority than defaultActiveTabId + const tabs = [ + { id: 'tab1Id', label: 'Tab One', visible: true, content: tab 1 content }, + { id: 'tab2Id', label: 'Tab Two', visible: true, content: tab 2 content }, + { id: 'tab3Id', label: 'Tab Three', visible: true, content: tab 3 content }, + ] + render() + + expect(screen.getAllByRole('tab').length).toBe(3) + expect(screen.getAllByRole('tabpanel').length).toBe(1) + expect(screen.getByText('tab 2 content')).toBeInTheDocument() + }) + + test('render only visible tabs', () => { + const tabs = [ + { id: 'tab1Id', label: 'Tab One', visible: false, content: tab 1 content }, // first tab is invisible + { id: 'tab2Id', label: 'Tab Two', visible: true, content: tab 2 content }, // tab 2 becomes first tab + { id: 'tab3Id', label: 'Tab Three', visible: true, content: tab 3 content }, + ] + render() + + expect(screen.getAllByRole('tab').length).toBe(2) + expect(screen.getAllByRole('tabpanel').length).toBe(1) + expect(screen.getByText('tab 2 content')).toBeInTheDocument() + }) + + test('handle invalid hash in url', () => { + window.location.hash = '#invalidTabId' + const tabs = [ + { id: 'tab1Id', label: 'Tab One', visible: true, content: tab 1 content }, + { id: 'tab2Id', label: 'Tab Two', visible: true, content: tab 2 content }, + { id: 'tab3Id', label: 'Tab Three', visible: true, content: tab 3 content }, + ] + render() + + expect(screen.getAllByRole('tab').length).toBe(3) + expect(screen.getAllByRole('tabpanel').length).toBe(1) + expect(screen.getByText('tab 1 content')).toBeInTheDocument() + }) + + test('switch tab and pass active tab id to parent', async () => { + const tabs = [ + { id: 'tab1Id', label: 'Tab One', visible: true, content: tab 1 content }, + { id: 'tab2Id', label: 'Tab Two', visible: true, content: tab 2 content }, + { id: 'tab3Id', label: 'Tab Three', visible: true, content: tab 3 content }, + ] + render() + + expect(screen.getByText('tab 1 content')).toBeInTheDocument() + + await userEvent.click(screen.getByText('Tab Two')) + expect(screen.getByText('tab 2 content')).toBeInTheDocument() + expect(routerParams).toBe('#tab2Id') + + await userEvent.click(screen.getByText('Tab Three')) + expect(screen.getByText('tab 3 content')).toBeInTheDocument() + expect(routerParams).toBe('#tab3Id') + }) +}) diff --git a/unitTests/ReviewerConsole.test.js b/unitTests/ReviewerConsole.test.js index 0e5930eb2..7701f8962 100644 --- a/unitTests/ReviewerConsole.test.js +++ b/unitTests/ReviewerConsole.test.js @@ -11,10 +11,12 @@ let noteReviewStatusProps jest.mock('next/router', () => ({ useRouter: () => ({ - replace: (params) => { + replace: jest.fn((params) => { routerParams = params - return jest.fn() - }, + return { + catch: jest.fn(), + } + }), }), })) jest.mock('../hooks/useUser', () => () => useUserReturnValue) diff --git a/unitTests/SeniorAreaChairConsole.test.js b/unitTests/SeniorAreaChairConsole.test.js index 19f5e81d2..ee20b99ab 100644 --- a/unitTests/SeniorAreaChairConsole.test.js +++ b/unitTests/SeniorAreaChairConsole.test.js @@ -1,7 +1,5 @@ -import { screen, waitFor } from '@testing-library/react' +import { screen } from '@testing-library/react' import '@testing-library/jest-dom' -import userEvent from '@testing-library/user-event' -import api from '../lib/api-client' import { reRenderWithWebFieldContext, renderWithWebFieldContext } from './util' import SeniorAreaChairConsole from '../components/webfield/SeniorAreaChairConsole' @@ -14,10 +12,12 @@ let sacTasksProps jest.mock('nanoid', () => ({ nanoid: () => 'some id' })) jest.mock('next/router', () => ({ useRouter: () => ({ - replace: (params) => { + replace: jest.fn((params) => { routerParams = params - return jest.fn() - }, + return { + catch: jest.fn(), + } + }), }), })) jest.mock('../hooks/useUser', () => () => useUserReturnValue) @@ -53,25 +53,6 @@ beforeEach(() => { }) describe('SeniorAreaChairConsole', () => { - test('default to paper status tab when window.location does not contain any hash', async () => { - const providerProps = { value: { submissionName: 'Submission' } } - renderWithWebFieldContext( - , - providerProps - ) - expect(routerParams).toEqual('#submission-status') - }) - - test('default to assigned papers tab when window.location.hash does not match any tab', async () => { - window.location.hash = '#some-unknown-tab' - const providerProps = { value: { submissionName: 'Submission' } } - renderWithWebFieldContext( - , - providerProps - ) - expect(routerParams).toEqual('#submission-status') - }) - test('show error message based on sac name when config is not complete', async () => { const providerProps = { value: { seniorAreaChairName: undefined } } const { rerender } = renderWithWebFieldContext(