diff --git a/doc/release/RELEASE-NOTES.md b/doc/release/RELEASE-NOTES.md index ad353f152..34e083ca3 100644 --- a/doc/release/RELEASE-NOTES.md +++ b/doc/release/RELEASE-NOTES.md @@ -61,6 +61,7 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html All other traffic will be redirected to the React application. ### Bugfix +* [OSDEV-1700](https://opensupplyhub.atlassian.net/browse/OSDEV-1700) - SLC: Keep only one previous OS ID in the search result if it matches the search query. * [OSDEV-1697](https://opensupplyhub.atlassian.net/browse/OSDEV-1697) - Added a redirect to the main page upon closing the SLC modal window to prevent the creation of multiple moderation events. * [OSDEV-1695](https://opensupplyhub.atlassian.net/browse/OSDEV-1695) - [SLC] Enabled the claim button for updated production locations when a moderation event has a pending status. Disabled claim button explicitly if production location has pending claim status. * [OSDEV-1701](https://opensupplyhub.atlassian.net/browse/OSDEV-1701) - Refactored "Go Back" button in production location info page. diff --git a/src/react/src/__tests__/components/ProductionLocationDetails.test.js b/src/react/src/__tests__/components/ProductionLocationDetails.test.js index 471f2c201..92fecd8fe 100644 --- a/src/react/src/__tests__/components/ProductionLocationDetails.test.js +++ b/src/react/src/__tests__/components/ProductionLocationDetails.test.js @@ -1,7 +1,13 @@ import React from 'react'; +import { useLocation } from 'react-router-dom'; import ProductionLocationDetails from '../../components/Contribute/ProductionLocationDetails'; import renderWithProviders from '../../util/testUtils/renderWithProviders'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), +})); + describe('ProductionLocationDetails component', () => { const osId = 'US2021250D1DTN7'; const name = 'Production Location Name'; @@ -17,17 +23,26 @@ describe('ProductionLocationDetails component', () => { historicalOsIds, }; - test('renders the production location details correctly', () => { - const { getByText } = renderWithProviders(); - + beforeEach(() => { + jest.clearAllMocks(); + useLocation.mockReturnValue({ pathname: '' }); + }); + + test('renders production location details correctly', () => { + useLocation.mockReturnValue({ + pathname: '/contribute/production-location/search/id/US2021250D1DTN7', + }); + + const { getByText } = renderWithProviders( + + ); + expect(getByText(name)).toBeInTheDocument(); - expect(getByText(`Current OS ID: ${osId}`)).toBeInTheDocument(); - expect(getByText(`Previous OS ID: ${historicalOsIds[0]}`)).toBeInTheDocument(); - expect(getByText(`Previous OS ID: ${historicalOsIds[1]}`)).toBeInTheDocument(); + expect(getByText(`OS ID: ${osId}`)).toBeInTheDocument(); expect(getByText(address)).toBeInTheDocument(); expect(getByText(countryName)).toBeInTheDocument(); }); - + test('does not render historical OS IDs if the array is empty', () => { const props = { ...defaultProps, historicalOsIds: [] }; const { getByText, queryByText } = renderWithProviders(); @@ -42,13 +57,54 @@ describe('ProductionLocationDetails component', () => { expect(getByText(`OS ID: ${osId}`)).toBeInTheDocument(); }); - - test('renders the tooltip for each historical OS ID', () => { - const { getAllByTestId } = renderWithProviders(); - - const tooltips = getAllByTestId('previous-os-id-tooltip'); - expect(tooltips.length).toBe(defaultProps.historicalOsIds.length); + + test('renders previous OS IDs that match the search parameter', () => { + useLocation.mockReturnValue({ + pathname: '/contribute/production-location/search/id/US2020053ZH1RY5', + }); + + const { getByText } = renderWithProviders( + + ); + + expect(getByText(`Current OS ID: ${osId}`)).toBeInTheDocument(); + expect(getByText('Previous OS ID: US2020053ZH1RY5')).toBeInTheDocument(); }); -}); + test('does not render previous OS IDs if they do not match the search parameter', () => { + useLocation.mockReturnValue({ + pathname: '/contribute/production-location/search/id/UNKNOWN_OS_ID', + }); + + const { queryByText } = renderWithProviders( + + ); + + expect(queryByText(`Previous OS ID: ${osId}`)).not.toBeInTheDocument(); + }); + + test('renders only "OS ID:" if there are no historical OS IDs', () => { + useLocation.mockReturnValue({ + pathname: '/contribute/production-location/search/id/US2021250D1DTN7', + }); + const { getByText } = renderWithProviders( + + ); + + expect(getByText(`OS ID: ${osId}`)).toBeInTheDocument(); + }); + + test('handles missing location pathname gracefully', () => { + useLocation.mockReturnValue({}); + + const { getByText } = renderWithProviders( + + ); + + expect(getByText(`OS ID: ${osId}`)).toBeInTheDocument(); + }); +}); diff --git a/src/react/src/__tests__/utils.tests.js b/src/react/src/__tests__/utils.tests.js index 382e341d4..aec2c4bfd 100644 --- a/src/react/src/__tests__/utils.tests.js +++ b/src/react/src/__tests__/utils.tests.js @@ -77,6 +77,7 @@ const { createUserDropdownLinks, createUploadFormErrorMessages, updateStateFromData, + getLastPathParameter, generateRangeField, } = require('../util/util'); @@ -1874,6 +1875,46 @@ it('should not call setter when dataKey is null', () => { expect(mockSetter).not.toHaveBeenCalled(); }); +it('extracts the ID from a valid URL without a trailing slash', () => { + const url = '/contribute/production-location/search/id/BD202034606B9SA'; + expect(getLastPathParameter(url)).toBe('BD202034606B9SA'); +}); + +it('extracts the ID from a valid URL with a trailing slash', () => { + const url = '/contribute/production-location/search/id/BD202034606B9SA/'; + expect(getLastPathParameter(url)).toBe('BD202034606B9SA'); +}); + +it('returns id when the URL ends at "id/" with no ID', () => { + const url = '/contribute/production-location/search/id/'; + expect(getLastPathParameter(url)).toBe('id'); +}); + +it('returns the correct ID when the URL contains query parameters', () => { + const url = '/contribute/production-location/search/id/BD202034606B9SA?foo=bar'; + expect(getLastPathParameter(url)).toBe('BD202034606B9SA'); +}); + +it('returns the correct ID when the URL has multiple segments after "id/"', () => { + const url = '/contribute/production-location/search/id/BD202034606B9SA/extra'; + expect(getLastPathParameter(url)).toBe('extra'); +}); + +it('returns the whole string if no slashes exist', () => { + const url = 'BD202034606B9SA'; + expect(getLastPathParameter(url)).toBe('BD202034606B9SA'); +}); + +it('returns empty string for an empty string', () => { + const url = ''; + expect(getLastPathParameter(url)).toBe(''); +}); + +it('returns empty string for a URL that only contains slashes', () => { + const url = '///'; + expect(getLastPathParameter(url)).toBe(''); +}); + it('should return { min: value, max: value } when value is a number', () => { expect(generateRangeField(10)).toEqual({ min: 10, max: 10 }); expect(generateRangeField(0)).toEqual({ min: 0, max: 0 }); diff --git a/src/react/src/components/Contribute/ProductionLocationDetails.jsx b/src/react/src/components/Contribute/ProductionLocationDetails.jsx index 7148042da..1fbc317e1 100644 --- a/src/react/src/components/Contribute/ProductionLocationDetails.jsx +++ b/src/react/src/components/Contribute/ProductionLocationDetails.jsx @@ -1,9 +1,11 @@ import React from 'react'; +import { useLocation } from 'react-router-dom'; import { string, arrayOf, object } from 'prop-types'; import { Typography } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import PreviousOsIdTooltip from './PreviousOsIdTooltip'; import { makeProductionLocationDetailsStyles } from '../../util/styles'; +import { getLastPathParameter } from '../../util/util'; const ProductionLocationDetails = ({ osId, @@ -13,8 +15,12 @@ const ProductionLocationDetails = ({ historicalOsIds, classes, }) => { - const historicalOsIdsNotEmpty = - Array.isArray(historicalOsIds) && historicalOsIds.length > 0; + const { pathname = '' } = useLocation(); + const osIdSearchParameter = pathname ? getLastPathParameter(pathname) : ''; + + const previousOsId = historicalOsIds.find( + historicalOsId => historicalOsId === osIdSearchParameter, + ); return (
@@ -25,17 +31,13 @@ const ProductionLocationDetails = ({ component="h6" className={classes.locationCurrentOsIdStyles} > - {historicalOsIdsNotEmpty ? 'Current OS ID:' : 'OS ID:'} {osId} + {previousOsId ? 'Current OS ID:' : 'OS ID:'} {osId} - {historicalOsIdsNotEmpty && - historicalOsIds.map(historicalOsId => ( - - Previous OS ID: {historicalOsId} - - ))} + {previousOsId && ( + + Previous OS ID: {previousOsId} + + )}
{address} diff --git a/src/react/src/util/styles.js b/src/react/src/util/styles.js index 37e769018..7b18b3329 100644 --- a/src/react/src/util/styles.js +++ b/src/react/src/util/styles.js @@ -1246,6 +1246,9 @@ export const makeProductionLocationDetailsStyles = theme => ({ marginTop: '8px', }), locationHistoricalOsIdStyles: Object.freeze({ + display: 'flex', + alignItems: 'center', + gap: '5px', fontSize: '14px', lineHeight: '20px', fontWeight: theme.typography.fontWeightBold, diff --git a/src/react/src/util/util.js b/src/react/src/util/util.js index be5938d54..3c43a4fba 100644 --- a/src/react/src/util/util.js +++ b/src/react/src/util/util.js @@ -4,6 +4,8 @@ import isArray from 'lodash/isArray'; import isObject from 'lodash/isObject'; import flatten from 'lodash/flatten'; import identity from 'lodash/identity'; +import split from 'lodash/split'; +import last from 'lodash/last'; import some from 'lodash/some'; import size from 'lodash/size'; import negate from 'lodash/negate'; @@ -1484,3 +1486,9 @@ export const parseContribData = contribData => { : null, }; }; + +export const getLastPathParameter = url => { + if (typeof url !== 'string') return ''; + const cleanUrl = url.split('?')[0]; + return last(split(trimEnd(cleanUrl, '/'), '/')) || ''; +};