diff --git a/src/pages/tickets/TicketModalPage.tsx b/src/pages/tickets/TicketModalPage.tsx index 6583bd63f..b061e07af 100644 --- a/src/pages/tickets/TicketModalPage.tsx +++ b/src/pages/tickets/TicketModalPage.tsx @@ -34,6 +34,8 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { const [visible, setVisible] = useState(false); const [isDeleted, setisDeleted] = useState(false); const [accessDenied, setAccessDenied] = useState(false); + const [hidePrevArrow, setHidePrevArrow] = useState(false); + const [hideNextArrow, setHideNextArrow] = useState(false); const [toasts, setToasts]: any = useState([]); @@ -78,7 +80,7 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { if (bountyId) { const isNumeric = /^\d+$/.test(bountyId); - + if (isNumeric) { bounty = await main.getBountyById(Number(bountyId), unlockCode); bountyIndex = await main.getBountyIndexById(Number(bountyId)); @@ -166,6 +168,11 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { getBounty(); }, [getBounty, removeNextAndPrev]); + useEffect(() => { + setHidePrevArrow(false); + setHideNextArrow(false); + }, [bountyId, activeWorkspace]); + const isDirectAccess = useCallback(() => !document.referrer, [location.pathname]); const goBack = () => { @@ -188,6 +195,7 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { try { const bountyId = await main.getWorkspaceNextBountyByCreated(activeWorkspace, created); if (bountyId === 0) { + setHidePrevArrow(true); addToast('There are no more bounties to display!', 'primary'); } else { history.replace(`/bounty/${bountyId}`); @@ -198,7 +206,12 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { } else { try { const bountyId = await main.getNextBountyByCreated(created); - history.replace(`/bounty/${bountyId}`); + if (bountyId === 0) { + setHidePrevArrow(true); + addToast('There are no more bounties to display!', 'primary'); + } else { + history.replace(`/bounty/${bountyId}`); + } } catch (e) { console.error(e); } @@ -212,6 +225,7 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { try { const bountyId = await main.getWorkspacePreviousBountyByCreated(activeWorkspace, created); if (bountyId === 0) { + setHideNextArrow(true); addToast('There are no more bounties to display!', 'primary'); } else { history.replace(`/bounty/${bountyId}`); @@ -222,7 +236,12 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { } else { try { const bountyId = await main.getPreviousBountyByCreated(created); - history.replace(`/bounty/${bountyId}`); + if (bountyId === 0) { + setHideNextArrow(true); + addToast('There are no more bounties to display!', 'primary'); + } else { + history.replace(`/bounty/${bountyId}`); + } } catch (e) { console.error(e); } @@ -324,8 +343,8 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { right: '-50px', borderRadius: '50%' }} - prevArrowNew={removeNextAndPrev ? undefined : prevArrHandler} - nextArrowNew={removeNextAndPrev ? undefined : nextArrHandler} + prevArrowNew={removeNextAndPrev || hidePrevArrow ? undefined : prevArrHandler} + nextArrowNew={removeNextAndPrev || hideNextArrow ? undefined : nextArrHandler} > () => {}); +// eslint-disable-next-line @typescript-eslint/no-empty-function +jest.mock('rehype-raw', () => () => {}); + jest.mock('hooks', () => ({ useIsMobile: jest.fn(), useFeatureFlag: jest.fn().mockReturnValue({ isEnabled: true }) @@ -20,17 +27,20 @@ jest.mock('hooks', () => ({ const mockPush = jest.fn(); const mockGoBack = jest.fn(); +const mockReplace = jest.fn(); +let mockLocationState: any; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: () => ({ push: mockPush, - goBack: mockGoBack + goBack: mockGoBack, + replace: mockReplace }), useLocation: () => ({ pathname: '/bounty/1239', search: '', - state: {} + state: mockLocationState }), useParams: () => ({ uuid: 'ck95pe04nncjnaefo08g', @@ -47,6 +57,11 @@ jest.mock('helpers', () => ({ describe('TicketModalPage Component', () => { beforeEach(() => { + mockLocationState = undefined; + mainStore.setActiveWorkspace(''); + fetchMock.doMock(); + fetchMock.mockResponse(JSON.stringify([])); + const mockIntersectionObserver = jest.fn(); mockIntersectionObserver.mockReturnValue({ observe: () => null, @@ -58,6 +73,9 @@ describe('TicketModalPage Component', () => { afterEach(() => { jest.clearAllMocks(); + fetchMock.resetMocks(); + fetchMock.dontMock(); + mainStore.setActiveWorkspace(''); }); it('reder ticket modal', async () => { @@ -811,6 +829,86 @@ describe('TicketModalPage Component', () => { }); }); + it('hides the public next arrow and keeps the user off the deleted bounty page at list end', async () => { + jest + .spyOn(mainStore, 'getBountyById') + .mockReturnValue( + Promise.resolve([ + { ...newBounty, body: { ...mockBountiesMutated[1].body, assignee: user } } + ]) + ); + jest.spyOn(mainStore, 'getBountyIndexById').mockReturnValue(Promise.resolve(1239)); + jest.spyOn(mainStore, 'getNextBountyByCreated').mockResolvedValueOnce(0); + + await act(async () => { + render( + + + + ); + }); + + await waitFor(() => screen.getByText('chevron_left')); + fireEvent.click(screen.getByText('chevron_left')); + + await waitFor(() => expect(screen.queryByText('chevron_left')).not.toBeInTheDocument()); + expect(mockReplace).not.toHaveBeenCalledWith('/bounty/0'); + expect(screen.getByText('There are no more bounties to display!')).toBeInTheDocument(); + }); + + it('hides the workspace previous arrow and keeps the user off the deleted bounty page at list end', async () => { + mockLocationState = { activeWorkspace: 'workspace-uuid' }; + jest + .spyOn(mainStore, 'getBountyById') + .mockReturnValue( + Promise.resolve([ + { ...newBounty, body: { ...mockBountiesMutated[1].body, assignee: user } } + ]) + ); + jest.spyOn(mainStore, 'getBountyIndexById').mockReturnValue(Promise.resolve(1239)); + jest.spyOn(mainStore, 'getWorkspacePreviousBountyByCreated').mockResolvedValueOnce(0); + + await act(async () => { + render( + + + + ); + }); + + await waitFor(() => screen.getByText('chevron_right')); + fireEvent.click(screen.getByText('chevron_right')); + + await waitFor(() => expect(screen.queryByText('chevron_right')).not.toBeInTheDocument()); + expect(mockReplace).not.toHaveBeenCalledWith('/bounty/0'); + expect(screen.getByText('There are no more bounties to display!')).toBeInTheDocument(); + }); + + it('navigates to the exact public bounty returned by the L/R lookup', async () => { + jest + .spyOn(mainStore, 'getBountyById') + .mockReturnValue( + Promise.resolve([ + { ...newBounty, body: { ...mockBountiesMutated[1].body, assignee: user } } + ]) + ); + jest.spyOn(mainStore, 'getBountyIndexById').mockReturnValue(Promise.resolve(1239)); + jest.spyOn(mainStore, 'getPreviousBountyByCreated').mockResolvedValueOnce(5678); + + await act(async () => { + render( + + + + ); + }); + + await waitFor(() => screen.getByText('chevron_right')); + fireEvent.click(screen.getByText('chevron_right')); + + await waitFor(() => expect(mockReplace).toHaveBeenCalledWith('/bounty/5678')); + }); + it('checks for enabled state of the delete button based on no assignment status', async () => { uiStore.setMeInfo(user); jest.spyOn(mainStore, 'getBountyById').mockReturnValue(