Skip to content

Commit

Permalink
UISACQCOMP-230 Move reusable version history components to the ACQ lib (
Browse files Browse the repository at this point in the history
#831)

* UISACQCOMP-230 Move reusable version history components to the ACQ lib

* add unit tests

* remove debug
  • Loading branch information
usavkov-epam authored Nov 14, 2024
1 parent 7f664e2 commit 583d17e
Show file tree
Hide file tree
Showing 19 changed files with 335 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
40 changes: 40 additions & 0 deletions lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js
Original file line number Diff line number Diff line change
@@ -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 ? <mark>{label}</mark> : label;

return (
<Checkbox
checked={Boolean(checked)}
disabled
label={checkboxLabel}
vertical
{...props}
/>
);
};

VersionCheckbox.defaultProps = {
checked: false,
};

VersionCheckbox.propTypes = {
checked: PropTypes.bool,
label: PropTypes.node.isRequired,
name: PropTypes.string.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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(
<VersionViewContext.Provider value={contextValue}>
<VersionCheckbox
{...defaultProps}
{...props}
/>
</VersionViewContext.Provider>,
);
};

describe('VersionCheckbox', () => {
it('renders with marked label when name is in context paths', () => {
renderVersionCheckbox({}, { paths: ['testName'] });

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();
});
});
1 change: 1 addition & 0 deletions lib/VersionHistory/components/VersionCheckbox/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { VersionCheckbox } from './VersionCheckbox';
49 changes: 49 additions & 0 deletions lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js
Original file line number Diff line number Diff line change
@@ -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) || <NoValue />;
const displayValue = isUpdated ? <mark>{content}</mark> : content;

return (
<KeyValue
label={label}
value={displayValue}
/>
);
};

VersionKeyValue.defaultProps = {
multiple: false,
};

VersionKeyValue.propTypes = {
children: PropTypes.node,
label: PropTypes.node.isRequired,
multiple: PropTypes.bool,
name: PropTypes.string.isRequired,
value: PropTypes.node,
};
Original file line number Diff line number Diff line change
@@ -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(
<VersionViewContext.Provider value={contextValue}>
<VersionKeyValue
{...defaultProps}
{...props}
/>
</VersionViewContext.Provider>,
);
};

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();
});
});
1 change: 1 addition & 0 deletions lib/VersionHistory/components/VersionKeyValue/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { VersionKeyValue } from './VersionKeyValue';
76 changes: 76 additions & 0 deletions lib/VersionHistory/components/VersionView/VersionView.js
Original file line number Diff line number Diff line change
@@ -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(() => (
<PaneMenu>
{tags && (
<TagsBadge
disabled
tagsQuantity={tags.length}
/>
)}
<VersionHistoryButton disabled />
</PaneMenu>
), [tags]);

if (isLoading) return <LoadingPane />;

return (
<Pane
id={`${id}-version-view`}
defaultWidth="fill"
onClose={onClose}
lastMenu={lastMenu}
{...props}
>
{
isVersionExist
? children
: (
<Layout
element="span"
className="flex centerContent"
>
<FormattedMessage id="stripes-acq-components.versionHistory.noVersion" />
</Layout>
)
}
</Pane>
);
};

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);
52 changes: 52 additions & 0 deletions lib/VersionHistory/components/VersionView/VersionView.test.js
Original file line number Diff line number Diff line change
@@ -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: <div>Version Content</div>,
id: 'test-id',
isLoading: false,
onClose: jest.fn(),
tags: [{ id: 'tag1' }, { id: 'tag2' }],
versionId: 'version1',
dismissible: true,
};

const renderComponent = (props = {}) => render(
<VersionView
{...defaultProps}
{...props}
/>,
);

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();
});
});
1 change: 1 addition & 0 deletions lib/VersionHistory/components/VersionView/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as VersionView } from './VersionView';
3 changes: 3 additions & 0 deletions lib/VersionHistory/components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { VersionCheckbox } from './VersionCheckbox';
export { VersionKeyValue } from './VersionKeyValue';
export { VersionView } from './VersionView';
1 change: 1 addition & 0 deletions lib/VersionHistory/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './components';
export { getFieldLabels } from './getFieldLabels';
export { getHighlightedFields } from './getHighlightedFields';
export * from './hooks';
Expand Down
1 change: 1 addition & 0 deletions lib/constants/api.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
4 changes: 2 additions & 2 deletions lib/hooks/useLineHoldings/useLineHoldings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
},
Expand Down
2 changes: 1 addition & 1 deletion lib/hooks/useOrganization/useOrganization.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
4 changes: 1 addition & 3 deletions lib/hooks/useOrganization/useOrganization.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<QueryClientProvider client={queryClient}>
{children}
Expand Down Expand Up @@ -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));
});
});
Loading

0 comments on commit 583d17e

Please sign in to comment.