From bec610e82bb4d55720fb798ca9b11c71b9c54f30 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Thu, 8 Feb 2024 15:54:57 +0500 Subject: [PATCH 1/7] UIU-2969: change user type confirmation modal --- CHANGELOG.md | 1 + .../EditSections/EditUserInfo/EditUserInfo.js | 39 ++++++++++--- .../ChangeUserTypeModal.js | 56 +++++++++++++++++++ .../ChangeUserTypeModal.test.js | 17 ++++++ .../components/ChangeUserTypeModal/index.js | 1 + .../EditUserInfo/components/index.js | 1 + translations/ui-users/en.json | 3 + 7 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js create mode 100644 src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js create mode 100644 src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ba2e8f7f..f646efc68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * Enable effective call number column sorting in Open Loans screen. Refs UIU-3002. * User Information in User Edit to display profile picture and update button set. Refs UIU-3005. * Update request header for pay several Fees/fines. Refs UIU-3040. +* Changing user type confirmation modal. Refs UIU-2969. ## [10.0.4](https://github.com/folio-org/ui-users/tree/v10.0.4) (2023-11-10) [Full Changelog](https://github.com/folio-org/ui-users/compare/v10.0.3...v10.0.4) diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.js b/src/components/EditSections/EditUserInfo/EditUserInfo.js index 2aebf9cf8..21fb7c1eb 100644 --- a/src/components/EditSections/EditUserInfo/EditUserInfo.js +++ b/src/components/EditSections/EditUserInfo/EditUserInfo.js @@ -1,12 +1,11 @@ -import _ from 'lodash'; -import React from 'react'; +import get from 'lodash/get'; +import moment from 'moment-timezone'; import PropTypes from 'prop-types'; +import React from 'react'; import { Field } from 'react-final-form'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import moment from 'moment-timezone'; import { OnChange } from 'react-final-form-listeners'; +import { FormattedMessage, injectIntl } from 'react-intl'; -import { ViewMetaData } from '@folio/stripes/smart-components'; import { Button, Select, @@ -19,14 +18,16 @@ import { Modal, ModalFooter, } from '@folio/stripes/components'; +import { ViewMetaData } from '@folio/stripes/smart-components'; import { USER_TYPES, USER_TYPE_FIELD } from '../../../constants'; import { isConsortiumEnabled } from '../../util'; import asyncValidateField from '../../validators/asyncValidateField'; import validateMinDate from '../../validators/validateMinDate'; +import { ChangeUserTypeModal, ProfilePicture } from './components'; + import css from './EditUserInfo.css'; -import ProfilePicture from './components/ProfilePicture'; class EditUserInfo extends React.Component { static propTypes = { @@ -56,6 +57,7 @@ class EditUserInfo extends React.Component { const { initialValues: { patronGroup } } = props; this.state = { showRecalculateModal: false, + showUserTypeModal: false, selectedPatronGroup: patronGroup, }; } @@ -78,6 +80,12 @@ class EditUserInfo extends React.Component { this.setState({ showRecalculateModal: false }); } + setChangedUserType = (userType) => { + const { form: { change } } = this.props; + change(USER_TYPE_FIELD, userType); + this.setState({ showUserTypeModal: false }); + } + calculateNewExpirationDate = (startCalcToday) => { const { initialValues } = this.props; const expirationDate = new Date(initialValues.expirationDate); @@ -94,7 +102,7 @@ class EditUserInfo extends React.Component { getPatronGroupOffset = () => { const selectedPatronGroup = this.props.patronGroups.find(i => i.id === this.state.selectedPatronGroup); - return _.get(selectedPatronGroup, 'expirationOffsetInDays', ''); + return get(selectedPatronGroup, 'expirationOffsetInDays', ''); }; parseExpirationDate = (expirationDate) => { @@ -207,7 +215,7 @@ class EditUserInfo extends React.Component { ].filter(o => o.visible); const offset = this.getPatronGroupOffset(); - const group = _.get(this.props.patronGroups.find(i => i.id === this.state.selectedPatronGroup), 'group', ''); + const group = get(this.props.patronGroups.find(i => i.id === this.state.selectedPatronGroup), 'group', ''); const date = moment(this.calculateNewExpirationDate(true)).format('LL'); const modalFooter = ( @@ -400,7 +408,15 @@ class EditUserInfo extends React.Component { dataOptions={typeOptions} aria-required={isConsortium} required={isConsortium} - /> + > + + {(selectedUserType) => { + if (initialValues.type === USER_TYPES.STAFF && selectedUserType === USER_TYPES.PATRON) { + this.setState({ showUserTypeModal: true }); + } + }} + + @@ -418,6 +434,11 @@ class EditUserInfo extends React.Component { /> + ); } diff --git a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js new file mode 100644 index 000000000..b1ad0b863 --- /dev/null +++ b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js @@ -0,0 +1,56 @@ +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +import { + Button, + Modal, + ModalFooter, +} from '@folio/stripes/components'; + +import { USER_TYPES } from '../../../../../constants'; + +const ChangeUserTypeModal = ({ onChange, initialUserType, open }) => { + const userTypeModalFooter = ( + + + + + ); + + return ( + } + open={open} + > +
+ +
+
+ ); +}; + +ChangeUserTypeModal.propTypes = { + onChange: PropTypes.func.isRequired, + initialUserType: PropTypes.string.isRequired, + open: PropTypes.bool, +}; + +ChangeUserTypeModal.defaultProps = { + open: false, +}; + +export default ChangeUserTypeModal; diff --git a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js new file mode 100644 index 000000000..f061a366c --- /dev/null +++ b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js @@ -0,0 +1,17 @@ +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; + +import { USER_TYPES } from '../../../../../constants'; +import ChangeUserTypeModal from './ChangeUserTypeModal'; + +describe('ChangeUserTypeModal', () => { + it('should render component', () => { + const onChange = jest.fn(); + render(); + + expect(screen.getByTestId('ui-users.information.change.userType.modal.label')).toBeInTheDocument(); + }); +}); diff --git a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/index.js b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/index.js new file mode 100644 index 000000000..d1699e951 --- /dev/null +++ b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/index.js @@ -0,0 +1 @@ +export { default } from './ChangeUserTypeModal'; diff --git a/src/components/EditSections/EditUserInfo/components/index.js b/src/components/EditSections/EditUserInfo/components/index.js index 2497a0f13..35c5af940 100644 --- a/src/components/EditSections/EditUserInfo/components/index.js +++ b/src/components/EditSections/EditUserInfo/components/index.js @@ -1,2 +1,3 @@ /* eslint-disable import/prefer-default-export */ export { default as ProfilePicture } from './ProfilePicture'; +export { default as ChangeUserTypeModal } from './ChangeUserTypeModal'; diff --git a/translations/ui-users/en.json b/translations/ui-users/en.json index 291f6ca50..092e9027e 100644 --- a/translations/ui-users/en.json +++ b/translations/ui-users/en.json @@ -335,6 +335,9 @@ "information.recalculate.modal.button": "Set", "information.recalculate.modal.text": "Library accounts with patron group {group} expire in {offset} days. Do you want to set this user’s account to expire on {date}?", "information.recalculate.modal.label": "Set expiration date?", + "information.change.userType.modal.label": "Changing user type?", + "information.change.userType.modal.button": "Set", + "information.change.userType.modal.text": "Making this change will update the users affiliations and their the permissions they are granted for those affiliations when clicking Save & close. This action can not easily be reversed, you would need to manually update the user's affiliations and permissions to reverse the resulting changes. Would you like to proceed?", "information.recalculate.will.reactivate.user": "User will reactivate after saving", "lostItems.message.noAccessToActualCostPage": "User does not have permission to access \"Lost items needing actual cost\" processing page", From 84d92b6723b812bffd5bdf13cb757b4b9c10fe57 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Thu, 8 Feb 2024 16:11:06 +0500 Subject: [PATCH 2/7] tests: fix failing tests --- .../EditSections/EditUserInfo/EditUserInfo.test.js | 5 ++++- .../ChangeUserTypeModal/ChangeUserTypeModal.test.js | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js index 3d7f7bf7f..37a62bfd2 100644 --- a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js +++ b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js @@ -37,7 +37,10 @@ jest.mock('../../util', () => ({ isConsortiumEnabled: jest.fn(() => true), })); -jest.mock('./components/ProfilePicture', () => jest.fn(() => 'Profile Picture')); +jest.mock('./components', () => ({ + ProfilePicture : jest.fn(() => 'Profile Picture'), + ChangeUserTypeModal: jest.fn(() => 'ChangeUserTypeModal'), +})); const onSubmit = jest.fn(); diff --git a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js index f061a366c..9c89da477 100644 --- a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js +++ b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js @@ -1,10 +1,11 @@ import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { USER_TYPES } from '../../../../../constants'; import ChangeUserTypeModal from './ChangeUserTypeModal'; describe('ChangeUserTypeModal', () => { - it('should render component', () => { + it('should render component', async () => { const onChange = jest.fn(); render( { initialUserType={USER_TYPES.STAFF} />); - expect(screen.getByTestId('ui-users.information.change.userType.modal.label')).toBeInTheDocument(); + expect(screen.getByText('ui-users.information.change.userType.modal.label')).toBeInTheDocument(); + + const cancelButton = screen.getByText('ui-users.cancel'); + + await userEvent.click(cancelButton); + expect(onChange).toHaveBeenCalled(); }); }); From 65d6996531b00577dde89b58b487b0e1a0ff0730 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Thu, 8 Feb 2024 17:23:11 +0500 Subject: [PATCH 3/7] tests: add test coverages --- .../EditUserInfo/EditUserInfo.test.js | 25 ++++++++++++++++++- .../ChangeUserTypeModal.test.js | 20 ++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js index 37a62bfd2..39c7f9679 100644 --- a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js +++ b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js @@ -39,7 +39,14 @@ jest.mock('../../util', () => ({ jest.mock('./components', () => ({ ProfilePicture : jest.fn(() => 'Profile Picture'), - ChangeUserTypeModal: jest.fn(() => 'ChangeUserTypeModal'), + ChangeUserTypeModal: jest.fn(({ onChange, initialUserType }) => { + return ( +
+

ChangeUserTypeModal

+ +
+ ); + }), })); const onSubmit = jest.fn(); @@ -198,6 +205,22 @@ describe('Render Edit User Information component', () => { expect(screen.getByText('Profile Picture')).toBeInTheDocument(); }); + it('should display change user type modal', async () => { + renderEditUserInfo({ + ...props, + initialValues: { + ...props.initialValues, + type: USER_TYPES.STAFF, + }, + }); + expect(screen.getByText('ChangeUserTypeModal')).toBeInTheDocument(); + + const cancelButton = screen.getByText('Cancel'); + + await userEvent.click(cancelButton); + expect(changeMock).toHaveBeenCalled(); + }); + describe('when profilePicture configuration is not enabled', () => { it('should not render profile picture', () => { renderEditUserInfo({ ...props, areProfilePicturesEnabled: false }); diff --git a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js index 9c89da477..b2cbc4ff4 100644 --- a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js +++ b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.test.js @@ -5,7 +5,7 @@ import { USER_TYPES } from '../../../../../constants'; import ChangeUserTypeModal from './ChangeUserTypeModal'; describe('ChangeUserTypeModal', () => { - it('should render component', async () => { + it('should cancel modal confirmation', async () => { const onChange = jest.fn(); render( { await userEvent.click(cancelButton); expect(onChange).toHaveBeenCalled(); }); + + it('should confirm modal confirmation', async () => { + const onChange = jest.fn(); + const initialUserType = USER_TYPES.STAFF; + + render(); + + expect(screen.getByText('ui-users.information.change.userType.modal.label')).toBeInTheDocument(); + + const cancelButton = screen.getByText('ui-users.information.change.userType.modal.button'); + + await userEvent.click(cancelButton); + expect(onChange).toHaveBeenCalled(); + }); }); From 40e2565e33c44cff6d9623c0d9e3c166d1e61725 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Thu, 8 Feb 2024 17:50:32 +0500 Subject: [PATCH 4/7] tests: improve test coverage --- .../EditUserInfo/EditUserInfo.test.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js index 39c7f9679..e47bab08f 100644 --- a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js +++ b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js @@ -39,7 +39,11 @@ jest.mock('../../util', () => ({ jest.mock('./components', () => ({ ProfilePicture : jest.fn(() => 'Profile Picture'), - ChangeUserTypeModal: jest.fn(({ onChange, initialUserType }) => { + ChangeUserTypeModal: jest.fn(({ onChange, initialUserType, open }) => { + if (!open) { + return null; + } + return (

ChangeUserTypeModal

@@ -213,7 +217,16 @@ describe('Render Edit User Information component', () => { type: USER_TYPES.STAFF, }, }); - expect(screen.getByText('ChangeUserTypeModal')).toBeInTheDocument(); + // expect(screen.getByText('ChangeUserTypeModal')).toBeInTheDocument(); + + await userEvent.selectOptions(screen.getByRole('combobox', { name: /ui-users.information.userType/i }), USER_TYPES.PATRON); + + const option = screen.getByRole('option', { name: /ui-users.information.userType.patron/i }); + + + await userEvent.click(option); + + await screen.findByText('ChangeUserTypeModal'); const cancelButton = screen.getByText('Cancel'); From 910f9978638a7f34e553a1aea2dedfecf2043f4d Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Thu, 8 Feb 2024 22:59:09 +0500 Subject: [PATCH 5/7] refactor code --- .../EditSections/EditUserInfo/EditUserInfo.js | 3 ++- .../EditSections/EditUserInfo/EditUserInfo.test.js | 10 +++++----- .../ChangeUserTypeModal/ChangeUserTypeModal.js | 6 ++---- .../ChangeUserTypeModal/ChangeUserTypeModal.test.js | 8 ++++---- translations/ui-users/en.json | 4 ++-- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.js b/src/components/EditSections/EditUserInfo/EditUserInfo.js index 21fb7c1eb..05c2d044b 100644 --- a/src/components/EditSections/EditUserInfo/EditUserInfo.js +++ b/src/components/EditSections/EditUserInfo/EditUserInfo.js @@ -411,7 +411,8 @@ class EditUserInfo extends React.Component { > {(selectedUserType) => { - if (initialValues.type === USER_TYPES.STAFF && selectedUserType === USER_TYPES.PATRON) { + const shouldDisplayModal = isConsortium && initialValues.type === USER_TYPES.STAFF && selectedUserType === USER_TYPES.PATRON; + if (shouldDisplayModal) { this.setState({ showUserTypeModal: true }); } }} diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js index e47bab08f..857e80b43 100644 --- a/src/components/EditSections/EditUserInfo/EditUserInfo.test.js +++ b/src/components/EditSections/EditUserInfo/EditUserInfo.test.js @@ -209,7 +209,8 @@ describe('Render Edit User Information component', () => { expect(screen.getByText('Profile Picture')).toBeInTheDocument(); }); - it('should display change user type modal', async () => { + it('should not change user type onClick `Cancel` button', async () => { + isConsortiumEnabled.mockClear().mockReturnValue(true); renderEditUserInfo({ ...props, initialValues: { @@ -217,11 +218,10 @@ describe('Render Edit User Information component', () => { type: USER_TYPES.STAFF, }, }); - // expect(screen.getByText('ChangeUserTypeModal')).toBeInTheDocument(); - await userEvent.selectOptions(screen.getByRole('combobox', { name: /ui-users.information.userType/i }), USER_TYPES.PATRON); + await userEvent.selectOptions(screen.getByRole('combobox', { name: 'ui-users.information.userType' }), USER_TYPES.PATRON); - const option = screen.getByRole('option', { name: /ui-users.information.userType.patron/i }); + const option = screen.getByRole('option', { name: 'ui-users.information.userType.patron' }); await userEvent.click(option); @@ -231,7 +231,7 @@ describe('Render Edit User Information component', () => { const cancelButton = screen.getByText('Cancel'); await userEvent.click(cancelButton); - expect(changeMock).toHaveBeenCalled(); + expect(changeMock).toHaveBeenCalledWith('type', USER_TYPES.STAFF); }); describe('when profilePicture configuration is not enabled', () => { diff --git a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js index b1ad0b863..dece42bc5 100644 --- a/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js +++ b/src/components/EditSections/EditUserInfo/components/ChangeUserTypeModal/ChangeUserTypeModal.js @@ -16,7 +16,7 @@ const ChangeUserTypeModal = ({ onChange, initialUserType, open }) => { id="userType-modal-btn" onClick={() => onChange(USER_TYPES.PATRON)} > - +