Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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';
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
Loading