From df2806b2ccd1be04af8fa4bd74f0abfc08f6d19b Mon Sep 17 00:00:00 2001 From: Dmytro-Melnyshyn <77053927+Dmytro-Melnyshyn@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:07:51 +0200 Subject: [PATCH] UIQM-716: Consolidate routes based on MARC type for bib and authority records to avoid page refresh after redirecting from the create page to the edit one. (#756) --- CHANGELOG.md | 4 + package.json | 2 +- src/MarcRoute/MarcRoute.js | 22 +- src/MarcRoute/MarcRoute.test.js | 2 +- src/QuickMarc.js | 65 +- src/QuickMarc.test.js | 51 +- src/QuickMarcEditor/QuickMarcCreateWrapper.js | 208 -- .../QuickMarcCreateWrapper.test.js | 786 ------- src/QuickMarcEditor/QuickMarcDeriveWrapper.js | 178 -- .../QuickMarcDeriveWrapper.test.js | 615 ----- src/QuickMarcEditor/QuickMarcEditWrapper.js | 283 --- .../QuickMarcEditWrapper.test.js | 955 -------- src/QuickMarcEditor/QuickMarcEditor.js | 33 +- src/QuickMarcEditor/QuickMarcEditor.test.js | 5 - .../QuickMarcEditorContainer.js | 102 +- .../QuickMarcEditorContainer.test.js | 31 +- src/QuickMarcEditor/index.js | 3 - src/QuickMarcEditor/useSaveRecord/index.js | 1 + .../useSaveRecord/useSaveRecord.js | 167 ++ .../useSaveRecord/useSaveRecord.test.js | 1997 +++++++++++++++++ .../useSaveRecord/useSubmitRecord/index.js | 1 + .../useSubmitRecord/useSumbitRecord.js | 396 ++++ src/QuickMarcEditor/utils.js | 7 + .../QuickMarcContext/QuickMarcContext.js | 29 +- test/jest/helpers/harness.js | 16 +- 25 files changed, 2779 insertions(+), 3180 deletions(-) delete mode 100644 src/QuickMarcEditor/QuickMarcCreateWrapper.js delete mode 100644 src/QuickMarcEditor/QuickMarcCreateWrapper.test.js delete mode 100644 src/QuickMarcEditor/QuickMarcDeriveWrapper.js delete mode 100644 src/QuickMarcEditor/QuickMarcDeriveWrapper.test.js delete mode 100644 src/QuickMarcEditor/QuickMarcEditWrapper.js delete mode 100644 src/QuickMarcEditor/QuickMarcEditWrapper.test.js create mode 100644 src/QuickMarcEditor/useSaveRecord/index.js create mode 100644 src/QuickMarcEditor/useSaveRecord/useSaveRecord.js create mode 100644 src/QuickMarcEditor/useSaveRecord/useSaveRecord.test.js create mode 100644 src/QuickMarcEditor/useSaveRecord/useSubmitRecord/index.js create mode 100644 src/QuickMarcEditor/useSaveRecord/useSubmitRecord/useSumbitRecord.js diff --git a/CHANGELOG.md b/CHANGELOG.md index abe8543b..6447966c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change history for ui-quick-marc +## [10.0.0] (IN PROGRESS) + +* [UIQM-716](https://issues.folio.org/browse/UIQM-716) *BREAKING* Consolidate routes based on MARC type for bib and authority records to avoid page refresh after redirecting from the create page to the edit one. + ## [9.0.1] (IN PROGRESS) * [UIQM-725](https://issues.folio.org/browse/UIQM-725) Fix wrong error message while saving MARC Bib record with invalid LDR position values. diff --git a/package.json b/package.json index aa506097..17e8c8de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@folio/quick-marc", - "version": "9.0.0", + "version": "10.0.0", "description": "Quick MARC editor", "main": "index.js", "repository": "", diff --git a/src/MarcRoute/MarcRoute.js b/src/MarcRoute/MarcRoute.js index 77393540..d3beec1e 100644 --- a/src/MarcRoute/MarcRoute.js +++ b/src/MarcRoute/MarcRoute.js @@ -14,12 +14,14 @@ import { import { QuickMarcEditorContainer } from '../QuickMarcEditor'; import { applyCentralTenantInHeaders } from '../QuickMarcEditor/utils'; import { QUICK_MARC_ACTIONS } from '../QuickMarcEditor/constants'; +import { QuickMarcProvider } from '../contexts'; const MarcRoute = ({ externalRecordPath, path, permission, routeProps, + basePath, onClose, onSave, }) => { @@ -64,13 +66,18 @@ const MarcRoute = ({ path={path} key={path} render={() => ( - + + + )} /> ); @@ -81,6 +88,7 @@ MarcRoute.propTypes = { path: PropTypes.string.isRequired, permission: PropTypes.string, routeProps: PropTypes.object.isRequired, + basePath: PropTypes.string.isRequired, onClose: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired, }; diff --git a/src/MarcRoute/MarcRoute.test.js b/src/MarcRoute/MarcRoute.test.js index f25f8eff..ec220bb2 100644 --- a/src/MarcRoute/MarcRoute.test.js +++ b/src/MarcRoute/MarcRoute.test.js @@ -28,7 +28,7 @@ const renderMarcRoute = ({ history, ...props } = {}) => (render( {}} diff --git a/src/QuickMarc.js b/src/QuickMarc.js index 060220a8..a4e002de 100644 --- a/src/QuickMarc.js +++ b/src/QuickMarc.js @@ -2,16 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Switch, + useLocation, } from 'react-router-dom'; import { CommandList } from '@folio/stripes/components'; import { MarcRoute } from './MarcRoute'; -import { - QuickMarcDeriveWrapper, - QuickMarcCreateWrapper, - QuickMarcEditWrapper, -} from './QuickMarcEditor'; import { QUICK_MARC_ACTIONS } from './QuickMarcEditor/constants'; import { MARC_TYPES, @@ -24,32 +20,34 @@ const QuickMarc = ({ onClose, onSave, }) => { + const location = useLocation(); + + const permissionsMap = { + 'create-bibliographic': 'ui-quick-marc.quick-marc-editor.create', + 'edit-bibliographic': 'ui-quick-marc.quick-marc-editor.all', + 'derive-bibliographic': 'ui-quick-marc.quick-marc-editor.derive.execute', + 'create-authority': 'ui-quick-marc.quick-marc-authorities-editor.create', + 'edit-authority': '', // ui-quick-marc.quick-marc-authorities-editor.all + }; + + // .../some-path/create-bibliographic => [, create-bibliographic, create, bibliographic] + const [, page, action] = location.pathname.match(/\/((edit|create|derive)-(bibliographic|authority|holdings))/) || []; + const editorRoutesConfig = [ { - path: `${basePath}/edit-bib/:externalId`, - permission: 'ui-quick-marc.quick-marc-editor.all', + path: `${basePath}/:action-bibliographic/:externalId?`, + permission: permissionsMap[page], props: { - action: QUICK_MARC_ACTIONS.EDIT, - wrapper: QuickMarcEditWrapper, + action, marcType: MARC_TYPES.BIB, }, }, { - path: `${basePath}/duplicate-bib/:externalId`, - permission: 'ui-quick-marc.quick-marc-editor.derive.execute', + path: `${basePath}/:action-authority/:externalId?`, + permission: permissionsMap[page], props: { - action: QUICK_MARC_ACTIONS.DERIVE, - wrapper: QuickMarcDeriveWrapper, - marcType: MARC_TYPES.BIB, - }, - }, - { - path: `${basePath}/create-bib`, - permission: 'ui-quick-marc.quick-marc-editor.create', - props: { - action: QUICK_MARC_ACTIONS.CREATE, - wrapper: QuickMarcCreateWrapper, - marcType: MARC_TYPES.BIB, + action, + marcType: MARC_TYPES.AUTHORITY, }, }, { @@ -57,7 +55,6 @@ const QuickMarc = ({ permission: 'ui-quick-marc.quick-marc-holdings-editor.create', props: { action: QUICK_MARC_ACTIONS.CREATE, - wrapper: QuickMarcCreateWrapper, marcType: MARC_TYPES.HOLDINGS, }, }, @@ -66,28 +63,9 @@ const QuickMarc = ({ permission: 'ui-quick-marc.quick-marc-holdings-editor.all', props: { action: QUICK_MARC_ACTIONS.EDIT, - wrapper: QuickMarcEditWrapper, marcType: MARC_TYPES.HOLDINGS, }, }, - { - path: `${basePath}/create-authority`, - permission: 'ui-quick-marc.quick-marc-authorities-editor.create', - props: { - action: QUICK_MARC_ACTIONS.CREATE, - wrapper: QuickMarcCreateWrapper, - marcType: MARC_TYPES.AUTHORITY, - }, - }, - { - path: `${basePath}/edit-authority/:externalId`, - // permission: 'ui-quick-marc.quick-marc-authorities-editor.all', - props: { - action: QUICK_MARC_ACTIONS.EDIT, - wrapper: QuickMarcEditWrapper, - marcType: MARC_TYPES.AUTHORITY, - }, - }, ]; return ( @@ -108,6 +86,7 @@ const QuickMarc = ({ path={path} permission={permission} routeProps={routeProps} + basePath={basePath} onClose={onClose} onSave={onSave} /> diff --git a/src/QuickMarc.test.js b/src/QuickMarc.test.js index 74a34126..e4d97555 100644 --- a/src/QuickMarc.test.js +++ b/src/QuickMarc.test.js @@ -8,6 +8,9 @@ import { createMemoryHistory } from 'history'; import QuickMarc from './QuickMarc'; import Harness from '../test/jest/helpers/harness'; +import { QuickMarcProvider } from './contexts'; +import { QUICK_MARC_ACTIONS } from './QuickMarcEditor/constants'; +import { MARC_TYPES } from './common'; jest.mock('@folio/stripes/core', () => ({ ...jest.requireActual('@folio/stripes/core'), @@ -19,19 +22,25 @@ jest.mock('@folio/stripes/core', () => ({ jest.mock('./QuickMarcEditor', () => { return { - QuickMarcEditorContainer: ({ action }) => QuickMarcEditorContainer {action}, + QuickMarcEditorContainer: () => QuickMarcEditorContainer, }; }); +jest.mock('./contexts', () => ({ + QuickMarcProvider: jest.fn(({ children }) =>
{children}
), +})); + const mockOnSave = jest.fn(); const mockOnClose = jest.fn(); +const basePath = '/some-path'; + const renderQuickMarc = (props = {}) => (render( , @@ -44,39 +53,63 @@ describe('Given Quick Marc', () => { history = createMemoryHistory(); }); - describe('When visiting "duplicate" route', () => { + describe('When visiting "derive" route', () => { beforeEach(() => { - history.push('/some-path/duplicate-bib/1234'); + history.push(`${basePath}/derive-bibliographic/1234`); }); it('should display correct route', () => { const { getByText } = renderQuickMarc({ history }); - expect(getByText('QuickMarcEditorContainer derive')).toBeDefined(); + const expectedProps = { + action: QUICK_MARC_ACTIONS.DERIVE, + marcType: MARC_TYPES.BIB, + basePath, + children: expect.anything(), + }; + + expect(getByText('QuickMarcEditorContainer')).toBeDefined(); + expect(QuickMarcProvider).toHaveBeenCalledWith(expect.objectContaining(expectedProps), {}); }); }); describe('When visiting "edit" route', () => { beforeEach(() => { - history.push('/some-path/edit-bib/1234'); + history.push(`${basePath}/edit-bibliographic/1234`); }); it('should display correct route', () => { const { getByText } = renderQuickMarc({ history }); - expect(getByText('QuickMarcEditorContainer edit')).toBeDefined(); + const expectedProps = { + action: QUICK_MARC_ACTIONS.EDIT, + marcType: MARC_TYPES.BIB, + basePath, + children: expect.anything(), + }; + + expect(getByText('QuickMarcEditorContainer')).toBeDefined(); + expect(QuickMarcProvider).toHaveBeenCalledWith(expect.objectContaining(expectedProps), {}); }); }); describe('When visiting "create" route', () => { beforeEach(() => { - history.push('/some-path/create-holdings/1234'); + history.push(`${basePath}/create-holdings/1234`); }); it('should display correct route', () => { const { getByText } = renderQuickMarc({ history }); - expect(getByText('QuickMarcEditorContainer create')).toBeDefined(); + const expectedProps = { + action: QUICK_MARC_ACTIONS.CREATE, + marcType: MARC_TYPES.HOLDINGS, + basePath, + children: expect.anything(), + }; + + expect(getByText('QuickMarcEditorContainer')).toBeDefined(); + expect(QuickMarcProvider).toHaveBeenCalledWith(expect.objectContaining(expectedProps), {}); }); }); }); diff --git a/src/QuickMarcEditor/QuickMarcCreateWrapper.js b/src/QuickMarcEditor/QuickMarcCreateWrapper.js deleted file mode 100644 index e5fe3a00..00000000 --- a/src/QuickMarcEditor/QuickMarcCreateWrapper.js +++ /dev/null @@ -1,208 +0,0 @@ -import React, { - useCallback, - useContext, - useMemo, - useState, -} from 'react'; -import { useLocation } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import flow from 'lodash/flow'; -import noop from 'lodash/noop'; -import isEmpty from 'lodash/isEmpty'; - -import { useShowCallout } from '@folio/stripes-acq-components'; -import { useStripes } from '@folio/stripes/core'; - -import QuickMarcEditor from './QuickMarcEditor'; -import { QuickMarcContext } from '../contexts'; -import getQuickMarcRecordStatus from './getQuickMarcRecordStatus'; -import { - useAuthorityLinking, - useValidation, -} from '../hooks'; -import { QUICK_MARC_ACTIONS } from './constants'; -import { MARC_TYPES } from '../common/constants'; -import { - hydrateMarcRecord, - formatLeaderForSubmit, - autopopulateSubfieldSection, - cleanBytesFields, - parseHttpError, - removeDeletedRecords, - saveLinksToNewRecord, - recordHasLinks, - combineSplitFields, - autopopulateFixedField, - autopopulatePhysDescriptionField, - autopopulateMaterialCharsField, - autopopulateIndicators, - removeRowsWithoutContent, - applyCentralTenantInHeaders, -} from './utils'; - -const propTypes = { - action: PropTypes.oneOf(Object.values(QUICK_MARC_ACTIONS)).isRequired, - initialValues: PropTypes.object.isRequired, - instance: PropTypes.object, - locations: PropTypes.object.isRequired, - marcType: PropTypes.oneOf(Object.values(MARC_TYPES)).isRequired, - fixedFieldSpec: PropTypes.object.isRequired, - mutator: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, - onSave: PropTypes.func.isRequired, -}; - -const QuickMarcCreateWrapper = ({ - action, - instance, - onClose, - onSave, - initialValues, - mutator, - marcType, - fixedFieldSpec, - locations, -}) => { - const stripes = useStripes(); - const location = useLocation(); - const showCallout = useShowCallout(); - const [httpError, setHttpError] = useState(null); - const { linkableBibFields, actualizeLinks, linkingRules, sourceFiles } = useAuthorityLinking({ marcType, action }); - const { validationErrorsRef } = useContext(QuickMarcContext); - - const isRequestToCentralTenantFromMember = applyCentralTenantInHeaders(location, stripes, marcType); - const centralTenantId = stripes.user.user.consortium?.centralTenantId; - const tenantId = isRequestToCentralTenantFromMember ? centralTenantId : ''; - - const validationContext = useMemo(() => ({ - initialValues, - marcType, - action: QUICK_MARC_ACTIONS.CREATE, - locations, - linkableBibFields, - linkingRules, - sourceFiles, - fixedFieldSpec, - instanceId: instance.id, - }), [initialValues, marcType, locations, linkableBibFields, linkingRules, sourceFiles, fixedFieldSpec, instance.id]); - const { validate } = useValidation(validationContext, tenantId); - - const prepareForSubmit = useCallback((formValues) => { - const formValuesForCreate = flow( - removeDeletedRecords, - removeRowsWithoutContent, - autopopulateIndicators, - marcRecord => autopopulateFixedField(marcRecord, marcType, fixedFieldSpec), - autopopulatePhysDescriptionField, - autopopulateMaterialCharsField, - marcRecord => autopopulateSubfieldSection(marcRecord, marcType), - marcRecord => cleanBytesFields(marcRecord, fixedFieldSpec, marcType), - marcRecord => formatLeaderForSubmit(marcType, marcRecord), - combineSplitFields, - )(formValues); - - return formValuesForCreate; - }, [marcType, fixedFieldSpec]); - - const runValidation = useCallback(async (formValues) => { - const formValuesForValidation = prepareForSubmit(formValues); - - return validate(formValuesForValidation.records); - }, [validate, prepareForSubmit]); - - const redirectToRecord = useCallback(async (externalId, instanceId) => { - if (marcType === MARC_TYPES.HOLDINGS) { - await onSave(`${instanceId}/${externalId}`); - } else { - await onSave(externalId); - } - }, [onSave, marcType]); - - const onSubmit = useCallback(async (formValues, _api, complete) => { - // if validation has any issues - cancel submit - if (!isEmpty(validationErrorsRef.current)) { - return complete(); - } - - const formValuesToProcess = prepareForSubmit(formValues); - - let formValuesToHydrate; - - try { - if (marcType === MARC_TYPES.BIB) { - formValuesToHydrate = await actualizeLinks(formValuesToProcess); - } else { - formValuesToHydrate = formValuesToProcess; - } - } catch (errorResponse) { - const parsedError = await parseHttpError(errorResponse); - - setHttpError(parsedError); - - return null; - } - - formValuesToHydrate._actionType = 'create'; - - const formValuesForCreate = hydrateMarcRecord(formValuesToHydrate); - - return mutator.quickMarcEditMarcRecord.POST(formValuesForCreate) - .then(async ({ qmRecordId }) => { - const instanceId = formValues.externalId; - - showCallout({ messageId: 'ui-quick-marc.record.save.success.processing' }); - - try { - const { externalId } = await getQuickMarcRecordStatus({ - quickMarcRecordStatusGETRequest: mutator.quickMarcRecordStatus.GET, - qmRecordId, - showCallout, - }); - - showCallout({ messageId: 'ui-quick-marc.record.saveNew.success' }); - - if (marcType === MARC_TYPES.BIB && recordHasLinks(formValuesForCreate.fields)) { - await saveLinksToNewRecord(mutator, externalId, formValuesForCreate) - .catch(noop); - } - - await redirectToRecord(externalId, instanceId); - } catch (e) { - showCallout({ - messageId: 'ui-quick-marc.record.saveNew.error', - type: 'error', - }); - } - }) - .catch(async (errorResponse) => { - const parsedError = await parseHttpError(errorResponse); - - setHttpError(parsedError); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - onClose, - showCallout, - prepareForSubmit, - actualizeLinks, - validationErrorsRef, - ]); - - return ( - - ); -}; - -QuickMarcCreateWrapper.propTypes = propTypes; - -export default QuickMarcCreateWrapper; diff --git a/src/QuickMarcEditor/QuickMarcCreateWrapper.test.js b/src/QuickMarcEditor/QuickMarcCreateWrapper.test.js deleted file mode 100644 index 829eb1d1..00000000 --- a/src/QuickMarcEditor/QuickMarcCreateWrapper.test.js +++ /dev/null @@ -1,786 +0,0 @@ -import React from 'react'; -import { - act, - render, - fireEvent, - screen, - waitFor, -} from '@folio/jest-config-stripes/testing-library/react'; -import faker from 'faker'; -import noop from 'lodash/noop'; - -import { runAxeTest } from '@folio/stripes-testing'; - -import '@folio/stripes-acq-components/test/jest/__mock__'; - -import QuickMarcCreateWrapper from './QuickMarcCreateWrapper'; -import QuickMarcEditor from './QuickMarcEditor'; -import { MARC_TYPES } from '../common/constants'; -import { QUICK_MARC_ACTIONS } from './constants'; - -import Harness from '../../test/jest/helpers/harness'; -import { useAuthorityLinking } from '../hooks'; -import { saveLinksToNewRecord } from './utils'; -import { - authorityLeader, - bibLeader, - bibLeaderString, - holdingsLeader, -} from '../../test/jest/fixtures/leaders'; -import fixedFieldSpecBib from '../../test/mocks/fixedFieldSpecBib'; -import fixedFieldSpecAuth from '../../test/mocks/fixedFieldSpecAuth'; - -const runWithDelayedPromise = (fn, delay) => () => { - return new Promise(resolve => setTimeout(() => resolve(fn()), delay)); -}; - -jest.mock('./utils', () => ({ - ...jest.requireActual('./utils'), - saveLinksToNewRecord: jest.fn(), -})); - -jest.mock('./QuickMarcEditor', () => { - const RealQuickMarcEditor = jest.requireActual('./QuickMarcEditor').default; - - return jest.fn(props => ); -}); -jest.mock('./getQuickMarcRecordStatus', () => { - return jest.fn().mockResolvedValue({ - externalId: 'externalId-1', - }); -}); - -jest.mock('react-final-form', () => ({ - ...jest.requireActual('react-final-form'), - FormSpy: jest.fn(() => (FormSpy)), -})); - -jest.mock('../hooks', () => ({ - ...jest.requireActual('../hooks'), - useAuthorityLinking: jest.fn(), -})); - -jest.mock('../queries', () => ({ - ...jest.requireActual('../queries'), - useLccnDuplicateConfig: jest.fn().mockReturnValue({ - isLoading: false, - duplicateLccnCheckingEnabled: false, - }), -})); - -const mockRecords = { - [MARC_TYPES.HOLDINGS]: [ - { - tag: 'LDR', - content: holdingsLeader, - id: 'LDR', - }, { - tag: '001', - id: '595a98e6-8e59-448d-b866-cd039b990423', - }, { - tag: '004', - content: 'in00000000022', - id: '93213747-46fb-4861-b8e8-8774bf4a46a4', - }, { - tag: '008', - content: {}, - indicators: ['\\', '\\'], - }, { - tag: '852', - content: '$b KU/CC/DI/A $t 3 $h M3 $i .M93 1955 $m + $x Rec\'d in Music Lib ;', - indicators: ['0', '1'], - id: '6abdaf9b-ac58-4f83-9687-73c939c3c21a', - }, { - tag: '014', - content: '$a ABS3966CU004', - indicators: ['1', '\\'], - id: '5aa1a643-b9f2-47e8-bb68-6c6457b5c9c5', - }, { - tag: '005', - id: '5aa1a643-b9f2-47e8-bb68-6c6457b5c9c5', - }, { - tag: '999', - indicators: ['f', 'f'], - id: '4a844042-5c7e-4e71-823e-599582a5d7ab', - }, - ], - [MARC_TYPES.BIB]: [ - { - 'tag': 'LDR', - 'content': bibLeader, - 'id': 'LDR', - }, { - 'tag': '001', - 'content': 'in00000000003', - 'id': '595a98e6-8e59-448d-b866-cd039b990423', - }, { - 'tag': '008', - 'content': { - 'Type': 'a', - 'BLvl': 'm', - 'Desc': 'c', - 'Entered': '211212', - 'DtSt': '|', - 'Date1': '2016', - 'Date2': '||||', - 'Ctry': '|||', - 'Lang': 'mul', - 'MRec': '|', - 'Srce': '|', - 'Ills': ['|', '|', '|', '|'], - 'Audn': '|', - 'Form': '\\', - 'Cont': ['\\', '\\', '\\', '\\'], - 'GPub': '\\', - 'Conf': '\\', - 'Fest': '|', - 'Indx': '|', - 'LitF': '|', - 'Biog': '|', - }, - }, { - 'id': '0b3938b5-3ed6-45a0-90f9-fcf24dfebc7c', - 'tag': '100', - 'content': '$a Ma, Wei $0 id.loc.gov/authorities/names/n84160718 $9 495884af-28d7-4d69-85e4-e84c5de693db', - 'indicators': ['\\', '\\'], - '_isAdded': true, - '_isLinked': true, - 'prevContent': '$a test', - 'linkDetails': { - 'authorityNaturalId': 'n84160718', - 'authorityId': '495884af-28d7-4d69-85e4-e84c5de693db', - 'linkingRuleId': 1, - 'status': 'NEW', - }, - 'subfieldGroups': { - 'controlled': '$a Ma, Wei', - 'uncontrolledAlpha': '', - 'zeroSubfield': '$0 id.loc.gov/authorities/names/n84160718', - 'nineSubfield': '$9 495884af-28d7-4d69-85e4-e84c5de693db', - 'uncontrolledNumber': '', - }, - }, { - 'content': '$a Title', - 'tag': '245', - }, - ], - [MARC_TYPES.AUTHORITY]: [ - { - 'tag': 'LDR', - 'content': authorityLeader, - 'id': 'LDR', - }, - { - 'tag': '001', - 'content': 'value1', - }, - { - 'tag': '008', - 'content': { - 'Undef_18': '\\\\\\\\\\\\\\\\\\\\', - 'Undef_30': '\\', - 'Undef_34': '\\\\\\\\', - 'Geo Subd': '\\', - 'Roman': '\\', - 'Lang': '\\', - 'Kind rec': '\\', - 'Cat Rules': '\\', - 'SH Sys': '\\', - 'Series': '\\', - 'Numb Series': '\\', - 'Main use': '\\', - 'Subj use': '\\', - 'Series use': '\\', - 'Type Subd': '\\', - 'Govt Ag': '\\', - 'RefEval': '\\', - 'RecUpd': '\\', - 'Pers Name': '\\', - 'Level Est': '\\', - 'Mod Rec Est': '\\', - 'Source': '\\', - }, - }, - { - 'tag': '010', - 'content': '$a value1', - 'indicators': ['\\', '\\'], - }, - { - 'tag': '100', - 'content': '$a value2', - 'indicators': ['\\', '\\'], - }, - ], -}; - -const mockLeaders = { - [MARC_TYPES.BIB]: bibLeader, - [MARC_TYPES.HOLDINGS]: holdingsLeader, - [MARC_TYPES.AUTHORITY]: authorityLeader, -}; - -const mockFormValues = jest.fn((marcType) => ({ - fields: undefined, - externalHrid: 'in00000000022', - externalId: '00000000-0000-0000-0000-000000000000', - leader: mockLeaders[marcType], - marcFormat: marcType.toUpperCase(), - parsedRecordDtoId: '00000000-0000-0000-0000-000000000000', - records: mockRecords[marcType], - relatedRecordVersion: 1, - suppressDiscovery: false, - updateInfo: { recordState: 'NEW' }, -})); - -const mockSpecs = { - [MARC_TYPES.BIB]: fixedFieldSpecBib, - [MARC_TYPES.AUTHORITY]: fixedFieldSpecAuth, -}; - -jest.mock('@folio/stripes/final-form', () => () => (Component) => ({ - onSubmit, - marcType, - ...props -}) => { - const formValues = mockFormValues(marcType); - - return ( - onSubmit(formValues)} - form={{ - mutators: { - markRecordsLinked: jest.fn(), - }, - reset: jest.fn(), - getState: jest.fn().mockReturnValue({ values: formValues }), - }} - marcType={marcType} - {...props} - /> - ); -}); - -const mockActualizeLinks = jest.fn((formValuesToProcess) => Promise.resolve(formValuesToProcess)); -const mockShowCallout = jest.fn(); - -jest.mock('@folio/stripes-acq-components', () => ({ - ...jest.requireActual('@folio/stripes-acq-components'), - useShowCallout: jest.fn(() => mockShowCallout), -})); - -jest.mock('./QuickMarcEditorRows', () => { - return { - QuickMarcEditorRows: () => (QuickMarcEditorRows), - }; -}); - -jest.mock('./QuickMarcRecordInfo', () => { - return { - QuickMarcRecordInfo: () => QuickMarcRecordInfo, - }; -}); - -jest.mock('./constants', () => ({ - ...jest.requireActual('./constants'), - QM_RECORD_STATUS_TIMEOUT: 5, - QM_RECORD_STATUS_BAIL_TIME: 20, -})); - -const getInstance = () => ({ - id: faker.random.uuid(), - title: 'ui-inventory.instanceRecordTitle', -}); - -const record = { - id: faker.random.uuid(), - leader: bibLeader, - fields: [], -}; - -const locations = [{ - code: 'KU/CC/DI/A', -}]; - -const renderQuickMarcCreateWrapper = ({ - instance, - onClose = noop, - onSave = noop, - mutator, - marcType = MARC_TYPES.HOLDINGS, -}) => (render( - - - , -)); - -describe('Given QuickMarcCreateWrapper', () => { - let mutator; - let instance; - - beforeEach(() => { - instance = getInstance(); - mutator = { - quickMarcEditInstance: { - GET: () => Promise.resolve(instance), - }, - quickMarcEditMarcRecord: { - GET: jest.fn(() => Promise.resolve(record)), - POST: jest.fn(() => Promise.resolve({})), - PUT: jest.fn().mockResolvedValue({}), - }, - quickMarcRecordStatus: { - GET: jest.fn(() => Promise.resolve({})), - }, - }; - - useAuthorityLinking.mockReturnValue({ - linkableBibFields: [], - actualizeLinks: mockActualizeLinks, - autoLinkingEnabled: true, - autoLinkableBibFields: [], - autoLinkAuthority: jest.fn(), - linkingRules: [], - sourceFiles: [], - }); - - jest.clearAllMocks(); - }); - - it('should render with no axe errors', async () => { - const { container } = renderQuickMarcCreateWrapper({ - instance, - mutator, - }); - - await runAxeTest({ - rootNode: container, - }); - }); - - describe('when click on cancel pane button', () => { - const onClose = jest.fn(); - - it('should handle onClose action', () => { - const { getByText } = renderQuickMarcCreateWrapper({ - instance, - mutator, - onClose, - }); - - fireEvent.click(getByText('stripes-acq-components.FormFooter.cancel')); - - expect(onClose).toHaveBeenCalled(); - }); - }); - - describe('when click on save button', () => { - it('should show on save message and redirect on load page', async () => { - let getByText; - - await act(async () => { - getByText = renderQuickMarcCreateWrapper({ - instance, - mutator, - }).getByText; - }); - - fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); - - await waitFor(() => { - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.save.success.processing' }); - }); - }); - - it('should create bib record with correct payload', async () => { - renderQuickMarcCreateWrapper({ - instance, - mutator, - marcType: MARC_TYPES.BIB, - }); - - const formValues = { - 'externalId': '00000000-0000-0000-0000-000000000000', - 'leader': { - 'Record length': '00000', - 'Status': 'n', - 'Type': '\\', - 'BLvl': '\\', - 'Ctrl': '\\', - '9-16 positions': 'a2200000', - 'ELvl': 'u', - 'Desc': 'u', - 'MultiLvl': '\\', - '20-23 positions': '4500', - }, - 'records': [ - { - 'tag': 'LDR', - 'content': { - 'Record length': '00000', - 'Status': 'n', - 'Type': 'a', - 'BLvl': 'm', - 'Ctrl': '\\', - '9-16 positions': 'a2200000', - 'ELvl': 'u', - 'Desc': 'u', - 'MultiLvl': '\\', - '20-23 positions': '4500', - }, - 'id': 'LDR', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '001', - 'id': '977127cc-efdd-4aa2-b941-ba92f4606e2c', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '005', - 'id': '4b643154-0d45-4876-9afb-1e2a21d74055', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '008', - 'id': 'a15db153-9d34-4dc8-b246-31f1e1254d33', - 'content': { - 'Type': 'a', - 'BLvl': 'm', - 'DtSt': 'u', - 'Date1': '\\\\\\\\', - 'Date2': '\\\\\\\\', - 'Ctry': '\\\\\\', - 'Ills': ['p', 'o', 'm', 'l'], - 'Audn': 'j', - 'Form': 's', - 'Cont': ['6', '5', '2', 'y'], - 'GPub': 's', - 'Conf': '0', - 'Fest': '1', - 'Indx': '0', - 'LitF': 'p', - 'Biog': '\\', - 'Lang': '\\\\\\', - 'MRec': '\\', - 'Srce': '\\', - }, - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '245', - 'id': 'ad563802-dd44-4c7a-b429-79f2bd877aef', - 'indicators': ['\\', '\\'], - 'content': '$a rec2', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '999', - 'id': '7ad9a9ab-3107-4fee-acd7-19cc4249b12e', - 'indicators': ['f', 'f'], - '_isDeleted': false, - '_isLinked': false, - }, - ], - 'parsedRecordDtoId': '00000000-0000-0000-0000-000000000000', - 'relatedRecordVersion': 1, - 'marcFormat': 'BIBLIOGRAPHIC', - 'suppressDiscovery': false, - 'updateInfo': { - 'recordState': 'NEW', - }, - }; - - const formValuesForCreate = { - 'externalId': '00000000-0000-0000-0000-000000000000', - 'leader': '00000nam\\a2200000uu\\4500', - 'fields': [ - { - 'tag': '008', - 'content': { - 'Type': 'a', - 'BLvl': 'm', - 'DtSt': 'u', - 'Date1': '\\\\\\\\', - 'Date2': '\\\\\\\\', - 'Ctry': '\\\\\\', - 'Ills': ['p', 'o', 'm', 'l'], - 'Audn': 'j', - 'Form': 's', - 'Cont': ['6', '5', '2', 'y'], - 'GPub': 's', - 'Conf': '0', - 'Fest': '1', - 'Indx': '0', - 'LitF': 'p', - 'Biog': '\\', - 'Lang': '\\\\\\', - 'MRec': '\\', - 'Srce': '\\', - }, - }, - { - 'tag': '245', - 'content': '$a rec2', - 'indicators': ['\\', '\\'], - }, - ], - 'parsedRecordDtoId': '00000000-0000-0000-0000-000000000000', - 'relatedRecordVersion': 1, - 'marcFormat': 'BIBLIOGRAPHIC', - 'suppressDiscovery': false, - 'updateInfo': { - 'recordState': 'NEW', - }, - '_actionType': 'create', - }; - - await QuickMarcEditor.mock.calls[0][0].onSubmit(formValues); - - expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalledWith(formValuesForCreate); - }); - - it('should create authority record with correct payload and call onSave', async () => { - const mockOnSave = jest.fn(); - - const payload = { - externalId: '00000000-0000-0000-0000-000000000000', - externalHrid: 'in00000000022', - leader: '00000nz\\\\a2200000o\\\\4500', - marcFormat: MARC_TYPES.AUTHORITY.toUpperCase(), - parsedRecordDtoId: '00000000-0000-0000-0000-000000000000', - records: undefined, - relatedRecordVersion: 1, - suppressDiscovery: false, - updateInfo: { recordState: 'NEW' }, - _actionType: 'create', - fields: [ - { - tag: '001', - content: 'value1', - indicators: undefined, - linkDetails: undefined, - }, - { - tag: '008', - content: { - 'Undef_18': '\\\\\\\\\\\\\\\\\\\\', - 'Undef_30': '\\', - 'Undef_34': '\\\\\\\\', - 'Geo Subd': '\\', - 'Roman': '\\', - 'Lang': '\\', - 'Kind rec': '\\', - 'Cat Rules': '\\', - 'SH Sys': '\\', - 'Series': '\\', - 'Numb Series': '\\', - 'Main use': '\\', - 'Subj use': '\\', - 'Series use': '\\', - 'Type Subd': '\\', - 'Govt Ag': '\\', - 'RefEval': '\\', - 'RecUpd': '\\', - 'Pers Name': '\\', - 'Level Est': '\\', - 'Mod Rec Est': '\\', - 'Source': '\\', - }, - indicators: undefined, - linkDetails: undefined, - }, - { - tag: '010', - content: '$a value1', - indicators: ['\\', '\\'], - linkDetails: undefined, - }, - { - tag: '100', - content: '$a value2', - indicators: ['\\', '\\'], - linkDetails: undefined, - }, - ], - }; - - const { getByText } = renderQuickMarcCreateWrapper({ - instance, - mutator, - marcType: MARC_TYPES.AUTHORITY, - onSave: mockOnSave, - }); - - fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); - - await waitFor(() => { - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.save.success.processing' }); - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.saveNew.success' }); - expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalledWith(payload); - - expect(mockOnSave).toHaveBeenCalled(); - }); - }); - - describe('when click on save button in an authority record', () => { - it('should wait for redirection in onSubmit function', async () => { - let isRedirectWaited = false; - - const onSave = runWithDelayedPromise(() => { - isRedirectWaited = true; - }); - - renderQuickMarcCreateWrapper({ - instance, - mutator, - marcType: MARC_TYPES.AUTHORITY, - onSave, - }); - - await QuickMarcEditor.mock.calls[0][0].onSubmit(mockFormValues(MARC_TYPES.AUTHORITY)); - - expect(isRedirectWaited).toBeTruthy(); - }); - }); - - describe('when click on save button in a holding record', () => { - it('should wait for redirection in onSubmit function', async () => { - let isRedirectWaited = false; - - const onSave = runWithDelayedPromise(() => { - isRedirectWaited = true; - }); - - renderQuickMarcCreateWrapper({ - instance, - mutator, - marcType: MARC_TYPES.HOLDINGS, - onSave, - }); - - await QuickMarcEditor.mock.calls[0][0].onSubmit(mockFormValues(MARC_TYPES.HOLDINGS)); - - expect(isRedirectWaited).toBeTruthy(); - }); - }); - - describe('when click on save button in a bib record', () => { - it('should wait for links saving and redirection in onSubmit function', async () => { - const calledFn = []; - - saveLinksToNewRecord.mockImplementationOnce(runWithDelayedPromise(() => { - calledFn.push('saveLinksToNewRecord'); - }, 20)); - - const onSave = runWithDelayedPromise(() => { - calledFn.push('onSave'); - }); - - renderQuickMarcCreateWrapper({ - instance, - mutator, - marcType: MARC_TYPES.BIB, - onSave, - }); - - await QuickMarcEditor.mock.calls[0][0].onSubmit(mockFormValues(MARC_TYPES.BIB)); - - expect(calledFn).toEqual(['saveLinksToNewRecord', 'onSave']); - }); - }); - - describe('when there is an error during POST request', () => { - it('should show an error message', async () => { - let getByText; - - await act(async () => { - getByText = renderQuickMarcCreateWrapper({ - instance, - mutator, - }).getByText; - }); - - // eslint-disable-next-line prefer-promise-reject-errors - mutator.quickMarcEditMarcRecord.POST = jest.fn(() => Promise.reject({ - json: () => Promise.resolve({}), - })); - - await act(async () => fireEvent.click(getByText('stripes-acq-components.FormFooter.save'))); - - expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalled(); - - expect(mockShowCallout).toHaveBeenCalledWith({ - messageId: 'ui-quick-marc.record.save.error.generic', - type: 'error', - }); - }); - }); - - it('should actualize links', async () => { - await act(async () => { - renderQuickMarcCreateWrapper({ - instance, - mutator, - marcType: MARC_TYPES.BIB, - }); - }); - - await act(async () => { fireEvent.click(screen.getByText('stripes-acq-components.FormFooter.save')); }); - - const expectedFormValues = { - marcFormat: MARC_TYPES.BIB.toUpperCase(), - records: expect.arrayContaining([ - expect.objectContaining({ - tag: 'LDR', - content: bibLeaderString, - }), - expect.objectContaining({ - tag: '100', - content: '$a Ma, Wei $0 id.loc.gov/authorities/names/n84160718 $9 495884af-28d7-4d69-85e4-e84c5de693db', - prevContent: '$a test', - linkDetails: { - authorityId: '495884af-28d7-4d69-85e4-e84c5de693db', - authorityNaturalId: 'n84160718', - linkingRuleId: 1, - status: 'NEW', - }, - }), - ]), - }; - - expect(mockActualizeLinks).toHaveBeenCalledWith(expect.objectContaining(expectedFormValues)); - }); - - describe('when marc type is not a bibliographic', () => { - it('should not call actualizeLinks', async () => { - await act(async () => { - renderQuickMarcCreateWrapper({ - instance, - mutator, - marcType: MARC_TYPES.HOLDINGS, - }); - }); - - await act(async () => { fireEvent.click(screen.getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockActualizeLinks).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/src/QuickMarcEditor/QuickMarcDeriveWrapper.js b/src/QuickMarcEditor/QuickMarcDeriveWrapper.js deleted file mode 100644 index e37fd373..00000000 --- a/src/QuickMarcEditor/QuickMarcDeriveWrapper.js +++ /dev/null @@ -1,178 +0,0 @@ -import React, { - useCallback, - useContext, - useMemo, - useState, -} from 'react'; -import PropTypes from 'prop-types'; -import flow from 'lodash/flow'; -import isEmpty from 'lodash/isEmpty'; - -import { useShowCallout } from '@folio/stripes-acq-components'; - -import QuickMarcEditor from './QuickMarcEditor'; -import { - useAuthorityLinking, - useValidation, -} from '../hooks'; -import getQuickMarcRecordStatus from './getQuickMarcRecordStatus'; -import { QuickMarcContext } from '../contexts'; -import { QUICK_MARC_ACTIONS } from './constants'; -import { MARC_TYPES } from '../common/constants'; -import { - hydrateMarcRecord, - formatLeaderForSubmit, - removeFieldsForDerive, - autopopulateIndicators, - autopopulateSubfieldSection, - cleanBytesFields, - parseHttpError, - removeDeletedRecords, - combineSplitFields, - saveLinksToNewRecord, - recordHasLinks, - autopopulateFixedField, - removeEnteredDate, - autopopulatePhysDescriptionField, - autopopulateMaterialCharsField, -} from './utils'; - -const propTypes = { - action: PropTypes.oneOf(Object.values(QUICK_MARC_ACTIONS)).isRequired, - initialValues: PropTypes.object.isRequired, - instance: PropTypes.object, - marcType: PropTypes.oneOf(Object.values(MARC_TYPES)).isRequired, - fixedFieldSpec: PropTypes.object.isRequired, - mutator: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, -}; - -const QuickMarcDeriveWrapper = ({ - action, - instance, - onClose, - initialValues, - mutator, - marcType, - fixedFieldSpec, -}) => { - const showCallout = useShowCallout(); - const { linkableBibFields, actualizeLinks, linkingRules } = useAuthorityLinking({ marcType, action }); - const [httpError, setHttpError] = useState(null); - const { validationErrorsRef } = useContext(QuickMarcContext); - - const validationContext = useMemo(() => ({ - initialValues, - marcType, - action: QUICK_MARC_ACTIONS.DERIVE, - linkableBibFields, - linkingRules, - fixedFieldSpec, - instanceId: instance.id, - }), [initialValues, marcType, linkableBibFields, linkingRules, fixedFieldSpec, instance.id]); - const { validate } = useValidation(validationContext); - - const prepareForSubmit = useCallback((formValues) => { - const formValuesForDerive = flow( - removeDeletedRecords, - removeFieldsForDerive, - removeEnteredDate, - autopopulateIndicators, - marcRecord => autopopulateFixedField(marcRecord, marcType, fixedFieldSpec), - autopopulatePhysDescriptionField, - autopopulateMaterialCharsField, - marcRecord => autopopulateSubfieldSection(marcRecord, marcType), - marcRecord => cleanBytesFields(marcRecord, fixedFieldSpec, marcType), - marcRecord => formatLeaderForSubmit(marcType, marcRecord), - combineSplitFields, - )(formValues); - - return formValuesForDerive; - }, [marcType, fixedFieldSpec]); - - const runValidation = useCallback(async (formValues) => { - const formValuesForValidation = prepareForSubmit(formValues); - - return validate(formValuesForValidation.records); - }, [validate, prepareForSubmit]); - - const onSubmit = useCallback(async (formValues, _api, complete) => { - // if validation has any issues - cancel submit - if (!isEmpty(validationErrorsRef.current)) { - return complete(); - } - - const formValuesToProcess = prepareForSubmit(formValues); - - showCallout({ messageId: 'ui-quick-marc.record.saveNew.onSave' }); - - let formValuesToHydrate; - - try { - formValuesToHydrate = await actualizeLinks(formValuesToProcess); - } catch (errorResponse) { - const parsedError = await parseHttpError(errorResponse); - - setHttpError(parsedError); - - return null; - } - - formValuesToHydrate.relatedRecordVersion = 1; - formValuesToHydrate._actionType = 'create'; - - const formValuesForDerive = hydrateMarcRecord(formValuesToHydrate); - - return mutator.quickMarcEditMarcRecord.POST(formValuesForDerive) - .then(async ({ qmRecordId }) => { - onClose('id'); // https://issues.folio.org/browse/UIQM-82 - - try { - const { externalId } = await getQuickMarcRecordStatus({ - quickMarcRecordStatusGETRequest: mutator.quickMarcRecordStatus.GET, - qmRecordId, - showCallout, - }); - - showCallout({ messageId: 'ui-quick-marc.record.saveNew.success' }); - - if (recordHasLinks(formValuesForDerive.fields)) { - saveLinksToNewRecord(mutator, externalId, formValuesForDerive) - .finally(() => onClose(externalId)); - } else { - onClose(externalId); - } - } catch (e) { - showCallout({ - messageId: 'ui-quick-marc.record.saveNew.error', - type: 'error', - }); - } - }) - .catch(async (errorResponse) => { - const parsedError = await parseHttpError(errorResponse); - - setHttpError(parsedError); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [onClose, showCallout, prepareForSubmit, actualizeLinks, validationErrorsRef]); - - return ( - - ); -}; - -QuickMarcDeriveWrapper.propTypes = propTypes; - -export default QuickMarcDeriveWrapper; diff --git a/src/QuickMarcEditor/QuickMarcDeriveWrapper.test.js b/src/QuickMarcEditor/QuickMarcDeriveWrapper.test.js deleted file mode 100644 index 5502c24b..00000000 --- a/src/QuickMarcEditor/QuickMarcDeriveWrapper.test.js +++ /dev/null @@ -1,615 +0,0 @@ -import React from 'react'; -import { - act, - render, - fireEvent, - screen, -} from '@folio/jest-config-stripes/testing-library/react'; -import faker from 'faker'; - -import { runAxeTest } from '@folio/stripes-testing'; - -import '@folio/stripes-acq-components/test/jest/__mock__'; - -import QuickMarcDeriveWrapper from './QuickMarcDeriveWrapper'; -import QuickMarcEditor from './QuickMarcEditor'; -import { QUICK_MARC_ACTIONS } from './constants'; -import { MARC_TYPES } from '../common/constants'; - -import Harness from '../../test/jest/helpers/harness'; -import { useAuthorityLinking } from '../hooks'; -import { bibLeader, bibLeaderString } from '../../test/jest/fixtures/leaders'; -import fixedFieldSpecBib from '../../test/mocks/fixedFieldSpecBib'; - -jest.mock('react-final-form', () => ({ - ...jest.requireActual('react-final-form'), - FormSpy: jest.fn(() => (FormSpy)), -})); - -jest.mock('./QuickMarcEditor', () => { - const RealQuickMarcEditor = jest.requireActual('./QuickMarcEditor').default; - - return jest.fn(props => ); -}); - -jest.mock('../queries', () => ({ - ...jest.requireActual('../queries'), - useLinkSuggestions: jest.fn().mockReturnValue({ isLoading: false, fetchLinkSuggestions: jest.fn() }), - useLccnDuplicateConfig: jest.fn().mockReturnValue({ - isLoading: false, - duplicateLccnCheckingEnabled: false, - }), -})); - -jest.mock('../hooks', () => ({ - ...jest.requireActual('../hooks'), - useAuthorityLinking: jest.fn(), -})); - -const mockFormValues = jest.fn(() => ({ - fields: undefined, - externalId: '17064f9d-0362-468d-8317-5984b7efd1b5', - leader: bibLeader, - parsedRecordDtoId: '1bf159d9-4da8-4c3f-9aac-c83e68356bbf', - parsedRecordId: '1bf159d9-4da8-4c3f-9aac-c83e68356bbf', - marcFormat: MARC_TYPES.BIB.toUpperCase(), - records: [ - { - tag: 'LDR', - content: bibLeader, - id: 'LDR', - }, { - tag: '001', - content: '', - indicators: [], - id: '595a98e6-8e59-448d-b866-cd039b990423', - }, { - tag: '008', - content: { - Audn: '\\', - BLvl: 'm', - Biog: '\\', - Conf: '0', - Cont: ['b', '\\', '\\', '\\'], - Ctry: 'miu', - Date1: '2009', - Date2: '\\\\\\\\', - Desc: 'i', - DtSt: 's', - Entered: '130325', - Fest: '0', - Form: 'o', - GPub: '\\', - Ills: ['\\', '\\', '\\', '\\'], - Indx: '1', - Lang: 'eng', - LitF: '0', - MRec: '\\', - Srce: 'd', - Type: 'a', - }, - indicators: [], - id: '93213747-46fb-4861-b8e8-8774bf4a46a4', - }, { - tag: '050', - content: '$a BS1545.53 $b .J46 2009eb', - indicators: [], - id: '6abdaf9b-ac58-4f83-9687-73c939c3c21a', - }, { - tag: '100', - content: '$a value $0 http://some-url/naturalId', - linkDetails: { - authorityId: 'authority-id', - authorityNaturalId: 'naturalId', - linkingRuleId: 1, - }, - }, { - 'tag': '100', - 'content': '$a Coates, Ta-Nehisi $e author. $0 id.loc.gov/authorities/names/n2008001084 $9 4808f6ae-8379-41e9-a795-915ac4751668', - 'indicators': ['1', '\\'], - 'isProtected': false, - 'id': '5481472d-a621-4571-9ef9-438a4c7044fd', - '_isDeleted': false, - '_isLinked': true, - 'linkDetails': { - 'authorityNaturalId': 'n2008001084', - 'authorityId': '4808f6ae-8379-41e9-a795-915ac4751668', - 'linkingRuleId': 1, - 'status': 'ACTUAL', - }, - 'subfieldGroups': { - 'controlled': '$a Coates, Ta-Nehisi', - 'uncontrolledAlpha': '$e author.', - 'zeroSubfield': '$0 id.loc.gov/authorities/names/n2008001084', - 'nineSubfield': '$9 4808f6ae-8379-41e9-a795-915ac4751668', - 'uncontrolledNumber': '', - }, - }, { - 'id': '0b3938b5-3ed6-45a0-90f9-fcf24dfebc7c', - 'tag': '100', - 'content': '$a Ma, Wei $0 id.loc.gov/authorities/names/n84160718 $9 495884af-28d7-4d69-85e4-e84c5de693db', - 'indicators': ['\\', '\\'], - '_isAdded': true, - '_isLinked': true, - 'prevContent': '$a test', - 'linkDetails': { - 'authorityNaturalId': 'n84160718', - 'authorityId': '495884af-28d7-4d69-85e4-e84c5de693db', - 'linkingRuleId': 1, - 'status': 'NEW', - }, - 'subfieldGroups': { - 'controlled': '$a Ma, Wei', - 'uncontrolledAlpha': '', - 'zeroSubfield': '$0 id.loc.gov/authorities/names/n84160718', - 'nineSubfield': '$9 495884af-28d7-4d69-85e4-e84c5de693db', - 'uncontrolledNumber': '', - }, - }, { - content: '$a (derived2)/Ezekiel / $c Robert W. Jenson.', - id: '5aa1a643-b9f2-47e8-bb68-6c6457b5c9c5', - indicators: ['1', '0'], - tag: '245', - }, { - tag: '999', - content: '', - indicators: [], - id: '4a844042-5c7e-4e71-823e-599582a5d7ab', - }, - ], - suppressDiscovery: false, - updateInfo: { recordState: 'NEW' }, -})); - -const mockDerivedRecord = () => { - const record = mockFormValues(); - - return { - ...record, - fields: record.records, - }; -}; - -jest.mock('@folio/stripes/final-form', () => () => (Component) => ({ onSubmit, ...props }) => { - const formValues = mockFormValues(); - - return ( - onSubmit(formValues)} - form={{ - mutators: { - markRecordsLinked: jest.fn(), - }, - reset: jest.fn(), - getState: jest.fn().mockReturnValue({ values: formValues }), - }} - {...props} - /> - ); -}); - -jest.mock('@folio/stripes/components', () => ({ - ...jest.requireActual('@folio/stripes/components'), - ConfirmationModal: jest.fn(({ - open, - onCancel, - onConfirm, - }) => (open ? ( -
- Confirmation modal - - -
- ) : null)), -})); - -const mockActualizeLinks = jest.fn((formValuesToProcess) => Promise.resolve(formValuesToProcess)); -const mockShowCallout = jest.fn(); -const mockOnClose = jest.fn(); -const mockOnSave = jest.fn(); - -jest.mock('@folio/stripes-acq-components', () => ({ - ...jest.requireActual('@folio/stripes-acq-components'), - useShowCallout: jest.fn(() => mockShowCallout), -})); - -jest.mock('./QuickMarcEditorRows', () => { - return { - QuickMarcEditorRows: () => (QuickMarcEditorRows), - }; -}); - -jest.mock('./QuickMarcRecordInfo', () => { - return { - QuickMarcRecordInfo: () => QuickMarcRecordInfo, - }; -}); - -jest.mock('./getQuickMarcRecordStatus', () => () => jest.fn().mockResolvedValue({})); - -jest.mock('./constants', () => ({ - ...jest.requireActual('./constants'), - QM_RECORD_STATUS_TIMEOUT: 5, - QM_RECORD_STATUS_BAIL_TIME: 20, -})); - -const getInstance = () => ({ - id: faker.random.uuid(), - title: 'ui-quick-marc.record.edit.title', -}); - -const linkingRules = [{ - id: 1, - bibField: '100', - authorityField: '100', - authoritySubfields: ['a', 'b', 't', 'd'], - subfieldModifications: [], - validation: {}, - autoLinkingEnabled: true, -}]; - -const initialValues = { - leader: bibLeader, - records: [ - { - tag: 'LDR', - content: bibLeader, - id: 'LDR', - }, - { - tag: '100', - content: '$a Coates, Ta-Nehisi $e author.', - indicators: ['1', '\\'], - _isLinked: true, - id: '100', - }, - { - tag: '110', - content: '$a Test title', - indicators: ['2', '\\'], - id: 'test-id-1', - }, - ], -}; - -const renderQuickMarcDeriveWrapper = (props) => (render( - - - , -)); - -describe('Given QuickMarcDeriveWrapper', () => { - let mutator; - let instance; - - beforeEach(() => { - jest.clearAllMocks(); - instance = getInstance(); - mutator = { - quickMarcEditInstance: { - GET: () => Promise.resolve(instance), - }, - quickMarcEditMarcRecord: { - GET: jest.fn(() => Promise.resolve(mockDerivedRecord())), - POST: jest.fn(() => Promise.resolve({})), - PUT: jest.fn(() => Promise.resolve({})), - }, - quickMarcRecordStatus: { - GET: jest.fn(() => Promise.resolve({})), - }, - }; - - useAuthorityLinking.mockReturnValue({ - linkableBibFields: [], - actualizeLinks: mockActualizeLinks, - autoLinkingEnabled: true, - autoLinkableBibFields: [], - autoLinkAuthority: jest.fn(), - linkingRules, - }); - }); - - it('should render with no axe errors', async () => { - const { container } = renderQuickMarcDeriveWrapper({ - instance, - mutator, - }); - - await runAxeTest({ - rootNode: container, - }); - }); - - describe('when click on cancel pane button', () => { - it('should display pane footer', () => { - const { getByRole } = renderQuickMarcDeriveWrapper({ - instance, - mutator, - }); - - fireEvent.click(getByRole('button', { name: 'stripes-acq-components.FormFooter.cancel' })); - - expect(mockOnClose).toHaveBeenCalled(); - }); - }); - - describe('when click on save button', () => { - it('should show on save message and redirect on load page', async () => { - let getByText; - - await act(async () => { - getByText = renderQuickMarcDeriveWrapper({ - instance, - mutator, - }).getByText; - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.saveNew.onSave' }); - - expect(mockOnClose).toHaveBeenCalledWith('id'); - }); - - it('should derive record with correct payload', async () => { - renderQuickMarcDeriveWrapper({ - instance, - mutator, - }); - - const formValues = { - '_actionType': 'view', - 'leader': { - 'Record length': '00246', - 'Status': 'n', - 'Type': 'a', - 'BLvl': 'm', - 'Ctrl': '\\', - '9-16 positions': 'a2200085', - 'ELvl': 'u', - 'Desc': 'u', - 'MultiLvl': '\\', - '20-23 positions': '4500', - }, - 'suppressDiscovery': false, - 'marcFormat': 'BIBLIOGRAPHIC', - 'parsedRecordId': '2b56625f-1ca0-4ada-a32d-2667be1bd509', - 'parsedRecordDtoId': '2b56625f-1ca0-4ada-a32d-2667be1bd509', - 'externalId': 'e72f49c9-9bbf-4d2b-89eb-3d2ee5878530', - 'externalHrid': 'in00000000035', - 'updateInfo': { - 'recordState': 'NEW', - }, - 'records': [ - { - 'tag': 'LDR', - 'content': { - 'Record length': '00246', - 'Status': 'n', - 'Type': 'e', - 'BLvl': 'm', - 'Ctrl': '\\', - '9-16 positions': 'a2200085', - 'ELvl': 'u', - 'Desc': 'u', - 'MultiLvl': '\\', - '20-23 positions': '4500', - }, - 'id': 'LDR', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '001', - 'content': '', - 'isProtected': true, - 'id': 'dea3aafd-9367-4592-9996-606592ea4947', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '005', - 'content': '', - 'isProtected': false, - 'id': 'c392e6ca-29cc-4511-9afe-79f9d6472ea9', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '008', - 'content': { - 'Type': 'a', - 'BLvl': 'm', - 'DtSt': 'u', - 'Date1': '\\\\\\\\', - 'Date2': '\\\\\\\\', - 'Ctry': '\\\\\\', - 'Lang': '\\\\\\', - 'MRec': '\\', - 'Srce': '\\', - 'Ills': ['p', 'o', 'm', 'l'], - 'Audn': 'j', - 'Form': 's', - 'Cont': ['6', '5', '2', 'y'], - 'GPub': 's', - 'Conf': '0', - 'Fest': '1', - 'Indx': '0', - 'LitF': 'p', - 'Biog': '\\', - 'SpFm': ['\\', '\\'], - 'Relf': ['a', 'b', 'c', 'd'], - 'Proj': '\\\\', - 'CrTp': 'a', - }, - 'isProtected': false, - 'id': 'c52b10e1-074e-4728-bd6a-440be762aed2', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '245', - 'content': '$a rec3', - 'indicators': ['\\', '\\'], - 'isProtected': false, - 'id': '6d7001a2-ba49-42e9-a7a1-b5597c9b6449', - '_isDeleted': false, - '_isLinked': false, - }, - { - 'tag': '999', - 'content': '', - 'indicators': ['f', 'f'], - 'isProtected': true, - 'id': '61b93d22-6857-4e68-bfeb-240ed4956318', - '_isDeleted': false, - '_isLinked': false, - }, - ], - }; - const formValuesForDerive = { - '_actionType': 'create', - 'leader': '00246nem\\a2200085uu\\4500', - 'fields': [ - { - 'tag': '008', - 'content': { - 'Type': 'e', - 'BLvl': 'm', - 'DtSt': 'u', - 'Date1': '\\\\\\\\', - 'Date2': '\\\\\\\\', - 'Ctry': '\\\\\\', - 'Lang': '\\\\\\', - 'MRec': '\\', - 'Srce': '\\', - 'Audn': 'j', - 'Form': 's', - 'GPub': 's', - 'Conf': '0', - 'Fest': '1', - 'Indx': '0', - 'LitF': 'p', - 'Biog': '\\', - 'SpFm': ['\\', '\\'], - 'Relf': ['a', 'b', 'c', 'd'], - 'Proj': '\\\\', - 'CrTp': 'a', - }, - }, - { - 'tag': '245', - 'content': '$a rec3', - 'indicators': ['\\', '\\'], - }, - ], - 'suppressDiscovery': false, - 'marcFormat': 'BIBLIOGRAPHIC', - 'parsedRecordId': '2b56625f-1ca0-4ada-a32d-2667be1bd509', - 'parsedRecordDtoId': '2b56625f-1ca0-4ada-a32d-2667be1bd509', - 'externalId': 'e72f49c9-9bbf-4d2b-89eb-3d2ee5878530', - 'externalHrid': 'in00000000035', - 'relatedRecordVersion': 1, - }; - - await QuickMarcEditor.mock.calls[0][0].onSubmit(formValues); - - expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalledWith(formValuesForDerive); - }); - - describe('when there is an error during POST request', () => { - it('should show an error message', async () => { - let getByText; - - await act(async () => { - getByText = renderQuickMarcDeriveWrapper({ - instance, - mutator, - }).getByText; - }); - - // eslint-disable-next-line prefer-promise-reject-errors - mutator.quickMarcEditMarcRecord.POST = jest.fn(() => Promise.reject({ - json: () => Promise.resolve({}), - })); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalled(); - - await new Promise(resolve => { - setTimeout(() => { - expect(mockShowCallout).toHaveBeenCalledWith({ - messageId: 'ui-quick-marc.record.save.error.generic', - type: 'error', - }); - - resolve(); - }, 10); - }); - }, 1000); - }); - - it('should actualize links', async () => { - await act(async () => { - renderQuickMarcDeriveWrapper({ - instance, - mutator, - }); - }); - - await act(async () => { fireEvent.click(screen.getByText('stripes-acq-components.FormFooter.save')); }); - - const expectedFormValues = { - marcFormat: MARC_TYPES.BIB.toUpperCase(), - records: expect.arrayContaining([ - expect.objectContaining({ - tag: 'LDR', - content: bibLeaderString, - }), - expect.objectContaining({ - tag: '100', - content: '$a Coates, Ta-Nehisi $e author. $0 id.loc.gov/authorities/names/n2008001084 $9 4808f6ae-8379-41e9-a795-915ac4751668', - linkDetails: { - authorityId: '4808f6ae-8379-41e9-a795-915ac4751668', - authorityNaturalId: 'n2008001084', - linkingRuleId: 1, - status: 'ACTUAL', - }, - }), - expect.objectContaining({ - tag: '100', - content: '$a Ma, Wei $0 id.loc.gov/authorities/names/n84160718 $9 495884af-28d7-4d69-85e4-e84c5de693db', - prevContent: '$a test', - linkDetails: { - authorityNaturalId: 'n84160718', - authorityId: '495884af-28d7-4d69-85e4-e84c5de693db', - linkingRuleId: 1, - status: 'NEW', - }, - }), - ]), - }; - - expect(mockActualizeLinks).toHaveBeenCalledWith(expect.objectContaining(expectedFormValues)); - }); - }); -}); diff --git a/src/QuickMarcEditor/QuickMarcEditWrapper.js b/src/QuickMarcEditor/QuickMarcEditWrapper.js deleted file mode 100644 index 78ca823a..00000000 --- a/src/QuickMarcEditor/QuickMarcEditWrapper.js +++ /dev/null @@ -1,283 +0,0 @@ -import React, { - useCallback, - useContext, - useMemo, - useState, -} from 'react'; -import { useLocation } from 'react-router'; -import PropTypes from 'prop-types'; -import flow from 'lodash/flow'; -import noop from 'lodash/noop'; -import isNil from 'lodash/isNil'; -import isEmpty from 'lodash/isEmpty'; - -import { useStripes } from '@folio/stripes/core'; -import { useShowCallout } from '@folio/stripes-acq-components'; -import { getHeaders } from '@folio/stripes-marc-components'; - -import QuickMarcEditor from './QuickMarcEditor'; -import { - useAuthorityLinking, - useValidation, -} from '../hooks'; -import { useMarcRecordMutation } from '../queries'; -import { QuickMarcContext } from '../contexts'; -import { QUICK_MARC_ACTIONS } from './constants'; -import { - EXTERNAL_INSTANCE_APIS, - MARC_TYPES, - ERROR_TYPES, -} from '../common/constants'; -import { - hydrateMarcRecord, - formatLeaderForSubmit, - autopopulateIndicators, - autopopulateSubfieldSection, - cleanBytesFields, - parseHttpError, - removeDeletedRecords, - combineSplitFields, - are010Or1xxUpdated, - removeDuplicateSystemGeneratedFields, - autopopulateFixedField, - autopopulatePhysDescriptionField, - autopopulateMaterialCharsField, - applyCentralTenantInHeaders, -} from './utils'; - -const propTypes = { - action: PropTypes.oneOf(Object.values(QUICK_MARC_ACTIONS)).isRequired, - linksCount: PropTypes.number, - refreshPageData: PropTypes.func.isRequired, - externalRecordPath: PropTypes.string.isRequired, - initialValues: PropTypes.object.isRequired, - instance: PropTypes.object, - marcType: PropTypes.oneOf(Object.values(MARC_TYPES)).isRequired, - fixedFieldSpec: PropTypes.object.isRequired, - mutator: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, - onSave: PropTypes.func.isRequired, - onCheckCentralTenantPerm: PropTypes.func, - locations: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -const QuickMarcEditWrapper = ({ - action, - instance, - onClose, - onSave, - initialValues, - mutator, - marcType, - fixedFieldSpec, - linksCount, - locations, - refreshPageData, - externalRecordPath, - onCheckCentralTenantPerm, -}) => { - const stripes = useStripes(); - const showCallout = useShowCallout(); - const location = useLocation(); - const [httpError, setHttpError] = useState(null); - const { validationErrorsRef, relatedRecordVersion } = useContext(QuickMarcContext); - - const { token, locale } = stripes.okapi; - const isRequestToCentralTenantFromMember = applyCentralTenantInHeaders(location, stripes, marcType); - const centralTenantId = stripes.user.user.consortium?.centralTenantId; - const tenantId = isRequestToCentralTenantFromMember ? centralTenantId : ''; - - const { linkableBibFields, actualizeLinks, linkingRules } = useAuthorityLinking({ marcType, action }); - const { updateMarcRecord } = useMarcRecordMutation({ tenantId }); - - const validationContext = useMemo(() => ({ - initialValues, - marcType, - action: QUICK_MARC_ACTIONS.EDIT, - locations, - linksCount, - naturalId: instance.naturalId, - linkableBibFields, - linkingRules, - fixedFieldSpec, - instanceId: instance.id, - }), [ - initialValues, - marcType, - locations, - linkableBibFields, - linkingRules, - fixedFieldSpec, - linksCount, - instance.naturalId, - instance.id, - ]); - const { validate } = useValidation(validationContext, tenantId); - - const prepareForSubmit = useCallback((formValues) => { - const formValuesToSave = flow( - removeDeletedRecords, - removeDuplicateSystemGeneratedFields, - marcRecord => formatLeaderForSubmit(marcType, marcRecord), - autopopulateIndicators, - marcRecord => autopopulateFixedField(marcRecord, marcType, fixedFieldSpec), - autopopulatePhysDescriptionField, - autopopulateMaterialCharsField, - marcRecord => autopopulateSubfieldSection(marcRecord, marcType), - marcRecord => cleanBytesFields(marcRecord, fixedFieldSpec, marcType), - combineSplitFields, - )(formValues); - - return formValuesToSave; - }, [marcType, fixedFieldSpec]); - - const runValidation = useCallback(async (formValues) => { - const formValuesForValidation = prepareForSubmit(formValues); - - return validate(formValuesForValidation.records); - }, [validate, prepareForSubmit]); - - const onSubmit = useCallback(async (formValues, _api, complete) => { - let is1xxOr010Updated = false; - - // if validation has any issues - cancel submit - if (!isEmpty(validationErrorsRef.current)) { - return complete(); - } - - if (marcType === MARC_TYPES.AUTHORITY && linksCount > 0) { - is1xxOr010Updated = are010Or1xxUpdated(initialValues.records, formValues.records); - } - - const formValuesToProcess = prepareForSubmit(formValues); - - const path = EXTERNAL_INSTANCE_APIS[marcType]; - - const fetchInstance = async () => { - const fetchedInstance = await mutator.quickMarcEditInstance.GET({ - path: `${path}/${formValuesToProcess.externalId}`, - ...(isRequestToCentralTenantFromMember && { headers: getHeaders(centralTenantId, token, locale) }), - }); - - return fetchedInstance; - }; - - let formValuesToHydrate; - let instanceResponse; - - try { - const actualizeLinksPromise = marcType === MARC_TYPES.BIB - ? actualizeLinks(formValuesToProcess) - : Promise.resolve(formValuesToProcess); - - const [ - formValuesWithActualizedLinkedFields, - instanceData, - ] = await Promise.all([ - actualizeLinksPromise, - fetchInstance(), - ]); - - formValuesToHydrate = formValuesWithActualizedLinkedFields; - instanceResponse = instanceData; - } catch (errorResponse) { - const parsedError = await parseHttpError(errorResponse); - - setHttpError(parsedError); - - return undefined; - } - - const prevVersion = instance._version; - const lastVersion = instanceResponse._version; - - if (!isNil(prevVersion) && !isNil(lastVersion) && prevVersion !== lastVersion) { - setHttpError({ - errorType: ERROR_TYPES.OPTIMISTIC_LOCKING, - message: 'ui-quick-marc.record.save.error.derive', - }); - - return null; - } - - formValuesToHydrate._actionType = 'edit'; - formValuesToHydrate.relatedRecordVersion = marcType === MARC_TYPES.AUTHORITY - ? instance._version - : relatedRecordVersion; - - const formValuesToSave = hydrateMarcRecord(formValuesToHydrate); - - return updateMarcRecord(formValuesToSave) - .then(async () => { - if (is1xxOr010Updated) { - const values = { - count: linksCount, - }; - - showCallout({ - messageId: 'ui-quick-marc.record.save.updatingLinkedBibRecords', - values, - }); - } else { - showCallout({ - messageId: 'ui-quick-marc.record.save.success.processing', - }); - } - - const fieldIds = formValuesToHydrate.records.slice(1).map(field => field.id); - - await refreshPageData(fieldIds); - - return { version: parseInt(formValuesToSave.relatedRecordVersion, 10) + 1 }; - }) - .catch(async (errorResponse) => { - const parsedError = await parseHttpError(errorResponse); - - setHttpError(parsedError); - }); - }, [ - showCallout, - refreshPageData, - initialValues, - instance, - marcType, - mutator, - linksCount, - prepareForSubmit, - actualizeLinks, - centralTenantId, - token, - locale, - updateMarcRecord, - isRequestToCentralTenantFromMember, - validationErrorsRef, - relatedRecordVersion, - ]); - - return ( - - ); -}; - -QuickMarcEditWrapper.propTypes = propTypes; -QuickMarcEditWrapper.defaultProps = { - onCheckCentralTenantPerm: noop, -}; - -export default QuickMarcEditWrapper; diff --git a/src/QuickMarcEditor/QuickMarcEditWrapper.test.js b/src/QuickMarcEditor/QuickMarcEditWrapper.test.js deleted file mode 100644 index 7b816cea..00000000 --- a/src/QuickMarcEditor/QuickMarcEditWrapper.test.js +++ /dev/null @@ -1,955 +0,0 @@ -import React from 'react'; -import { - render, - act, - fireEvent, -} from '@folio/jest-config-stripes/testing-library/react'; -import faker from 'faker'; -import noop from 'lodash/noop'; -import { Form } from 'react-final-form'; -import arrayMutators from 'final-form-arrays'; -import { useLocation } from 'react-router'; - -import QuickMarcEditWrapper from './QuickMarcEditWrapper'; -import { useAuthorityLinking } from '../hooks'; -import { - useMarcRecordMutation, - useValidate, -} from '../queries'; -import { QUICK_MARC_ACTIONS } from './constants'; -import { MARC_TYPES } from '../common/constants'; -import { applyCentralTenantInHeaders } from './utils'; - -import Harness from '../../test/jest/helpers/harness'; -import { - authorityLeader, - bibLeader, - bibLeaderString, -} from '../../test/jest/fixtures/leaders'; -import fixedFieldSpecBib from '../../test/mocks/fixedFieldSpecBib'; -import fixedFieldSpecAuth from '../../test/mocks/fixedFieldSpecAuth'; - -jest.mock('./utils', () => ({ - ...jest.requireActual('./utils'), - applyCentralTenantInHeaders: jest.fn(), -})); - -jest.mock('react-router', () => ({ - ...jest.requireActual('react-router'), - useLocation: jest.fn(), -})); - -jest.mock('../queries', () => ({ - ...jest.requireActual('../queries'), - useMarcRecordMutation: jest.fn(), - useLccnDuplicateConfig: jest.fn().mockReturnValue({ - isLoading: false, - duplicateLccnCheckingEnabled: false, - }), - useValidate: jest.fn(), -})); - -jest.mock('../hooks', () => ({ - ...jest.requireActual('../hooks'), - useAuthorityLinking: jest.fn(), -})); - -const mockRecords = { - [MARC_TYPES.BIB]: [ - { - tag: 'LDR', - content: bibLeader, - id: 'LDR', - }, { - tag: '001', - content: 'in00000000003', - id: '595a98e6-8e59-448d-b866-cd039b990423', - }, { - tag: '004', - content: 'in00000000022', - id: '93213747-46fb-4861-b8e8-8774bf4a46a4', - }, { - tag: '008', - 'content': { - 'Type': 'a', - 'BLvl': 'm', - 'Entered': '240404', - 'DtSt': 'u', - 'Date1': '\\\\\\\\', - 'Date2': '\\\\\\\\', - 'Ctry': '\\\\\\', - 'Lang': '\\\\\\', - 'MRec': '\\', - 'Srce': '\\', - 'Ills': ['p', 'o', 'm', 'l'], - 'Audn': 'j', - 'Form': 's', - 'Cont': ['6', '5', '2', 'y'], - 'GPub': 's', - 'Conf': '0', - 'Fest': '1', - 'Indx': '0', - 'LitF': 'p', - 'Biog': '\\', - 'SpFm': ['\\', '\\'], - 'Relf': ['a', 'b', 'c', 'd'], - 'Proj': '\\\\', - 'CrTp': 'a', - }, - }, { - 'tag': '100', - 'content': '$a Coates, Ta-Nehisi $e author. $0 id.loc.gov/authorities/names/n2008001084 $9 4808f6ae-8379-41e9-a795-915ac4751668', - 'indicators': ['1', '\\'], - 'isProtected': false, - 'id': '5481472d-a621-4571-9ef9-438a4c7044fd', - '_isDeleted': false, - '_isLinked': true, - 'linkDetails': { - 'authorityNaturalId': 'n2008001084', - 'authorityId': '4808f6ae-8379-41e9-a795-915ac4751668', - 'linkingRuleId': 1, - 'status': 'ACTUAL', - }, - 'subfieldGroups': { - 'controlled': '$a Coates, Ta-Nehisi', - 'uncontrolledAlpha': '$e author.', - 'zeroSubfield': '$0 id.loc.gov/authorities/names/n2008001084', - 'nineSubfield': '$9 4808f6ae-8379-41e9-a795-915ac4751668', - 'uncontrolledNumber': '', - }, - }, { - 'id': '0b3938b5-3ed6-45a0-90f9-fcf24dfebc7c', - 'tag': '100', - 'content': '$a Ma, Wei $0 id.loc.gov/authorities/names/n84160718 $9 495884af-28d7-4d69-85e4-e84c5de693db', - 'indicators': ['\\', '\\'], - '_isAdded': true, - '_isLinked': true, - 'prevContent': '$a test', - 'linkDetails': { - 'authorityNaturalId': 'n84160718', - 'authorityId': '495884af-28d7-4d69-85e4-e84c5de693db', - 'linkingRuleId': 1, - 'status': 'NEW', - }, - 'subfieldGroups': { - 'controlled': '$a Ma, Wei', - 'uncontrolledAlpha': '', - 'zeroSubfield': '$0 id.loc.gov/authorities/names/n84160718', - 'nineSubfield': '$9 495884af-28d7-4d69-85e4-e84c5de693db', - 'uncontrolledNumber': '', - }, - }, { - content: '$a Title', - tag: '245', - }, { - tag: '852', - content: '$b KU/CC/DI/A $t 3 $h M3 $i .M93 1955 $m + $x Rec\'d in Music Lib ;', - indicators: ['0', '1'], - id: '6abdaf9b-ac58-4f83-9687-73c939c3c21a', - }, { - tag: '014', - content: '$a ABS3966CU004', - indicators: ['1', '\\'], - id: '5aa1a643-b9f2-47e8-bb68-6c6457b5c9c5', - }, { - tag: '005', - content: '20221228135005.0', - id: '5aa1a643-b9f2-47e8-bb68-6c6457b5c9c6', - }, { - tag: '999', - indicators: ['f', 'f'], - content: '$s 9585bca7-8e4c-4cbb-bab4-46c5832e7654 $i 9012727e-bffc-4298-a424-7da30d6008aa', - id: '4a844042-5c7e-4e71-823e-599582a5d7ab', - }, - ], - [MARC_TYPES.AUTHORITY]: [ - { - tag: 'LDR', - content: authorityLeader, - id: 'LDR', - }, - { - id: 1, - tag: '001', - content: '971255', - isProtected: true, - }, - { - id: 2, - tag: '005', - content: '20120323070509.0', - }, - { - id: 3, - tag: '008', - content: { - 'Date Ent': '860211', - 'Geo Subd': 'i', - Roman: '|', - Lang: '\\', - 'Kind rec': 'a', - 'Cat Rules': 'n', - 'SH Sys': 'a', - Series: 'n', - 'Numb Series': 'n', - 'Main use': 'b', - 'Subj use': 'a', - 'Series use': 'b', - 'Type Subd': 'n', - Undef_18: '\\\\\\\\\\\\\\\\\\\\', - 'Govt Ag': '|', - RefEval: 'b', - Undef_30: '\\', - RecUpd: 'a', - 'Pers Name': 'n', - 'Level Est': 'a', - Undef_34: '\\\\\\\\', - 'Mod Rec Est': '\\', - Source: '\\', - }, - }, - { - id: 4, - tag: '010', - content: '$a sh 85026421 ', - indicators: ['\\', '\\'], - }, - { - id: 5, - tag: '035', - content: '$a (DLC)sh 85026421', - indicators: ['\\', '\\'], - }, - { - id: 6, - tag: '035', - content: '$a (DLC)25463', - indicators: ['\\', '\\'], - }, - { - id: 7, - tag: '040', - content: '$a DLC $c DLC $d DLC $d ViU', - indicators: ['\\', '\\'], - }, - { - id: 8, - tag: '150', - content: '$a Civil war', - indicators: ['\\', '\\'], - }, - { - id: 9, - tag: '360', - content: '$i individual civil wars, e.g. $a United States--History--Civil War, 1861-1865', - indicators: ['\\', '\\'], - }, - { - id: 10, - tag: '450', - content: '$a Civil wars', - indicators: ['\\', '\\'], - }, - { - id: 11, - tag: '550', - content: '$w g $a Revolutions', - indicators: ['\\', '\\'], - }, - { - id: 12, - tag: '999', - content: '$s 2c85c168-a821-434a-981a-7e95df2fbd84 $i 06d6033f-dd91-400d-b0db-58817d05654f', - indicators: ['f', 'f'], - }, - ], -}; - -const mockLeaders = { - [MARC_TYPES.BIB]: bibLeader, - [MARC_TYPES.AUTHORITY]: authorityLeader, -}; - -const mockFormValues = jest.fn(); - -const mockSpecs = { - [MARC_TYPES.BIB]: fixedFieldSpecBib, - [MARC_TYPES.AUTHORITY]: fixedFieldSpecAuth, -}; - -const mockActualizeLinks = jest.fn((formValuesToProcess) => Promise.resolve(formValuesToProcess)); -const mockUpdateMarcRecord = jest.fn().mockResolvedValue(); -const mockOnCheckCentralTenantPerm = jest.fn().mockReturnValue(false); -const mockValidateFetch = jest.fn().mockResolvedValue({}); - -jest.mock('@folio/stripes/final-form', () => () => (Component) => ({ - onSubmit, - marcType, - ...props -}) => { - const formValues = mockFormValues(marcType); - - return ( - onSubmit(formValues)} - form={{ - mutators: { - markRecordsLinked: jest.fn(), - addRecord: jest.fn(), - deleteRecord: jest.fn(), - markRecordDeleted: jest.fn(), - markRecordLinked: jest.fn(), - markRecordUnlinked: jest.fn(), - moveRecord: jest.fn(), - restoreRecord: jest.fn(), - updateRecord: jest.fn(), - }, - reset: jest.fn(), - getState: jest.fn().mockReturnValue({ values: formValues }), - }} - marcType={marcType} - {...props} - /> - ); -}); - -const mockShowCallout = jest.fn(); -const mockRefreshPageData = jest.fn().mockResolvedValue(); - -jest.mock('@folio/stripes-acq-components', () => ({ - ...jest.requireActual('@folio/stripes-acq-components'), - useShowCallout: jest.fn(() => mockShowCallout), -})); - -jest.mock('./QuickMarcRecordInfo', () => { - return { - QuickMarcRecordInfo: () => QuickMarcRecordInfo, - }; -}); - -jest.mock('./constants', () => ({ - ...jest.requireActual('./constants'), - QM_RECORD_STATUS_TIMEOUT: 5, - QM_RECORD_STATUS_BAIL_TIME: 20, -})); - -const getInstance = () => ({ - id: faker.random.uuid(), - title: 'ui-quick-marc.record.edit.title', - _version: 0, -}); - -const linkingRules = [{ - id: 1, - bibField: '100', - authorityField: '100', - authoritySubfields: ['a', 'b', 't', 'd'], - subfieldModifications: [], - validation: {}, - autoLinkingEnabled: true, -}]; - -const record = { - id: faker.random.uuid(), - fields: [], -}; - -const locations = [{ - code: 'KU/CC/DI/A', -}]; - -const renderQuickMarcEditWrapper = ({ - instance, - mutator, - marcType = MARC_TYPES.BIB, - quickMarcContext, - ...props -}) => (render( - -
( - - )} - /> - , -)); - -describe('Given QuickMarcEditWrapper', () => { - let mutator; - let instance; - - beforeEach(() => { - instance = getInstance(); - mutator = { - externalInstanceApi: { - update: jest.fn(), - }, - quickMarcEditInstance: { - GET: jest.fn(() => Promise.resolve(instance)), - }, - quickMarcEditMarcRecord: { - GET: jest.fn(() => Promise.resolve(record)), - PUT: jest.fn(() => Promise.resolve()), - }, - locations: { - GET: () => Promise.resolve({}), - }, - }; - - mockFormValues.mockImplementation((marcType) => ({ - fields: undefined, - externalId: '17064f9d-0362-468d-8317-5984b7efd1b5', - marcFormat: marcType.toUpperCase(), - leader: mockLeaders[marcType], - parsedRecordDtoId: '1bf159d9-4da8-4c3f-9aac-c83e68356bbf', - parsedRecordId: '1bf159d9-4da8-4c3f-9aac-c83e68356bbf', - records: mockRecords[marcType], - suppressDiscovery: false, - updateInfo: { recordState: 'NEW' }, - })); - - useAuthorityLinking.mockReturnValue({ - linkableBibFields: [], - actualizeLinks: mockActualizeLinks, - autoLinkingEnabled: true, - autoLinkableBibFields: [], - autoLinkAuthority: jest.fn(), - linkingRules, - }); - - useMarcRecordMutation.mockReturnValue({ - updateMarcRecord: mockUpdateMarcRecord, - isLoading: false, - }); - - useLocation.mockReturnValue({}); - - useValidate.mockReturnValue({ - validate: mockValidateFetch, - }); - - jest.clearAllMocks(); - }); - - describe('when is bib marc type', () => { - describe('when click on "Save & keep editing" button', () => { - it('should show on save message and stay on the edit page', async () => { - const mockOnClose = jest.fn(); - const mockOnSave = jest.fn(); - - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - onClose: mockOnClose, - onSave: mockOnSave, - }); - - await act(async () => { fireEvent.click(getByText('ui-quick-marc.record.save.continue')); }); - - expect(mutator.quickMarcEditInstance.GET).toHaveBeenCalled(); - expect(mockUpdateMarcRecord).toHaveBeenCalled(); - - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.save.success.processing' }); - expect(mockOnClose).not.toHaveBeenCalled(); - expect(mockOnSave).not.toHaveBeenCalled(); - }); - - it('pass ids of fields to the refreshPageData', async () => { - const mockOnClose = jest.fn(); - const mockOnSave = jest.fn(); - - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - onClose: mockOnClose, - onSave: mockOnSave, - }); - - await act(async () => { fireEvent.click(getByText('ui-quick-marc.record.save.continue')); }); - - const fieldIds = mockRecords[MARC_TYPES.BIB].slice(1).map(field => field.id); - - expect(mockRefreshPageData).toHaveBeenCalledWith(fieldIds); - }); - }); - - describe('when click on save button', () => { - it('should show on save message and redirect on load page', async () => { - const mockOnSave = jest.fn(); - - const { getByRole } = renderQuickMarcEditWrapper({ - instance, - mutator, - onSave: mockOnSave, - quickMarcContext: { - relatedRecordVersion: 1, - }, - }); - - await act(async () => { fireEvent.click(getByRole('button', { name: 'stripes-acq-components.FormFooter.save' })); }); - - expect(mutator.quickMarcEditInstance.GET).toHaveBeenCalled(); - expect(mockUpdateMarcRecord).toHaveBeenCalled(); - - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.save.success.processing' }); - expect(mockOnSave).toHaveBeenCalled(); - }); - - it('should edit record with correct payload', async () => { - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - }); - - const formValuesForEdit = { - '_actionType': 'edit', - 'externalId': '17064f9d-0362-468d-8317-5984b7efd1b5', - 'fields': [ - { - 'content': 'in00000000003', - 'indicators': undefined, - 'linkDetails': undefined, - 'tag': '001', - }, - { - 'content': 'in00000000022', - 'indicators': undefined, - 'linkDetails': undefined, - 'tag': '004', - }, - { - 'content': { - 'Audn': 'j', - 'BLvl': 'm', - 'Biog': '\\', - 'Conf': '0', - 'Cont': ['6', '5', '2', 'y'], - 'CrTp': 'a', - 'Ctry': '\\\\\\', - 'Date1': '\\\\\\\\', - 'Date2': '\\\\\\\\', - 'DtSt': 'u', - 'Entered': '240404', - 'Fest': '1', - 'Form': 's', - 'GPub': 's', - 'Ills': ['p', 'o', 'm', 'l'], - 'Indx': '0', - 'Lang': '\\\\\\', - 'LitF': 'p', - 'MRec': '\\', - 'Proj': '\\\\', - 'Srce': '\\', - 'Type': 'a', - }, - 'indicators': undefined, - 'linkDetails': undefined, - 'tag': '008', - }, - { - 'content': '$a Coates, Ta-Nehisi $e author. $0 id.loc.gov/authorities/names/n2008001084 $9 4808f6ae-8379-41e9-a795-915ac4751668', - 'indicators': [ - '1', - '\\', - ], - 'linkDetails': { - 'authorityId': '4808f6ae-8379-41e9-a795-915ac4751668', - 'authorityNaturalId': 'n2008001084', - 'linkingRuleId': 1, - 'status': 'ACTUAL', - }, - 'tag': '100', - }, - { - 'content': '$a Ma, Wei $0 id.loc.gov/authorities/names/n84160718 $9 495884af-28d7-4d69-85e4-e84c5de693db', - 'indicators': [ - '\\', - '\\', - ], - 'linkDetails': { - 'authorityId': '495884af-28d7-4d69-85e4-e84c5de693db', - 'authorityNaturalId': 'n84160718', - 'linkingRuleId': 1, - 'status': 'NEW', - }, - 'tag': '100', - }, - { - 'content': '$a Title', - 'indicators': undefined, - 'linkDetails': undefined, - 'tag': '245', - }, - { - 'content': '$b KU/CC/DI/A $t 3 $h M3 $i .M93 1955 $m + $x Rec\'d in Music Lib ;', - 'indicators': [ - '0', - '1', - ], - 'linkDetails': undefined, - 'tag': '852', - }, - { - 'content': '$a ABS3966CU004', - 'indicators': [ - '1', - '\\', - ], - 'linkDetails': undefined, - 'tag': '014', - }, - { - 'content': '20221228135005.0', - 'indicators': undefined, - 'linkDetails': undefined, - 'tag': '005', - }, - { - 'content': '$s 9585bca7-8e4c-4cbb-bab4-46c5832e7654 $i 9012727e-bffc-4298-a424-7da30d6008aa', - 'indicators': [ - 'f', - 'f', - ], - 'linkDetails': undefined, - 'tag': '999', - }, - ], - 'leader': '00000nam\\a2200000uu\\4500', - 'marcFormat': 'BIBLIOGRAPHIC', - 'parsedRecordDtoId': '1bf159d9-4da8-4c3f-9aac-c83e68356bbf', - 'parsedRecordId': '1bf159d9-4da8-4c3f-9aac-c83e68356bbf', - 'records': undefined, - 'suppressDiscovery': false, - 'updateInfo': { - 'recordState': 'NEW', - }, - }; - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockUpdateMarcRecord).toHaveBeenCalledWith(formValuesForEdit); - }); - - describe('and there is a linked field', () => { - it('should run backend validation with the content from all split fields', async () => { - const formValues = mockFormValues(MARC_TYPES.BIB); - - const field240 = { - 'tag': '240', - 'content': '$a a $0 http://id.loc.gov/authorities/names/n2016004081 $9 58e3deb0-d1c9-4e22-918d-a393f627e18c', - 'indicators': ['\\', '\\'], - 'isProtected': false, - 'linkDetails': { - 'authorityId': '58e3deb0-d1c9-4e22-918d-a393f627e18c', - 'authorityNaturalId': 'n2016004081', - 'linkingRuleId': 5, - 'status': 'ACTUAL', - }, - 'id': '5cc17747-0b0a-44f6-807e-fd28138bbcaa', - '_isDeleted': false, - '_isLinked': true, - 'subfieldGroups': { - 'controlled': '$a a', - 'uncontrolledAlpha': '$3 test', - 'zeroSubfield': '$0 http://id.loc.gov/authorities/names/n2016004081', - 'nineSubfield': '$9 58e3deb0-d1c9-4e22-918d-a393f627e18c', - 'uncontrolledNumber': '', - }, - }; - - mockFormValues.mockReturnValue({ - ...formValues, - records: [ - field240, - ...formValues.records, - ], - }); - - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockValidateFetch).toHaveBeenCalledWith(expect.objectContaining({ - body: expect.objectContaining({ - fields: expect.arrayContaining([ - expect.objectContaining({ - tag: '240', - content: '$a a $3 test $0 http://id.loc.gov/authorities/names/n2016004081 $9 58e3deb0-d1c9-4e22-918d-a393f627e18c', - indicators: ['\\', '\\'], - }), - ]), - }), - })); - }); - }); - - describe('when there is an error during POST request', () => { - it('should show an error message', async () => { - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - }); - - mockUpdateMarcRecord.mockRejectedValueOnce({}); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockUpdateMarcRecord).toHaveBeenCalled(); - - expect(mockShowCallout).toHaveBeenCalledWith({ - messageId: 'ui-quick-marc.record.save.error.generic', - type: 'error', - }); - }); - }); - - describe('when there is an error during POST request due to optimistic locking', () => { - it('should show optimistic locking banner', async () => { - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - }); - - mockUpdateMarcRecord.mockRejectedValueOnce({ - json: jest.fn().mockResolvedValue({ message: 'optimistic locking' }), - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockUpdateMarcRecord).toHaveBeenCalled(); - expect(getByText('stripes-components.optimisticLocking.saveError')).toBeDefined(); - }); - }); - - describe('when there is a record returned with different version', () => { - it('should show up a conflict detection banner and not make an update request', async () => { - mutator.quickMarcEditInstance.GET = jest.fn(() => Promise.resolve({ - ...instance, - _version: 1, - })); - - const { getByText } = renderQuickMarcEditWrapper({ - instance: { - ...instance, - _version: 0, - }, - mutator, - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mutator.quickMarcEditInstance.GET).toHaveBeenCalled(); - expect(mockUpdateMarcRecord).not.toHaveBeenCalled(); - expect(getByText('stripes-components.optimisticLocking.saveError')).toBeDefined(); - }); - }); - - describe('when record not found (already deleted)', () => { - it('should reveal an error message', async () => { - mutator.quickMarcEditInstance.GET = jest.fn().mockRejectedValue({ httpStatus: 404 }); - - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - expect(mockShowCallout).toHaveBeenCalledWith({ - messageId: 'ui-quick-marc.record.save.error.notFound', - type: 'error', - }); - }); - }); - - it('should actualize links', async () => { - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - const expectedFormValues = { - marcFormat: MARC_TYPES.BIB.toUpperCase(), - records: expect.arrayContaining([ - expect.objectContaining({ - tag: 'LDR', - content: bibLeaderString, - }), - expect.objectContaining({ - tag: '100', - content: '$a Coates, Ta-Nehisi $e author. $0 id.loc.gov/authorities/names/n2008001084 $9 4808f6ae-8379-41e9-a795-915ac4751668', - linkDetails: { - authorityId: '4808f6ae-8379-41e9-a795-915ac4751668', - authorityNaturalId: 'n2008001084', - linkingRuleId: 1, - status: 'ACTUAL', - }, - }), - expect.objectContaining({ - tag: '100', - content: '$a Ma, Wei $0 id.loc.gov/authorities/names/n84160718 $9 495884af-28d7-4d69-85e4-e84c5de693db', - prevContent: '$a test', - linkDetails: { - authorityNaturalId: 'n84160718', - authorityId: '495884af-28d7-4d69-85e4-e84c5de693db', - linkingRuleId: 1, - status: 'NEW', - }, - }), - ]), - }; - - expect(mockActualizeLinks).toHaveBeenCalledWith(expect.objectContaining(expectedFormValues)); - }); - - describe('when a member tenant edits a shared record', () => { - it('should apply the central tenant id for all authority linking requests', async () => { - applyCentralTenantInHeaders.mockReturnValue(true); - - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(useAuthorityLinking).toHaveBeenCalledWith({ - marcType: MARC_TYPES.BIB, - action: QUICK_MARC_ACTIONS.EDIT, - }); - expect(useMarcRecordMutation).toHaveBeenCalledWith({ tenantId: 'consortia' }); - }); - }); - - describe('when marc type is not a bibliographic', () => { - it('should not call actualizeLinks', async () => { - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - marcType: MARC_TYPES.AUTHORITY, - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockActualizeLinks).not.toHaveBeenCalled(); - }); - }); - }); - }); - - describe('when is authority marc type', () => { - describe('and record is linked to a bib record', () => { - describe('and changing 1XX field', () => { - it('should display confirmation modal', async () => { - const { - getByTestId, - getByText, - } = renderQuickMarcEditWrapper({ - instance, - mutator, - marcType: MARC_TYPES.AUTHORITY, - linksCount: 1, - }); - - await act(async () => { fireEvent.change(getByTestId('content-field-8'), { target: { value: '$a Civil war edited' } }); }); - await act(async () => { fireEvent.click(getByText('ui-quick-marc.record.save.continue')); }); - - expect(getByText('ui-quick-marc.update-linked-bib-fields.modal.label')).toBeDefined(); - }); - }); - }); - - describe('when click on "Save & keep editing" button', () => { - it('should show on save message and stay on the edit page', async () => { - const mockOnClose = jest.fn(); - const mockOnSave = jest.fn(); - - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - marcType: MARC_TYPES.AUTHORITY, - onClose: mockOnClose, - onSave: mockOnSave, - }); - - await act(async () => { fireEvent.click(getByText('ui-quick-marc.record.save.continue')); }); - - expect(mutator.quickMarcEditInstance.GET).toHaveBeenCalled(); - expect(mockUpdateMarcRecord).toHaveBeenCalled(); - - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.save.success.processing' }); - expect(mockOnClose).not.toHaveBeenCalled(); - expect(mockOnSave).not.toHaveBeenCalled(); - }); - }); - - describe('when click on save button', () => { - it('should show on save message and redirect on load page', async () => { - const mockOnSave = jest.fn(); - - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - marcType: MARC_TYPES.AUTHORITY, - onSave: mockOnSave, - }); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mutator.quickMarcEditInstance.GET).toHaveBeenCalled(); - expect(mockUpdateMarcRecord).toHaveBeenCalled(); - - expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.save.success.processing' }); - - expect(mockOnSave).toHaveBeenCalled(); - }); - - describe('when there is an error during POST request', () => { - it('should show an error message', async () => { - const { getByText } = renderQuickMarcEditWrapper({ - instance, - mutator, - marcType: MARC_TYPES.AUTHORITY, - }); - - mockUpdateMarcRecord.mockRejectedValueOnce({}); - - await act(async () => { fireEvent.click(getByText('stripes-acq-components.FormFooter.save')); }); - - expect(mockUpdateMarcRecord).toHaveBeenCalled(); - - expect(mockShowCallout).toHaveBeenCalledWith({ - messageId: 'ui-quick-marc.record.save.error.generic', - type: 'error', - }); - }); - }); - }); - }); -}); diff --git a/src/QuickMarcEditor/QuickMarcEditor.js b/src/QuickMarcEditor/QuickMarcEditor.js index 3f90b497..db4eff1d 100644 --- a/src/QuickMarcEditor/QuickMarcEditor.js +++ b/src/QuickMarcEditor/QuickMarcEditor.js @@ -93,7 +93,6 @@ const QuickMarcEditor = ({ action, instance, onClose, - onSave, handleSubmit, submitting, pristine, @@ -125,11 +124,13 @@ const QuickMarcEditor = ({ const [isValidationModalOpen, setIsValidationModalOpen] = useState(false); const [isLoadingLinkSuggestions, setIsLoadingLinkSuggestions] = useState(false); const [isValidatedCurrentValues, setIsValidatedCurrentValues] = useState(false); - const continueAfterSave = useRef(false); const formRef = useRef(null); const lastFocusedInput = useRef(null); const confirmationChecks = useRef({ ...REQUIRED_CONFIRMATIONS }); - const { setValidationErrors, setRelatedRecordVersion } = useContext(QuickMarcContext); + const { + setValidationErrors, + continueAfterSave, + } = useContext(QuickMarcContext); const { hasErrorIssues, isBackEndValidationMarcType } = useValidation(); const isConsortiaEnv = stripes.hasInterface('consortia'); @@ -152,22 +153,12 @@ const QuickMarcEditor = ({ const saveFormDisabled = submitting || pristine; - const handleSubmitResponse = useCallback((updatedRecord) => { - if (!updatedRecord?.version) { - continueAfterSave.current = false; - - return; - } - + const handleSubmitResponse = useCallback(() => { if (continueAfterSave.current) { - setRelatedRecordVersion(updatedRecord.version); + continueAfterSave.current = false; lastFocusedInput.current?.focus(); - - return; } - - onSave(); - }, [setRelatedRecordVersion, onSave]); + }, [continueAfterSave]); const closeModals = () => { setIsDeleteModalOpened(false); @@ -250,6 +241,8 @@ const QuickMarcEditor = ({ newValidationErrors = await validate(getState().values); closeValidationModal(); } catch (err) { + // eslint-disable-next-line no-console + console.error(err); closeValidationModal(); showCallout({ @@ -290,6 +283,7 @@ const QuickMarcEditor = ({ isValidatedCurrentValues, setIsValidatedCurrentValues, manageBackendValidationModal, + continueAfterSave, ]); const paneFooter = useMemo(() => { @@ -305,7 +299,7 @@ const QuickMarcEditor = ({ const end = ( <> - {action === QUICK_MARC_ACTIONS.EDIT && ( + {([MARC_TYPES.BIB, MARC_TYPES.AUTHORITY].includes(marcType) || action === QUICK_MARC_ACTIONS.EDIT) && (