From 2b08ec872cd45c37c930499be3c2fd707a6a1169 Mon Sep 17 00:00:00 2001 From: Yury Saukou Date: Wed, 13 Nov 2024 21:37:51 +0400 Subject: [PATCH 1/3] UISACQCOMP-230 Move reusable version history components to the ACQ lib --- CHANGELOG.md | 1 + .../VersionCheckbox/VersionCheckbox.js | 40 ++++++++++ .../components/VersionCheckbox/index.js | 1 + .../VersionKeyValue/VersionKeyValue.js | 49 ++++++++++++ .../components/VersionKeyValue/index.js | 1 + .../components/VersionView/VersionView.js | 76 +++++++++++++++++++ .../components/VersionView/index.js | 1 + lib/VersionHistory/components/index.js | 3 + lib/VersionHistory/index.js | 1 + lib/constants/api.js | 1 + lib/hooks/useOrganization/useOrganization.js | 2 +- translations/stripes-acq-components/en.json | 1 + 12 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js create mode 100644 lib/VersionHistory/components/VersionCheckbox/index.js create mode 100644 lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js create mode 100644 lib/VersionHistory/components/VersionKeyValue/index.js create mode 100644 lib/VersionHistory/components/VersionView/VersionView.js create mode 100644 lib/VersionHistory/components/VersionView/index.js create mode 100644 lib/VersionHistory/components/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5c453c..80b5050b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## (6.1.0 IN PROGRESS) * Add more reusable hooks and utilities. Refs UISACQCOMP-228. +* Move reusable version history components to the ACQ lib. Refs UISACQCOMP-230. ## (6.0.1 IN PROGRESS) diff --git a/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js new file mode 100644 index 00000000..5c7ad0d9 --- /dev/null +++ b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js @@ -0,0 +1,40 @@ +import PropTypes from 'prop-types'; +import { useContext, useMemo } from 'react'; + +import { Checkbox } from '@folio/stripes/components'; + +import { VersionViewContext } from '../../VersionViewContext'; + +export const VersionCheckbox = ({ + checked, + label, + name, + ...props +}) => { + const versionContext = useContext(VersionViewContext); + const isUpdated = useMemo(() => ( + versionContext?.paths?.includes(name) + ), [name, versionContext?.paths]); + + const checkboxLabel = isUpdated ? {label} : label; + + return ( + + ); +}; + +VersionCheckbox.defaultProps = { + checked: false, +}; + +VersionCheckbox.propTypes = { + checked: PropTypes.bool, + label: PropTypes.node.isRequired, + name: PropTypes.string.isRequired, +}; diff --git a/lib/VersionHistory/components/VersionCheckbox/index.js b/lib/VersionHistory/components/VersionCheckbox/index.js new file mode 100644 index 00000000..6b54ebbc --- /dev/null +++ b/lib/VersionHistory/components/VersionCheckbox/index.js @@ -0,0 +1 @@ +export { VersionCheckbox } from './VersionCheckbox'; diff --git a/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js new file mode 100644 index 00000000..fa340550 --- /dev/null +++ b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import { + useContext, + useMemo, +} from 'react'; + +import { + KeyValue, + NoValue, +} from '@folio/stripes/components'; + +import { VersionViewContext } from '../../VersionViewContext'; + +export const VersionKeyValue = ({ + children, + label, + multiple, + name, + value, +}) => { + const versionContext = useContext(VersionViewContext); + const isUpdated = useMemo(() => ( + multiple + ? versionContext?.paths?.find((field) => new RegExp(`^${name}\\[\\d\\]$`).test(field)) + : versionContext?.paths?.includes(name) + ), [multiple, name, versionContext?.paths]); + + const content = (children || value) || ; + const displayValue = isUpdated ? {content} : content; + + return ( + + ); +}; + +VersionKeyValue.defaultProps = { + multiple: false, +}; + +VersionKeyValue.propTypes = { + children: PropTypes.node, + label: PropTypes.node.isRequired, + multiple: PropTypes.bool, + name: PropTypes.string.isRequired, + value: PropTypes.node, +}; diff --git a/lib/VersionHistory/components/VersionKeyValue/index.js b/lib/VersionHistory/components/VersionKeyValue/index.js new file mode 100644 index 00000000..a27963d2 --- /dev/null +++ b/lib/VersionHistory/components/VersionKeyValue/index.js @@ -0,0 +1 @@ +export { VersionKeyValue } from './VersionKeyValue'; diff --git a/lib/VersionHistory/components/VersionView/VersionView.js b/lib/VersionHistory/components/VersionView/VersionView.js new file mode 100644 index 00000000..c8b58a4a --- /dev/null +++ b/lib/VersionHistory/components/VersionView/VersionView.js @@ -0,0 +1,76 @@ +import PropTypes from 'prop-types'; +import { + memo, + useMemo, +} from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { + Layout, + LoadingPane, + Pane, + PaneMenu, +} from '@folio/stripes/components'; + +import { TagsBadge } from '../../../Tags'; +import { VersionHistoryButton } from '../../VersionHistoryButton'; + +const VersionView = ({ + children, + id, + isLoading, + onClose, + tags, + versionId, + ...props +}) => { + const isVersionExist = Boolean(versionId && !isLoading); + + const lastMenu = useMemo(() => ( + + {tags && ( + + )} + + + ), [tags]); + + if (isLoading) return ; + + return ( + + { + isVersionExist + ? children + : ( + + + + ) + } + + ); +}; + +VersionView.propTypes = { + children: PropTypes.node.isRequired, + id: PropTypes.string.isRequired, + isLoading: PropTypes.bool, + onClose: PropTypes.func, + tags: PropTypes.arrayOf(PropTypes.object), + versionId: PropTypes.string, +}; + +export default memo(VersionView); diff --git a/lib/VersionHistory/components/VersionView/index.js b/lib/VersionHistory/components/VersionView/index.js new file mode 100644 index 00000000..77c4a41e --- /dev/null +++ b/lib/VersionHistory/components/VersionView/index.js @@ -0,0 +1 @@ +export { default as VersionView } from './VersionView'; diff --git a/lib/VersionHistory/components/index.js b/lib/VersionHistory/components/index.js new file mode 100644 index 00000000..6c8b0a4f --- /dev/null +++ b/lib/VersionHistory/components/index.js @@ -0,0 +1,3 @@ +export { VersionCheckbox } from './VersionCheckbox'; +export { VersionKeyValue } from './VersionKeyValue'; +export { VersionView } from './VersionView'; diff --git a/lib/VersionHistory/index.js b/lib/VersionHistory/index.js index 099a7b0d..97242923 100644 --- a/lib/VersionHistory/index.js +++ b/lib/VersionHistory/index.js @@ -1,3 +1,4 @@ +export * from './components'; export { getFieldLabels } from './getFieldLabels'; export { getHighlightedFields } from './getHighlightedFields'; export * from './hooks'; diff --git a/lib/constants/api.js b/lib/constants/api.js index 6a4dfbf3..ef5dbb98 100644 --- a/lib/constants/api.js +++ b/lib/constants/api.js @@ -1,6 +1,7 @@ export const ACQUISITION_METHODS_API = 'orders/acquisition-methods'; export const ACQUISITIONS_UNITS_API = 'acquisitions-units/units'; export const ACQUISITIONS_UNIT_MEMBERSHIPS_API = 'acquisitions-units/memberships'; +export const AUDIT_ACQ_EVENTS_API = 'audit-data/acquisition'; export const BATCH_GROUPS_API = 'batch-groups'; export const BUDGETS_API = 'finance/budgets'; export const CAMPUSES_API = 'location-units/campuses'; diff --git a/lib/hooks/useOrganization/useOrganization.js b/lib/hooks/useOrganization/useOrganization.js index 4e1f4b17..8d629b2a 100644 --- a/lib/hooks/useOrganization/useOrganization.js +++ b/lib/hooks/useOrganization/useOrganization.js @@ -23,7 +23,7 @@ export const useOrganization = (id, options = {}) => { isLoading, } = useQuery({ queryKey: [namespace, id, tenantId], - queryFn: () => ky.get(`${VENDORS_API}/${id}`).json(), + queryFn: ({ signal }) => ky.get(`${VENDORS_API}/${id}`, { signal }).json(), enabled: enabled && Boolean(id), ...queryOptions, }); diff --git a/translations/stripes-acq-components/en.json b/translations/stripes-acq-components/en.json index c642972f..3e357d86 100644 --- a/translations/stripes-acq-components/en.json +++ b/translations/stripes-acq-components/en.json @@ -221,6 +221,7 @@ "versionHistory.card.version.current": "Current version", "versionHistory.card.version.original": "Original version", "versionHistory.deletedRecord": "Record deleted", + "versionHistory.noVersion": "No version history to display.", "versionHistory.pane.header": "Version history", "versionHistory.pane.sub": "{count, plural, one {# version} other {# versions}}", From 441dc05e1d4fba4f784798f6b9e3f8e387c0c4ca Mon Sep 17 00:00:00 2001 From: Yury Saukou Date: Wed, 13 Nov 2024 22:21:38 +0400 Subject: [PATCH 2/3] add unit tests --- .../VersionCheckbox/VersionCheckbox.test.js | 39 +++++++++++ .../VersionKeyValue/VersionKeyValue.test.js | 64 +++++++++++++++++++ .../VersionView/VersionView.test.js | 52 +++++++++++++++ lib/hooks/useLineHoldings/useLineHoldings.js | 4 +- .../useOrganization/useOrganization.test.js | 4 +- lib/hooks/useUsersBatch/useUsersBatch.js | 4 +- lib/hooks/useUsersBatch/useUsersBatch.test.js | 3 +- 7 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js create mode 100644 lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.test.js create mode 100644 lib/VersionHistory/components/VersionView/VersionView.test.js diff --git a/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js new file mode 100644 index 00000000..2e3fd021 --- /dev/null +++ b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js @@ -0,0 +1,39 @@ +import { + render, + screen, +} from '@testing-library/react'; + +import { VersionViewContext } from '../../VersionViewContext'; +import { VersionCheckbox } from './VersionCheckbox'; + +const defaultProps = { + label: 'Test Label', + name: 'testName', +}; + +const renderVersionCheckbox = (props = {}, contextValue = {}) => { + return render( + + + , + ); +}; + +describe('VersionCheckbox', () => { + it('renders with marked label when name is in context paths', () => { + renderVersionCheckbox({}, { paths: ['testName'] }); + + screen.debug(); + + expect(screen.getByText('Test Label').closest('mark')).toBeInTheDocument(); + }); + + it('renders with normal label when name is not in context paths', () => { + renderVersionCheckbox({}, { paths: ['otherName'] }); + + expect(screen.getByText('Test Label').closest('mark')).not.toBeInTheDocument(); + }); +}); diff --git a/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.test.js b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.test.js new file mode 100644 index 00000000..f20a184a --- /dev/null +++ b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.test.js @@ -0,0 +1,64 @@ +import { + render, + screen, +} from '@testing-library/react'; + +import { VersionViewContext } from '../../VersionViewContext'; +import { VersionKeyValue } from './VersionKeyValue'; + +const defaultProps = { + label: 'Test Label', + value: 'Test Value', + name: 'testName', +}; + +const renderComponent = (props = {}, contextValue = {}) => { + return render( + + + , + ); +}; + +describe('VersionKeyValue', () => { + it('should render label and value', () => { + renderComponent(); + + expect(screen.getByText('Test Label')).toBeInTheDocument(); + expect(screen.getByText('Test Value')).toBeInTheDocument(); + }); + + it('should render NoValue when value is not provided', () => { + renderComponent({ value: undefined }); + + expect(screen.getByText('Test Label')).toBeInTheDocument(); + expect(screen.getByText('stripes-components.noValue.noValueSet')).toBeInTheDocument(); + }); + + it('should highlight updated value', () => { + renderComponent({ name: 'testName' }, { paths: ['testName'] }); + + expect(screen.getByText('Test Value').closest('mark')).toBeInTheDocument(); + }); + + it('should not highlight non-updated value', () => { + renderComponent({}, { paths: ['anotherName'] }); + + expect(screen.getByText('Test Value').closest('mark')).not.toBeInTheDocument(); + }); + + it('should highlight updated value for multiple fields', () => { + renderComponent({ multiple: true }, { paths: ['testName[0]'] }); + + expect(screen.getByText('Test Value').closest('mark')).toBeInTheDocument(); + }); + + it('should not highlight non-updated value for multiple fields', () => { + renderComponent({ multiple: true }, { paths: ['anotherName[0]'] }); + + expect(screen.getByText('Test Value').closest('mark')).not.toBeInTheDocument(); + }); +}); diff --git a/lib/VersionHistory/components/VersionView/VersionView.test.js b/lib/VersionHistory/components/VersionView/VersionView.test.js new file mode 100644 index 00000000..422b0596 --- /dev/null +++ b/lib/VersionHistory/components/VersionView/VersionView.test.js @@ -0,0 +1,52 @@ +import { + render, + screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import VersionView from './VersionView'; + +const defaultProps = { + children:
Version Content
, + id: 'test-id', + isLoading: false, + onClose: jest.fn(), + tags: [{ id: 'tag1' }, { id: 'tag2' }], + versionId: 'version1', + dismissible: true, +}; + +const renderComponent = (props = {}) => render( + , +); + +describe('VersionView', () => { + it('should render loading pane when isLoading is true', () => { + renderComponent({ isLoading: true }); + + expect(screen.queryByText('Version Content')).not.toBeInTheDocument(); + }); + + it('should render children when version exists and is not loading', () => { + renderComponent(); + + expect(screen.getByText('Version Content')).toBeInTheDocument(); + }); + + it('should render no version message when version does not exist', () => { + renderComponent({ versionId: null }); + + expect(screen.getByText('stripes-acq-components.versionHistory.noVersion')).toBeInTheDocument(); + }); + + it('should call onClose when Pane onClose is triggered', async () => { + renderComponent(); + + await userEvent.click(screen.getByRole('button', { name: 'stripes-components.closeItem' })); + + expect(defaultProps.onClose).toHaveBeenCalled(); + }); +}); diff --git a/lib/hooks/useLineHoldings/useLineHoldings.js b/lib/hooks/useLineHoldings/useLineHoldings.js index cd79b33f..3b01813e 100644 --- a/lib/hooks/useLineHoldings/useLineHoldings.js +++ b/lib/hooks/useLineHoldings/useLineHoldings.js @@ -11,9 +11,9 @@ export const useLineHoldings = (holdingIds) => { const query = useQuery( [namespace, holdingIds], - () => { + ({ signal }) => { return batchRequest( - ({ params: searchParams }) => ky.get(HOLDINGS_API, { searchParams }).json(), + ({ params: searchParams }) => ky.get(HOLDINGS_API, { searchParams, signal }).json(), holdingIds, ); }, diff --git a/lib/hooks/useOrganization/useOrganization.test.js b/lib/hooks/useOrganization/useOrganization.test.js index a3a381f1..5c91bbb7 100644 --- a/lib/hooks/useOrganization/useOrganization.test.js +++ b/lib/hooks/useOrganization/useOrganization.test.js @@ -11,8 +11,6 @@ import { VENDORS_API } from '../../constants'; import { useOrganization } from './useOrganization'; const queryClient = new QueryClient(); - -// eslint-disable-next-line react/prop-types const wrapper = ({ children }) => ( {children} @@ -42,6 +40,6 @@ describe('useOrganization', () => { await waitFor(() => !result.current.isLoading); expect(result.current.organization).toEqual(organization); - expect(mockGet).toHaveBeenCalledWith(`${VENDORS_API}/${organization.id}`); + expect(mockGet).toHaveBeenCalledWith(`${VENDORS_API}/${organization.id}`, expect.any(Object)); }); }); diff --git a/lib/hooks/useUsersBatch/useUsersBatch.js b/lib/hooks/useUsersBatch/useUsersBatch.js index b4650009..256ef2c6 100644 --- a/lib/hooks/useUsersBatch/useUsersBatch.js +++ b/lib/hooks/useUsersBatch/useUsersBatch.js @@ -25,9 +25,9 @@ export const useUsersBatch = (userIds, options = {}) => { isLoading, } = useQuery( [namespace, userIds], - async () => { + async ({ signal }) => { const response = await batchRequest( - ({ params: searchParams }) => ky.get(USERS_API, { searchParams }).json(), + ({ params: searchParams }) => ky.get(USERS_API, { searchParams, signal }).json(), userIds, ); diff --git a/lib/hooks/useUsersBatch/useUsersBatch.test.js b/lib/hooks/useUsersBatch/useUsersBatch.test.js index 97b71787..68eb8e16 100644 --- a/lib/hooks/useUsersBatch/useUsersBatch.test.js +++ b/lib/hooks/useUsersBatch/useUsersBatch.test.js @@ -10,8 +10,6 @@ import { USERS_API } from '../../constants'; import { useUsersBatch } from './useUsersBatch'; const queryClient = new QueryClient(); - -// eslint-disable-next-line react/prop-types const wrapper = ({ children }) => ( {children} @@ -45,6 +43,7 @@ describe('useUsersBatch', () => { searchParams: expect.objectContaining({ query: userIds.map(id => `id==${id}`).join(' or '), }), + signal: expect.any(AbortSignal), }); }); }); From b161a6936454c7eedae77b1c7e8fac26c5dfd615 Mon Sep 17 00:00:00 2001 From: Yury Saukou Date: Wed, 13 Nov 2024 22:25:48 +0400 Subject: [PATCH 3/3] remove debug --- .../components/VersionCheckbox/VersionCheckbox.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js index 2e3fd021..4a1fbbe0 100644 --- a/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js +++ b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js @@ -26,8 +26,6 @@ describe('VersionCheckbox', () => { it('renders with marked label when name is in context paths', () => { renderVersionCheckbox({}, { paths: ['testName'] }); - screen.debug(); - expect(screen.getByText('Test Label').closest('mark')).toBeInTheDocument(); });