diff --git a/doc/release/RELEASE-NOTES.md b/doc/release/RELEASE-NOTES.md index 0699f0abc..c10625569 100644 --- a/doc/release/RELEASE-NOTES.md +++ b/doc/release/RELEASE-NOTES.md @@ -54,7 +54,7 @@ Also was added sanitization on the server side by using the `Django-Bleach` libr ### Bugfix * [OSDEV-1806](https://opensupplyhub.atlassian.net/browse/OSDEV-1806) - Refactored the Parent Company field validation. The field is now validated as a regular character field. -* [OSDEV-1787](https://opensupplyhub.atlassian.net/browse/OSDEV-1787) - The tooltip messages for the Claim button have been removed for all statuses of moderation events on the `Contribution Record` page and changed according to the design on `Thanks for adding data for this production location` pop-up. +* [OSDEV-1787](https://opensupplyhub.atlassian.net/browse/OSDEV-1787) - The tooltip messages for the Claim button have been removed for all statuses of moderation events on the `Contribution Record` page and changed according to the design on `Thanks for adding data for this production location` pop-up. Changed tooltip text for pending badge if existing production location has pending claim status or has been claimed already. * [OSDEV-1789](https://opensupplyhub.atlassian.net/browse/OSDEV-1789) - Fixed an issue where the scroll position was not resetting to the top when navigating through SLC workflow pages. * [OSDEV-1795](https://opensupplyhub.atlassian.net/browse/OSDEV-1795) - Resolved database connection issue after PostgreSQL 16.3 upgrade by upgrading pg8000 module version. * [OSDEV-1803](https://opensupplyhub.atlassian.net/browse/OSDEV-1803) - Updated text from `Facility Type` to `Location Type` and `Facility Name` to `Location Name` on the SLC `Thank You for Your Submission` page. diff --git a/src/react/src/__tests__/components/ProductionLocationDialog.test.js b/src/react/src/__tests__/components/ProductionLocationDialog.test.js index 09b0f62b7..a5d351de4 100644 --- a/src/react/src/__tests__/components/ProductionLocationDialog.test.js +++ b/src/react/src/__tests__/components/ProductionLocationDialog.test.js @@ -1,8 +1,12 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import { BrowserRouter as Router, useHistory } from 'react-router-dom'; import ProductionLocationDialog from '../../components/Contribute/ProductionLocationDialog'; import ProductionLocationDialogCloseButton from '../../components/Contribute/ProductionLocationDialogCloseButton'; +import { + MODERATION_STATUSES_ENUM, + PRODUCTION_LOCATION_CLAIM_STATUSES_ENUM, +} from '../../util/constants'; jest.mock('react-router-dom', () => ({ @@ -10,6 +14,34 @@ jest.mock('react-router-dom', () => ({ useHistory: jest.fn(), })); +jest.mock("../../components/Contribute/DialogTooltip", () => { + // eslint-disable-next-line no-shadow, global-require + const React = require('react'); + return function MockDialogTooltip({ text, childComponent }) { + const [open, setOpen] = React.useState(false); + + const handleMouseOver = () => { + setOpen(true); + }; + + const handleMouseOut = () => { + setOpen(false); + }; + + return ( +
+ {childComponent} + {open &&
{text}
} +
+ ); + }; +}); + const mockHistoryPush = jest.fn(); describe('ProductionLocationDialog', () => { @@ -66,7 +98,7 @@ describe('ProductionLocationDialog', () => { }); test('renders dialog content', () => { - render( + const { getAllByText, getByText } = render( { ); - expect(screen.getByText(/Thanks for adding data for this production location!/i)).toBeInTheDocument(); + expect(getByText(/Thanks for adding data for this production location!/i)).toBeInTheDocument(); - expect(screen.getAllByText(/Location name/i)).toHaveLength(2); - expect(screen.getByText(/Production Location Name/i)).toBeInTheDocument(); + expect(getAllByText(/Location name/i)).toHaveLength(2); + expect(getByText(/Production Location Name/i)).toBeInTheDocument(); - expect(screen.getByText(/Address/i)).toBeInTheDocument(); - expect(screen.getByText(/1234 Production Location St, City, State, 12345/i)).toBeInTheDocument(); + expect(getByText(/Address/i)).toBeInTheDocument(); + expect(getByText(/1234 Production Location St, City, State, 12345/i)).toBeInTheDocument(); - expect(screen.getByText(/OS ID/i)).toBeInTheDocument(); - expect(screen.getByText(/US2021250D1DTN7/i)).toBeInTheDocument(); + expect(getByText(/OS ID/i)).toBeInTheDocument(); + expect(getByText(/US2021250D1DTN7/i)).toBeInTheDocument(); - expect(screen.getByText(/Pending/i)).toBeInTheDocument(); - expect(screen.getByText(/Location Type/i)).toBeInTheDocument(); + expect(getByText(/Pending/i)).toBeInTheDocument(); + expect(getByText(/Location Type/i)).toBeInTheDocument(); - expect(screen.getByText(/Number of workers/i)).toBeInTheDocument(); - expect(screen.getByText(/35 - 60/i)).toBeInTheDocument(); + expect(getByText(/Number of workers/i)).toBeInTheDocument(); + expect(getByText(/35 - 60/i)).toBeInTheDocument(); - expect(screen.getByText(/Processing Type/i)).toBeInTheDocument(); - expect(screen.getByText(/Assembly, Printing/i)).toBeInTheDocument(); + expect(getByText(/Processing Type/i)).toBeInTheDocument(); + expect(getByText(/Assembly, Printing/i)).toBeInTheDocument(); - expect(screen.getByText(/Parent Company/i)).toBeInTheDocument(); - expect(screen.getByText(/ParentCompany1, ParentCompany2/i)).toBeInTheDocument(); + expect(getByText(/Parent Company/i)).toBeInTheDocument(); + expect(getByText(/ParentCompany1, ParentCompany2/i)).toBeInTheDocument(); - expect(screen.getByText(/Country/i)).toBeInTheDocument(); - expect(screen.getByText(/BD/i)).toBeInTheDocument(); + expect(getByText(/Country/i)).toBeInTheDocument(); + expect(getByText(/BD/i)).toBeInTheDocument(); - expect(screen.getByText(/Product Type/i)).toBeInTheDocument(); - expect(screen.getByText(/Shirts, Pants/i)).toBeInTheDocument(); + expect(getByText(/Product Type/i)).toBeInTheDocument(); + expect(getByText(/Shirts, Pants/i)).toBeInTheDocument(); }); test.each([ @@ -191,7 +223,7 @@ describe('ProductionLocationDialog', () => { }); test('redirect to the main page when clicking close button', () => { - render( + const { getByText, getByRole } = render( { ); - expect(screen.getByText(/Continue to Claim/i)).toBeInTheDocument(); + expect(getByText(/Continue to Claim/i)).toBeInTheDocument(); - const closeButton = screen.getByRole('button', { name: /close/i }); + const closeButton = getByRole('button', { name: /close/i }); fireEvent.click(closeButton); expect(mockHistoryPush).toHaveBeenCalledWith('/'); }); }); + +describe('ProductionLocationDialog tooltip messages for PENDING, CLAIMED and UNCLAIMED production locations', () => { + const defaultProps = { + osID: 'US2021250D1DTN7', + data: { + raw_json: { + name: 'Production Location Name', + address: '1234 Production Location St, City, State, 12345', + }, + fields: { + country: "BD" + } + } + }; + + beforeEach(() => { + useHistory.mockReturnValue({ + push: mockHistoryPush, + listen: jest.fn(() => jest.fn()), + }); + }); + + test.each([ + [PRODUCTION_LOCATION_CLAIM_STATUSES_ENUM.CLAIMED, + 'Your submission is being reviewed. You will receive an email confirming your OS ID once the review is complete.', + 'This location has already been claimed and therefore cannot be claimed again.'], + [PRODUCTION_LOCATION_CLAIM_STATUSES_ENUM.PENDING, + 'Your submission is being reviewed. You will receive an email confirming your OS ID once the review is complete.', + 'This location cannot be claimed because a pending claim already exists.'] + ])( + 'renders claim button and pending badge tooltips when moderation event is pending and production location has status: %s', + async (claimStatus, expectedPendingTooltip, expectedClaimTooltip) => { + const { getAllByTestId, getByRole, findByText } = render( + + + + ); + + const tooltipIcons = getAllByTestId('tooltip-icon'); + + // Pending icon tooltip message + fireEvent.mouseOver(tooltipIcons[0]); + + const pendingTooltipText = await findByText(expectedPendingTooltip); + expect(pendingTooltipText).toBeInTheDocument(); + + fireEvent.mouseOut(tooltipIcons[0]); + expect(pendingTooltipText).not.toBeInTheDocument(); + + // Claim button tooltip message + const claimButton = getByRole('button', { name: /Continue to Claim/i }); + expect(claimButton).toHaveAttribute('tabindex', '-1'); + + fireEvent.mouseOver(claimButton); + const claimTooltipText = await findByText(expectedClaimTooltip); + expect(claimTooltipText).toBeInTheDocument(); + + fireEvent.mouseOut(claimButton); + expect(claimTooltipText).not.toBeInTheDocument(); + } + ); + + test('renders claim button and pending badge tooltips when moderation event is pending and production location is available for claim', async () => { + const { getAllByTestId, getByRole, findByText } = render( + + + + ); + + // Pending icon tooltip message + const tooltipIcons = getAllByTestId('tooltip-icon'); + + fireEvent.mouseOver(tooltipIcons[0]); + + const pendingTooltipText = await findByText( + 'Your submission is under review. You will receive a notification once the production location is live on OS Hub. You can proceed to submit a claim while your request is pending.' + ); + expect(pendingTooltipText).toBeInTheDocument(); + + fireEvent.mouseOut(tooltipIcons[0]); + expect(pendingTooltipText).not.toBeInTheDocument(); + + // Claim button + const claimButton = getByRole('button', { name: /Continue to Claim/i }); + expect(claimButton).toHaveAttribute('href', `/facilities/${defaultProps.osID}/claim`); + expect(claimButton).not.toBeDisabled(); + }); + + test('renders claim button and pending badge tooltips when moderation event is pending and production location has\'t been created yet', async () => { + const { getAllByTestId, getByRole, findByText } = render( + + + + ); + + // Pending icon tooltip message + const tooltipIcons = getAllByTestId('tooltip-icon'); + + fireEvent.mouseOver(tooltipIcons[0]); + + const pendingTooltipText = await findByText( + 'Your submission is being reviewed. You will receive an email with your OS ID once the review is complete.' + ); + expect(pendingTooltipText).toBeInTheDocument(); + + fireEvent.mouseOut(tooltipIcons[0]); + expect(pendingTooltipText).not.toBeInTheDocument(); + + // Claim button tooltip message + const claimButton = getByRole('button', { name: /Continue to Claim/i }); + expect(claimButton).toHaveAttribute('tabindex', '-1'); + + fireEvent.mouseOver(claimButton); + const claimTooltipText = await findByText( + 'You will be able to claim this location once the review is complete.' + ); + expect(claimTooltipText).toBeInTheDocument(); + + fireEvent.mouseOut(claimButton); + expect(claimTooltipText).not.toBeInTheDocument(); + }); +}); diff --git a/src/react/src/__tests__/components/RejectModerationEventDialog.test.js b/src/react/src/__tests__/components/RejectModerationEventDialog.test.js index 6eb5e9325..14b7161f6 100644 --- a/src/react/src/__tests__/components/RejectModerationEventDialog.test.js +++ b/src/react/src/__tests__/components/RejectModerationEventDialog.test.js @@ -33,14 +33,13 @@ jest.mock('@material-ui/core/Tooltip', () => ({ children, title, open, onOpen, o onMouseOver={onOpen} onFocus={onOpen} onMouseOut={onClose} - onBlur={onClose} + onBlur={onClose} > {children} {open &&
{title}
} )); - - + describe('RejectModerationEventDialog component', () => { const defaultProps = { updateModerationEvent: mockUpdateModerationEvent, @@ -66,7 +65,7 @@ describe('RejectModerationEventDialog component', () => { test('calls closeDialog when "Cancel" button is clicked', () => { const { getByRole } = renderWithProviders(); const cancelButton = getByRole('button', {name: /Cancel/i}); - + fireEvent.click(cancelButton); expect(mockCloseDialog).toHaveBeenCalledTimes(1); }); @@ -76,10 +75,10 @@ describe('RejectModerationEventDialog component', () => { ); const rejectButton = getByRole('button', {name: /Reject/i}); - + expect(rejectButton).toBeDisabled(); }); - + test('reject button is enabled when editor text is at least 30 characters and calls updateModerationEvent and closeDialog on click', async () => { const { getByRole, getByTestId } = renderWithProviders( @@ -94,9 +93,9 @@ describe('RejectModerationEventDialog component', () => { fireEvent.change(fakeEditor, { target: { value: validText } }); expect(rejectButton).toBeEnabled(); - + fireEvent.click(rejectButton); - + expect(mockUpdateModerationEvent).toHaveBeenCalledTimes(1); expect(mockUpdateModerationEvent).toHaveBeenCalledWith( MODERATION_STATUSES_ENUM.REJECTED, @@ -137,7 +136,7 @@ describe('RejectModerationEventDialog component', () => { fireEvent.change(fakeEditor, { target: { value: validText } }); expect(rejectButton).toBeEnabled(); - + fireEvent.mouseOver(rejectButton); expect(queryByText('Please provide a message with at least 30 characters.')).not.toBeInTheDocument(); }); diff --git a/src/react/src/components/Contribute/ProductionLocationDialog.jsx b/src/react/src/components/Contribute/ProductionLocationDialog.jsx index b272149a3..d1c806e08 100644 --- a/src/react/src/components/Contribute/ProductionLocationDialog.jsx +++ b/src/react/src/components/Contribute/ProductionLocationDialog.jsx @@ -35,7 +35,10 @@ import { makeProductionLocationDialogStyles } from '../../util/styles'; import { getIsMobile, makeClaimFacilityLink } from '../../util/util'; const infoIcon = classes => ( - + ); const claimButton = ({ classes, osID = '', isDisabled = true }) => ( @@ -72,9 +75,21 @@ const getStatusBadgeClass = (classes, status) => { }; const getPendingTooltipText = claimStatus => { - if (claimStatus === PRODUCTION_LOCATION_CLAIM_STATUSES_ENUM.UNCLAIMED) { + const { + PENDING, + CLAIMED, + UNCLAIMED, + } = PRODUCTION_LOCATION_CLAIM_STATUSES_ENUM; + + if (claimStatus === PENDING || claimStatus === CLAIMED) { + return 'Your submission is being reviewed. You will receive an email confirming your OS ID once the review is complete.'; + } + + if (claimStatus === UNCLAIMED) { return 'Your submission is under review. You will receive a notification once the production location is live on OS Hub. You can proceed to submit a claim while your request is pending.'; } + + // Default tooltip text if a production location hasn't been created yet. return 'Your submission is being reviewed. You will receive an email with your OS ID once the review is complete.'; };