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) && (
);
- }, [confirmSubmit, saveFormDisabled, onClose, action]);
+ }, [confirmSubmit, saveFormDisabled, onClose, action, marcType]);
const getConfirmModalMessage = () => (
{
if (!httpError) {
@@ -642,7 +636,6 @@ QuickMarcEditor.propTypes = {
externalRecordPath: PropTypes.string,
instance: PropTypes.object,
onClose: PropTypes.func.isRequired,
- onSave: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool,
pristine: PropTypes.bool,
diff --git a/src/QuickMarcEditor/QuickMarcEditor.test.js b/src/QuickMarcEditor/QuickMarcEditor.test.js
index dd36babc..ed83b0f2 100644
--- a/src/QuickMarcEditor/QuickMarcEditor.test.js
+++ b/src/QuickMarcEditor/QuickMarcEditor.test.js
@@ -82,7 +82,6 @@ jest.mock('./QuickMarcRecordInfo', () => {
});
const onCloseMock = jest.fn();
-const onSaveMock = jest.fn();
const onSubmitMock = jest.fn(() => Promise.resolve({ version: 1 }));
const mockShowCallout = jest.fn();
const mockValidate = jest.fn().mockReturnValue({});
@@ -147,7 +146,6 @@ const renderQuickMarcEditor = (props = {}, { quickMarcContext } = {}) => (render
action={QUICK_MARC_ACTIONS.EDIT}
instance={instance}
onClose={onCloseMock}
- onSave={onSaveMock}
onSubmit={onSubmitMock}
mutators={{
addRecord: jest.fn(),
@@ -725,7 +723,6 @@ describe('Given QuickMarcEditor', () => {
await waitFor(() => {
expect(onSubmitMock).toHaveBeenCalled();
- expect(onSaveMock).toHaveBeenCalledWith();
});
});
});
@@ -895,7 +892,6 @@ describe('Given QuickMarcEditor', () => {
await waitFor(() => {
expect(onSubmitMock).toHaveBeenCalled();
expect(queryByText('Confirmation modal'));
- expect(onSaveMock).toHaveBeenCalledWith();
});
});
@@ -1151,7 +1147,6 @@ describe('Given QuickMarcEditor', () => {
await waitFor(() => {
expect(onSubmitMock).toHaveBeenCalled();
expect(queryByText('Confirmation modal')).toBeNull();
- expect(onSaveMock).toHaveBeenCalled();
});
});
diff --git a/src/QuickMarcEditor/QuickMarcEditorContainer.js b/src/QuickMarcEditor/QuickMarcEditorContainer.js
index 09bc755a..552da16c 100644
--- a/src/QuickMarcEditor/QuickMarcEditorContainer.js
+++ b/src/QuickMarcEditor/QuickMarcEditorContainer.js
@@ -1,8 +1,9 @@
-import {
+import React, {
useEffect,
useState,
useCallback,
useMemo,
+ useContext,
} from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';
@@ -17,8 +18,10 @@ import {
} from '@folio/stripes-acq-components';
import { getHeaders } from '@folio/stripes-marc-components';
+import QuickMarcEditor from './QuickMarcEditor';
import { useAuthorityLinksCount } from '../queries';
-import { QuickMarcProvider } from '../contexts';
+import { QuickMarcContext } from '../contexts';
+import { useSaveRecord } from './useSaveRecord';
import {
EXTERNAL_INSTANCE_APIS,
MARC_RECORD_API,
@@ -40,18 +43,14 @@ import {
import { QUICK_MARC_ACTIONS } from './constants';
const propTypes = {
- action: PropTypes.oneOf(Object.values(QUICK_MARC_ACTIONS)).isRequired,
onClose: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
externalRecordPath: PropTypes.string.isRequired,
history: ReactRouterPropTypes.history.isRequired,
location: ReactRouterPropTypes.location.isRequired,
- marcType: PropTypes.oneOf(Object.values(MARC_TYPES)).isRequired,
mutator: PropTypes.object.isRequired,
match: ReactRouterPropTypes.match.isRequired,
- resources: PropTypes.object.isRequired,
stripes: PropTypes.object.isRequired,
- wrapper: PropTypes.func.isRequired,
onCheckCentralTenantPerm: PropTypes.func,
};
@@ -62,17 +61,13 @@ const createRecordDefaults = {
};
const QuickMarcEditorContainer = ({
- action,
mutator,
match,
onClose,
onSave,
- wrapper: Wrapper,
history,
location,
- marcType,
externalRecordPath,
- resources,
stripes,
onCheckCentralTenantPerm,
}) => {
@@ -80,8 +75,16 @@ const QuickMarcEditorContainer = ({
externalId,
instanceId,
} = match.params;
- const [instance, setInstance] = useState();
- const [marcRecord, setMarcRecord] = useState();
+
+ const {
+ action,
+ marcType,
+ initialValues,
+ instance,
+ setInstance,
+ setMarcRecord,
+ setRelatedRecordVersion,
+ } = useContext(QuickMarcContext);
const [locations, setLocations] = useState();
const [isLoading, setIsLoading] = useState(true);
const [fixedFieldSpec, setFixedFieldSpec] = useState();
@@ -118,8 +121,11 @@ const QuickMarcEditorContainer = ({
return `${externalRecordPath}/${externalId}`;
}, [externalRecordPath, marcType, externalId, instanceId, action]);
- const loadData = useCallback(async (fieldIds) => {
- const path = action === QUICK_MARC_ACTIONS.CREATE && marcType === MARC_TYPES.HOLDINGS
+ const loadData = useCallback(async (fieldIds, nextAction, nextExternalId) => {
+ const _action = nextAction || action;
+ const _externalId = nextExternalId || externalId;
+
+ const path = _action === QUICK_MARC_ACTIONS.CREATE && marcType === MARC_TYPES.HOLDINGS
? EXTERNAL_INSTANCE_APIS[MARC_TYPES.BIB]
: EXTERNAL_INSTANCE_APIS[marcType];
@@ -127,17 +133,17 @@ const QuickMarcEditorContainer = ({
? { headers: getHeaders(centralTenantId, token, locale) }
: {};
- const instancePromise = action === QUICK_MARC_ACTIONS.CREATE && marcType !== MARC_TYPES.HOLDINGS
+ const instancePromise = _action === QUICK_MARC_ACTIONS.CREATE && marcType !== MARC_TYPES.HOLDINGS
? Promise.resolve({})
: mutator.quickMarcEditInstance.GET({
- path: `${path}/${externalId}`,
+ path: `${path}/${_externalId}`,
...headers,
});
- const marcRecordPromise = action === QUICK_MARC_ACTIONS.CREATE
+ const marcRecordPromise = _action === QUICK_MARC_ACTIONS.CREATE
? Promise.resolve({})
: mutator.quickMarcEditMarcRecord.GET({
- params: { externalId },
+ params: { externalId: _externalId },
...headers,
});
@@ -169,7 +175,7 @@ const QuickMarcEditorContainer = ({
]) => {
let dehydratedMarcRecord;
- if (action === QUICK_MARC_ACTIONS.CREATE) {
+ if (_action === QUICK_MARC_ACTIONS.CREATE) {
dehydratedMarcRecord = createRecordDefaults[marcType](instanceResponse, fixedFieldSpecResponse);
} else {
dehydratedMarcRecord = dehydrateMarcRecordResponse(
@@ -180,17 +186,20 @@ const QuickMarcEditorContainer = ({
);
}
- const formattedMarcRecord = formatMarcRecordByQuickMarcAction(dehydratedMarcRecord, action, marcType);
+ const formattedMarcRecord = formatMarcRecordByQuickMarcAction(dehydratedMarcRecord, _action, marcType);
const marcRecordWithInternalProps = addInternalFieldProperties(formattedMarcRecord);
const marcRecordWithSplitFields = splitFields(marcRecordWithInternalProps, linkingRulesResponse);
+ setRelatedRecordVersion(instanceResponse?._version);
setInstance(instanceResponse);
setMarcRecord(marcRecordWithSplitFields);
setLocations(locationsResponse);
setFixedFieldSpec(fixedFieldSpecResponse);
setIsLoading(false);
})
- .catch(() => {
+ .catch((err) => {
+ // eslint-disable-next-line no-console
+ console.error(err);
showCallout({ messageId: 'ui-quick-marc.record.load.error', type: 'error' });
handleClose();
});
@@ -198,16 +207,29 @@ const QuickMarcEditorContainer = ({
}, [
externalId,
history,
+ action,
marcType,
centralTenantId,
token,
locale,
isRequestToCentralTenantFromMember,
+ setRelatedRecordVersion,
]);
+ const { onSubmit, httpError, runValidation } = useSaveRecord({
+ linksCount,
+ locations,
+ fixedFieldSpec,
+ mutator,
+ refreshPageData: loadData,
+ onClose: handleClose,
+ onSave: handleSave,
+ });
+
useEffect(() => {
loadData();
- }, [loadData]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
if (isLoading) {
return (
@@ -220,26 +242,22 @@ const QuickMarcEditorContainer = ({
}
return (
-
-
-
+
);
};
diff --git a/src/QuickMarcEditor/QuickMarcEditorContainer.test.js b/src/QuickMarcEditor/QuickMarcEditorContainer.test.js
index 673eab97..f4852e4c 100644
--- a/src/QuickMarcEditor/QuickMarcEditorContainer.test.js
+++ b/src/QuickMarcEditor/QuickMarcEditorContainer.test.js
@@ -12,8 +12,6 @@ import faker from 'faker';
import '@folio/stripes-acq-components/test/jest/__mock__';
import QuickMarcEditorContainer from './QuickMarcEditorContainer';
-import QuickMarcEditWrapper from './QuickMarcEditWrapper';
-import QuickMarcDeriveWrapper from './QuickMarcDeriveWrapper';
import { QUICK_MARC_ACTIONS } from './constants';
import {
MARC_TYPES,
@@ -100,8 +98,6 @@ const record = {
fields: [],
};
-const resources = {};
-
const locations = [];
const externalRecordPath = '/external/record/path';
@@ -109,14 +105,21 @@ const externalRecordPath = '/external/record/path';
const mockOnClose = jest.fn();
const mockOnSave = jest.fn();
-const renderQuickMarcEditorContainer = ({ history, ...props } = {}) => (render(
-
+const renderQuickMarcEditorContainer = ({
+ history,
+ action = QUICK_MARC_ACTIONS.EDIT,
+ marcType = MARC_TYPES.BIB,
+ ...props
+} = {}) => (render(
+
false}
/>
@@ -160,8 +163,6 @@ describe('Given Quick Marc Editor Container', () => {
await renderQuickMarcEditorContainer({
mutator,
onClose: jest.fn(),
- action: QUICK_MARC_ACTIONS.EDIT,
- wrapper: QuickMarcEditWrapper,
});
});
@@ -175,7 +176,6 @@ describe('Given Quick Marc Editor Container', () => {
mutator,
onClose: jest.fn(),
action: QUICK_MARC_ACTIONS.EDIT,
- wrapper: QuickMarcEditWrapper,
marcType: MARC_TYPES.AUTHORITY,
});
});
@@ -195,7 +195,6 @@ describe('Given Quick Marc Editor Container', () => {
mutator,
onClose,
action: QUICK_MARC_ACTIONS.DERIVE,
- wrapper: QuickMarcEditWrapper,
});
});
});
@@ -216,8 +215,6 @@ describe('Given Quick Marc Editor Container', () => {
const renderer = await renderQuickMarcEditorContainer({
mutator,
onClose: jest.fn(),
- action: QUICK_MARC_ACTIONS.EDIT,
- wrapper: QuickMarcEditWrapper,
});
getByText = renderer.getByText;
@@ -245,7 +242,6 @@ describe('Given Quick Marc Editor Container', () => {
mutator,
onClose: jest.fn(),
action: QUICK_MARC_ACTIONS.CREATE,
- wrapper: QuickMarcEditWrapper,
});
});
@@ -371,7 +367,6 @@ describe('Given Quick Marc Editor Container', () => {
onClose: jest.fn(),
action: QUICK_MARC_ACTIONS.CREATE,
marcType: MARC_TYPES.AUTHORITY,
- wrapper: QuickMarcEditWrapper,
});
});
@@ -455,7 +450,6 @@ describe('Given Quick Marc Editor Container', () => {
onClose: jest.fn(),
action: QUICK_MARC_ACTIONS.CREATE,
marcType: MARC_TYPES.HOLDINGS,
- wrapper: QuickMarcEditWrapper,
});
});
@@ -527,7 +521,6 @@ describe('Given Quick Marc Editor Container', () => {
mutator,
onClose,
action: QUICK_MARC_ACTIONS.EDIT,
- wrapper: QuickMarcEditWrapper,
marcType: MARC_TYPES.BIB,
});
@@ -561,7 +554,6 @@ describe('Given Quick Marc Editor Container', () => {
mutator,
onClose: jest.fn(),
action: QUICK_MARC_ACTIONS.DERIVE,
- wrapper: QuickMarcDeriveWrapper,
stripes,
});
});
@@ -607,7 +599,6 @@ describe('Given Quick Marc Editor Container', () => {
mutator: newMutator,
onClose: jest.fn(),
action: QUICK_MARC_ACTIONS.DERIVE,
- wrapper: QuickMarcDeriveWrapper,
stripes,
location: newLocation,
});
diff --git a/src/QuickMarcEditor/index.js b/src/QuickMarcEditor/index.js
index 40c55c50..73b15d8a 100644
--- a/src/QuickMarcEditor/index.js
+++ b/src/QuickMarcEditor/index.js
@@ -1,5 +1,2 @@
export { default as QuickMarcEditor } from './QuickMarcEditor';
export { default as QuickMarcEditorContainer } from './QuickMarcEditorContainer';
-export { default as QuickMarcDeriveWrapper } from './QuickMarcDeriveWrapper';
-export { default as QuickMarcCreateWrapper } from './QuickMarcCreateWrapper';
-export { default as QuickMarcEditWrapper } from './QuickMarcEditWrapper';
diff --git a/src/QuickMarcEditor/useSaveRecord/index.js b/src/QuickMarcEditor/useSaveRecord/index.js
new file mode 100644
index 00000000..4e49c318
--- /dev/null
+++ b/src/QuickMarcEditor/useSaveRecord/index.js
@@ -0,0 +1 @@
+export * from './useSaveRecord';
diff --git a/src/QuickMarcEditor/useSaveRecord/useSaveRecord.js b/src/QuickMarcEditor/useSaveRecord/useSaveRecord.js
new file mode 100644
index 00000000..f862f120
--- /dev/null
+++ b/src/QuickMarcEditor/useSaveRecord/useSaveRecord.js
@@ -0,0 +1,167 @@
+import {
+ useCallback,
+ useContext,
+ useMemo,
+} from 'react';
+import { useLocation } from 'react-router-dom';
+import flow from 'lodash/flow';
+
+import { useStripes } from '@folio/stripes/core';
+
+import {
+ useAuthorityLinking,
+ useValidation,
+} from '../../hooks';
+import { QUICK_MARC_ACTIONS } from '../constants';
+import {
+ applyCentralTenantInHeaders,
+ autopopulateFixedField,
+ autopopulateIndicators,
+ autopopulateMaterialCharsField,
+ autopopulatePhysDescriptionField,
+ autopopulateSubfieldSection,
+ cleanBytesFields,
+ combineSplitFields,
+ formatLeaderForSubmit,
+ removeDeletedRecords,
+ removeDuplicateSystemGeneratedFields,
+ removeEnteredDate,
+ removeFieldsForDerive,
+ removeRowsWithoutContent,
+} from '../utils';
+import { useSubmitRecord } from './useSubmitRecord';
+import { QuickMarcContext } from '../../contexts';
+
+const useSaveRecord = ({
+ linksCount,
+ locations,
+ fixedFieldSpec,
+ mutator,
+ refreshPageData,
+ onClose,
+ onSave,
+}) => {
+ const location = useLocation();
+ const stripes = useStripes();
+
+ const { action, marcType, initialValues, instance } = useContext(QuickMarcContext);
+ const { linkableBibFields, linkingRules, sourceFiles } = useAuthorityLinking({ marcType, action });
+
+ const isRequestToCentralTenantFromMember = applyCentralTenantInHeaders(location, stripes, marcType);
+ const centralTenantId = stripes.user.user.consortium?.centralTenantId;
+ const tenantId = isRequestToCentralTenantFromMember ? centralTenantId : '';
+
+ const validationContext = useMemo(() => ({
+ initialValues,
+ marcType,
+ action,
+ linkableBibFields,
+ linkingRules,
+ fixedFieldSpec,
+ instanceId: instance?.id,
+ sourceFiles,
+ linksCount,
+ naturalId: instance?.naturalId,
+ locations,
+ }), [
+ action,
+ instance?.naturalId,
+ linksCount,
+ initialValues,
+ marcType,
+ locations,
+ linkableBibFields,
+ linkingRules,
+ sourceFiles,
+ fixedFieldSpec,
+ instance?.id,
+ ]);
+
+ const { validate } = useValidation(validationContext, tenantId);
+
+ const prepareForSubmit = useCallback((formValues) => {
+ let handlers = [];
+
+ if (action === QUICK_MARC_ACTIONS.CREATE) {
+ handlers = [
+ removeDeletedRecords,
+ removeRowsWithoutContent,
+ autopopulateIndicators,
+ marcRecord => autopopulateFixedField(marcRecord, marcType, fixedFieldSpec),
+ autopopulatePhysDescriptionField,
+ autopopulateMaterialCharsField,
+ marcRecord => autopopulateSubfieldSection(marcRecord, marcType),
+ marcRecord => cleanBytesFields(marcRecord, fixedFieldSpec, marcType),
+ marcRecord => formatLeaderForSubmit(marcType, marcRecord),
+ combineSplitFields,
+ ];
+ } else if (action === QUICK_MARC_ACTIONS.EDIT) {
+ handlers = [
+ removeDeletedRecords,
+ removeDuplicateSystemGeneratedFields,
+ marcRecord => formatLeaderForSubmit(marcType, marcRecord),
+ autopopulateIndicators,
+ marcRecord => autopopulateFixedField(marcRecord, marcType, fixedFieldSpec),
+ autopopulatePhysDescriptionField,
+ autopopulateMaterialCharsField,
+ marcRecord => autopopulateSubfieldSection(marcRecord, marcType),
+ marcRecord => cleanBytesFields(marcRecord, fixedFieldSpec, marcType),
+ combineSplitFields,
+ ];
+ } else if (action === QUICK_MARC_ACTIONS.DERIVE) {
+ handlers = [
+ 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,
+ ];
+ }
+
+ const formValuesForCreate = flow(handlers)(formValues);
+
+ return formValuesForCreate;
+ }, [marcType, fixedFieldSpec, action]);
+
+ const {
+ onCreate,
+ onEdit,
+ onDerive,
+ httpError,
+ } = useSubmitRecord({
+ prepareForSubmit,
+ mutator,
+ linksCount,
+ isRequestToCentralTenantFromMember,
+ tenantId,
+ refreshPageData,
+ onClose,
+ onSave,
+ });
+
+ const runValidation = useCallback(async (formValues) => {
+ const formValuesForValidation = prepareForSubmit(formValues);
+
+ return validate(formValuesForValidation.records);
+ }, [validate, prepareForSubmit]);
+
+ const submitMap = {
+ [QUICK_MARC_ACTIONS.CREATE]: onCreate,
+ [QUICK_MARC_ACTIONS.EDIT]: onEdit,
+ [QUICK_MARC_ACTIONS.DERIVE]: onDerive,
+ };
+
+ return {
+ onSubmit: submitMap[action],
+ httpError,
+ runValidation,
+ };
+};
+
+export { useSaveRecord };
diff --git a/src/QuickMarcEditor/useSaveRecord/useSaveRecord.test.js b/src/QuickMarcEditor/useSaveRecord/useSaveRecord.test.js
new file mode 100644
index 00000000..af76c079
--- /dev/null
+++ b/src/QuickMarcEditor/useSaveRecord/useSaveRecord.test.js
@@ -0,0 +1,1997 @@
+import { act } from 'react';
+import { useParams } from 'react-router-dom';
+import { createMemoryHistory } from 'history';
+import faker from 'faker';
+
+import { renderHook } from '@folio/jest-config-stripes/testing-library/react';
+
+import { QUICK_MARC_ACTIONS } from '../constants';
+import { ERROR_TYPES, MARC_TYPES } from '../../common';
+import { useAuthorityLinking } from '../../hooks';
+import {
+ useMarcRecordMutation,
+ useValidate,
+} from '../../queries';
+import Harness from '../../../test/jest/helpers/harness';
+import { useSaveRecord } from './useSaveRecord';
+import fixedFieldSpecBib from '../../../test/mocks/fixedFieldSpecBib';
+import fixedFieldSpecAuth from '../../../test/mocks/fixedFieldSpecAuth';
+import {
+ authorityLeader,
+ bibLeader,
+ bibLeaderString,
+ holdingsLeader,
+} from '../../../test/jest/fixtures/leaders';
+import {
+ applyCentralTenantInHeaders,
+ saveLinksToNewRecord,
+} from '../utils';
+
+const mockShowCallout = jest.fn();
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useParams: jest.fn(() => ({})),
+}));
+
+jest.mock('@folio/stripes-acq-components', () => ({
+ ...jest.requireActual('@folio/stripes-acq-components'),
+ useShowCallout: jest.fn(() => mockShowCallout),
+}));
+
+jest.mock('../utils', () => ({
+ ...jest.requireActual('../utils'),
+ applyCentralTenantInHeaders: jest.fn(() => false),
+ saveLinksToNewRecord: jest.fn().mockResolvedValue(),
+}));
+
+jest.mock('../../hooks', () => ({
+ ...jest.requireActual('../../hooks'),
+ useAuthorityLinking: jest.fn(),
+}));
+
+jest.mock('../../queries', () => ({
+ ...jest.requireActual('../../queries'),
+ useMarcRecordMutation: jest.fn(),
+ useLccnDuplicateConfig: jest.fn().mockReturnValue({
+ isLoading: false,
+ duplicateLccnCheckingEnabled: false,
+ }),
+ useValidate: jest.fn(),
+}));
+
+jest.mock('../getQuickMarcRecordStatus', () => {
+ return jest.fn().mockResolvedValue({ externalId: 'externalId-1' });
+});
+
+const getWrapper = ({ quickMarcContext, history }) => ({ children }) => (
+
+ {children}
+
+);
+
+const getMutator = (instance) => ({
+ quickMarcEditMarcRecord: {
+ POST: jest.fn().mockResolvedValue({}),
+ },
+ quickMarcRecordStatus: {
+ GET: jest.fn(() => Promise.resolve({})),
+ },
+ quickMarcEditInstance: {
+ GET: jest.fn(() => Promise.resolve(instance)),
+ },
+ locations: {
+ GET: () => Promise.resolve({}),
+ },
+});
+const mockRefreshPageData = jest.fn().mockResolvedValue(null);
+const mockOnClose = jest.fn();
+const mockOnSave = jest.fn();
+const mockActualizeLinks = jest.fn((formValuesToProcess) => Promise.resolve(formValuesToProcess));
+const mockUpdateMarcRecord = jest.fn().mockResolvedValue();
+const mockValidateFetch = jest.fn().mockResolvedValue({});
+const mockComplete = jest.fn();
+
+const _api = null;
+const basePath = '/base-path';
+
+const locations = [{
+ code: 'KU/CC/DI/A',
+}];
+
+const mockSpecs = {
+ [MARC_TYPES.BIB]: fixedFieldSpecBib,
+ [MARC_TYPES.AUTHORITY]: fixedFieldSpecAuth,
+};
+
+const getInstance = () => ({
+ id: faker.random.uuid(),
+ title: 'ui-quick-marc.record.edit.title',
+});
+
+const getInitialProps = (marcType, instance) => ({
+ linksCount: 0,
+ locations,
+ fixedFieldSpec: mockSpecs[marcType],
+ mutator: getMutator(instance),
+ refreshPageData: mockRefreshPageData,
+ onClose: mockOnClose,
+ onSave: mockOnSave,
+});
+
+const leadersMap = {
+ [MARC_TYPES.BIB]: bibLeader,
+ [MARC_TYPES.HOLDINGS]: holdingsLeader,
+ [MARC_TYPES.AUTHORITY]: authorityLeader,
+};
+
+const recordsMap = {
+ [QUICK_MARC_ACTIONS.CREATE]: () => ({
+ [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': ['\\', '\\'],
+ },
+ ],
+ }),
+ [QUICK_MARC_ACTIONS.EDIT]: () => ({
+ [MARC_TYPES.HOLDINGS]: [],
+ [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': '035',
+ 'content': '$a 12883376',
+ 'indicators': ['\\', '\\'],
+ 'isProtected': false,
+ _isDeleted: true,
+ }, {
+ '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'],
+ },
+ ],
+ }),
+ [QUICK_MARC_ACTIONS.DERIVE]: () => ({
+ [MARC_TYPES.BIB]: [
+ {
+ 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',
+ },
+ ],
+ }),
+};
+
+const getIdsOfFields = (action, marcType) => {
+ return recordsMap[action]()[marcType]
+ .slice(1)
+ .filter(field => !field._isDeleted)
+ .map(field => field.id);
+};
+
+const getInitialValues = (action, marcType) => {
+ const initialValuesMap = {
+ [QUICK_MARC_ACTIONS.CREATE]: () => ({}),
+ [QUICK_MARC_ACTIONS.EDIT]: () => ({
+ leader: leadersMap[marcType],
+ records: recordsMap[action]()[marcType],
+ relatedRecordVersion: 1,
+ }),
+ [QUICK_MARC_ACTIONS.DERIVE]: () => ({
+ 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',
+ },
+ ],
+ }),
+ };
+
+ return initialValuesMap[action]();
+};
+
+const getFormValues = (action, marcType) => {
+ const formValuesMap = {
+ [QUICK_MARC_ACTIONS.CREATE]: () => ({
+ externalHrid: 'in00000000022',
+ externalId: '00000000-0000-0000-0000-000000000000',
+ leader: leadersMap[marcType],
+ marcFormat: marcType.toUpperCase(),
+ parsedRecordDtoId: '00000000-0000-0000-0000-000000000000',
+ records: recordsMap[action]()[marcType],
+ relatedRecordVersion: 1,
+ suppressDiscovery: false,
+ updateInfo: { recordState: 'NEW' },
+ }),
+ [QUICK_MARC_ACTIONS.EDIT]: () => ({
+ externalId: '17064f9d-0362-468d-8317-5984b7efd1b5',
+ marcFormat: marcType.toUpperCase(),
+ leader: leadersMap[marcType],
+ parsedRecordDtoId: '1bf159d9-4da8-4c3f-9aac-c83e68356bbf',
+ parsedRecordId: '1bf159d9-4da8-4c3f-9aac-c83e68356bbf',
+ records: recordsMap[action]()[marcType],
+ suppressDiscovery: false,
+ updateInfo: { recordState: 'NEW' },
+ }),
+ [QUICK_MARC_ACTIONS.DERIVE]: () => ({
+ 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: recordsMap[action]()[marcType],
+ suppressDiscovery: false,
+ updateInfo: { recordState: 'NEW' },
+ }),
+ };
+
+ return formValuesMap[action]();
+};
+
+const linkingRules = [{
+ id: 1,
+ bibField: '100',
+ authorityField: '100',
+ authoritySubfields: ['a', 'b', 't', 'd'],
+ subfieldModifications: [],
+ validation: {},
+ autoLinkingEnabled: true,
+}];
+
+describe('useSaveRecord', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ useAuthorityLinking.mockReturnValue({
+ linkableBibFields: [],
+ actualizeLinks: mockActualizeLinks,
+ autoLinkingEnabled: true,
+ autoLinkableBibFields: [],
+ autoLinkAuthority: jest.fn(),
+ linkingRules,
+ sourceFiles: [],
+ });
+
+ useMarcRecordMutation.mockReturnValue({
+ updateMarcRecord: mockUpdateMarcRecord,
+ isLoading: false,
+ });
+
+ useValidate.mockReturnValue({
+ validate: mockValidateFetch,
+ });
+
+ applyCentralTenantInHeaders.mockReturnValue(false);
+ useParams.mockReturnValue({});
+ });
+
+ describe('when creating', () => {
+ describe('when there is a validation error', () => {
+ it('should stop submitting', async () => {
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(MARC_TYPES.BIB),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.CREATE,
+ validationErrorsRef: {
+ current: {
+ 'id-with-error': [{ id: 'some-error' }],
+ },
+ },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(null, _api, mockComplete);
+
+ expect(mockComplete).toHaveBeenCalled();
+ expect(getMutator().quickMarcEditMarcRecord.POST).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when marc type is not a bib', () => {
+ it('should not call actualizeLinks', async () => {
+ const marcType = MARC_TYPES.HOLDINGS;
+ const action = QUICK_MARC_ACTIONS.CREATE;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ expect(mockActualizeLinks).not.toHaveBeenCalled();
+ });
+ });
+
+ it('should actualize links', async () => {
+ const marcType = MARC_TYPES.BIB;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.CREATE,
+ marcType,
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.CREATE, marcType));
+
+ 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 there is a linked field', () => {
+ it('should call the saveLinksToNewRecord', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const mutator = getMutator();
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator,
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.CREATE,
+ marcType,
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.CREATE, marcType));
+
+ expect(saveLinksToNewRecord).toHaveBeenCalledWith(
+ mutator,
+ 'externalId-1',
+ expect.objectContaining({ _actionType: 'create' }),
+ );
+ });
+ });
+
+ it('should create bib record with correct payload', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const mutator = getMutator();
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator,
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.CREATE,
+ marcType,
+ },
+ }),
+ });
+
+ 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 result.current.onSubmit(formValues);
+
+ expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalledWith(formValuesForCreate);
+ });
+
+ it('should create authority record with correct payload and call onSave', async () => {
+ 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 marcType = MARC_TYPES.AUTHORITY;
+ const mutator = getMutator();
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator,
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.CREATE,
+ marcType,
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.CREATE, marcType));
+
+ expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalledWith(payload);
+ expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.save.success.processing' });
+ expect(mockShowCallout).toHaveBeenCalledWith({ messageId: 'ui-quick-marc.record.saveNew.success' });
+ expect(mockOnSave).toHaveBeenCalled();
+ });
+
+ describe('when hitting save&continue', () => {
+ it('should call refreshPageData', async () => {
+ const action = QUICK_MARC_ACTIONS.CREATE;
+ const marcType = MARC_TYPES.BIB;
+ const history = createMemoryHistory({
+ initialEntries: [`${basePath}?sort=title`],
+ });
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ continueAfterSave: { current: true },
+ },
+ history,
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ const fieldIds = getIdsOfFields(action, marcType);
+
+ expect(mockRefreshPageData).toHaveBeenCalledWith(fieldIds, QUICK_MARC_ACTIONS.EDIT, 'externalId-1');
+ });
+
+ it('should redirect to the edit page', async () => {
+ const action = QUICK_MARC_ACTIONS.CREATE;
+ const marcType = MARC_TYPES.BIB;
+ const history = createMemoryHistory({
+ initialEntries: [`${basePath}?sort=title`],
+ });
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ continueAfterSave: { current: true },
+ },
+ history,
+ }),
+ });
+
+ await act(async () => result.current.onSubmit(getFormValues(action, marcType)));
+
+ expect(history.location.pathname).toBe(`${basePath}/edit-bibliographic/externalId-1`);
+ expect(history.location.search).toBe('?sort=title');
+ });
+ });
+
+ describe('when hitting save&close', () => {
+ it('should call onSave with `externalId`', async () => {
+ const marcType = MARC_TYPES.BIB;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.CREATE,
+ marcType,
+ continueAfterSave: { current: false },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.CREATE, marcType));
+
+ expect(mockOnSave).toHaveBeenCalledWith('externalId-1');
+ });
+ });
+ });
+
+ describe('when editing', () => {
+ describe('when there is a validation error', () => {
+ it('should stop submitting', async () => {
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(MARC_TYPES.BIB),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.EDIT,
+ validationErrorsRef: {
+ current: {
+ 'id-with-error': [{ id: 'some-error' }],
+ },
+ },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(null, _api, mockComplete);
+
+ expect(mockComplete).toHaveBeenCalled();
+ });
+ });
+
+ describe('when marc type is not a bib', () => {
+ it('should not call actualizeLinks', async () => {
+ const marcType = MARC_TYPES.AUTHORITY;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ expect(mockActualizeLinks).not.toHaveBeenCalled();
+ });
+ });
+
+ it('should actualize links', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ },
+ }),
+ });
+
+ result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.EDIT, marcType));
+
+ 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));
+ });
+
+ it('should edit record with correct payload', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ },
+ }),
+ });
+
+ 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 result.current.onSubmit(getFormValues(action, marcType));
+
+ 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 marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ },
+ }),
+ });
+
+ 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': '',
+ },
+ };
+
+ const formValues = getFormValues(action, marcType);
+
+ const newFormValues = {
+ ...formValues,
+ records: [
+ field240,
+ ...formValues.records,
+ ],
+ };
+
+ await result.current.runValidation(newFormValues);
+
+ 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 a record returned with different version', () => {
+ it('should return the optimistic locking error', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator({
+ ...getInstance(),
+ _version: '2',
+ }),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: {
+ ...getInstance(),
+ _version: '1',
+ },
+ },
+ }),
+ });
+
+ await act(async () => result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.EDIT, marcType)));
+
+ expect(result.current.httpError).toEqual({
+ errorType: ERROR_TYPES.OPTIMISTIC_LOCKING,
+ message: 'ui-quick-marc.record.save.error.derive',
+ });
+ expect(mockUpdateMarcRecord).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when there is an error during PUT request due to optimistic locking', () => {
+ it('should return the error', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ },
+ }),
+ });
+
+ mockUpdateMarcRecord.mockRejectedValueOnce({
+ json: jest.fn().mockResolvedValue({ message: 'optimistic locking' }),
+ });
+
+ await act(async () => result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.EDIT, marcType)));
+
+ expect(mockUpdateMarcRecord).toHaveBeenCalled();
+
+ expect(result.current.httpError).toEqual({
+ errorType: ERROR_TYPES.OPTIMISTIC_LOCKING,
+ message: 'optimistic locking',
+ });
+ });
+ });
+
+ describe('when record not found (already deleted)', () => {
+ it('should return the error', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: {
+ ...getMutator(getInstance()),
+ quickMarcEditInstance: { GET: jest.fn().mockRejectedValue({ httpStatus: 404 }) },
+ },
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ },
+ }),
+ });
+
+ await act(async () => result.current.onSubmit(getFormValues(QUICK_MARC_ACTIONS.EDIT, marcType)));
+
+ expect(result.current.httpError).toEqual({
+ errorType: 'other',
+ httpStatus: 404,
+ });
+ });
+ });
+
+ describe('when a member tenant edits a shared record', () => {
+ it('should apply the central tenant id for all authority linking ', async () => {
+ applyCentralTenantInHeaders.mockReturnValue(true);
+
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ },
+ }),
+ });
+
+ expect(useAuthorityLinking).toHaveBeenCalledWith({
+ marcType: MARC_TYPES.BIB,
+ action: QUICK_MARC_ACTIONS.EDIT,
+ });
+ expect(useMarcRecordMutation).toHaveBeenCalledWith({ tenantId: 'consortia' });
+ });
+ });
+
+ describe('when hitting save&continue', () => {
+ it('should call refreshPageData', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ continueAfterSave: { current: true },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ const fieldIds = getIdsOfFields(action, marcType);
+
+ expect(mockRefreshPageData).toHaveBeenCalledWith(fieldIds);
+ });
+ });
+
+ describe('when hitting save&close', () => {
+ it('should call onSave with `externalId`', async () => {
+ const marcType = MARC_TYPES.BIB;
+ const action = QUICK_MARC_ACTIONS.EDIT;
+
+ useParams.mockReturnValue({
+ externalId: 'externalId-url',
+ });
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator: getMutator(getInstance()),
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ initialValues: getInitialValues(action, marcType),
+ instance: getInstance(),
+ continueAfterSave: { current: false },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ expect(mockRefreshPageData).not.toHaveBeenCalled();
+ expect(mockOnSave).toHaveBeenCalledWith('externalId-url');
+ });
+ });
+ });
+
+ describe('when deriving', () => {
+ describe('when there is a validation error', () => {
+ it('should stop submitting', async () => {
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(MARC_TYPES.BIB),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action: QUICK_MARC_ACTIONS.DERIVE,
+ validationErrorsRef: {
+ current: {
+ 'id-with-error': [{ id: 'some-error' }],
+ },
+ },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(null, _api, mockComplete);
+
+ expect(mockComplete).toHaveBeenCalled();
+ });
+ });
+
+ it('should actualize links', async () => {
+ const action = QUICK_MARC_ACTIONS.DERIVE;
+ const marcType = MARC_TYPES.BIB;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ 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));
+ });
+
+ it('should derive record with correct payload', async () => {
+ const action = QUICK_MARC_ACTIONS.DERIVE;
+ const marcType = MARC_TYPES.BIB;
+ const mutator = getMutator();
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: {
+ ...getInitialProps(marcType),
+ mutator,
+ },
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ },
+ }),
+ });
+
+ 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 result.current.onSubmit(formValues);
+
+ expect(mutator.quickMarcEditMarcRecord.POST).toHaveBeenCalledWith(formValuesForDerive);
+ });
+
+ describe('when hitting save&close', () => {
+ it('should call onClose with `id` string and externalId', async () => {
+ const action = QUICK_MARC_ACTIONS.DERIVE;
+ const marcType = MARC_TYPES.BIB;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ continueAfterSave: { current: false },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ expect(mockOnClose).toHaveBeenCalledWith('id');
+ expect(mockOnClose).toHaveBeenCalledWith('externalId-1');
+ });
+ });
+
+ describe('when hitting save&continue', () => {
+ it('should call refreshPageData', async () => {
+ const action = QUICK_MARC_ACTIONS.DERIVE;
+ const marcType = MARC_TYPES.BIB;
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ continueAfterSave: { current: true },
+ },
+ }),
+ });
+
+ await result.current.onSubmit(getFormValues(action, marcType));
+
+ const fieldIds = getIdsOfFields(action, marcType);
+
+ expect(mockRefreshPageData).toHaveBeenCalledWith(fieldIds, QUICK_MARC_ACTIONS.EDIT, 'externalId-1');
+ });
+
+ it('should redirect to the edit page', async () => {
+ const action = QUICK_MARC_ACTIONS.DERIVE;
+ const marcType = MARC_TYPES.BIB;
+ const history = createMemoryHistory({
+ initialEntries: [`${basePath}?sort=title`],
+ });
+
+ const { result } = renderHook(useSaveRecord, {
+ initialProps: getInitialProps(marcType),
+ wrapper: getWrapper({
+ quickMarcContext: {
+ action,
+ marcType,
+ continueAfterSave: { current: true },
+ },
+ history,
+ }),
+ });
+
+ await act(async () => result.current.onSubmit(getFormValues(action, marcType)));
+
+ expect(history.location.pathname).toBe(`${basePath}/edit-bibliographic/externalId-1`);
+ expect(history.location.search).toBe('?sort=title');
+ });
+ });
+ });
+});
diff --git a/src/QuickMarcEditor/useSaveRecord/useSubmitRecord/index.js b/src/QuickMarcEditor/useSaveRecord/useSubmitRecord/index.js
new file mode 100644
index 00000000..8bba6214
--- /dev/null
+++ b/src/QuickMarcEditor/useSaveRecord/useSubmitRecord/index.js
@@ -0,0 +1 @@
+export * from './useSumbitRecord';
diff --git a/src/QuickMarcEditor/useSaveRecord/useSubmitRecord/useSumbitRecord.js b/src/QuickMarcEditor/useSaveRecord/useSubmitRecord/useSumbitRecord.js
new file mode 100644
index 00000000..cba62fe1
--- /dev/null
+++ b/src/QuickMarcEditor/useSaveRecord/useSubmitRecord/useSumbitRecord.js
@@ -0,0 +1,396 @@
+import {
+ useCallback,
+ useContext,
+ useState,
+} from 'react';
+import {
+ useHistory,
+ useLocation,
+ useParams,
+} from 'react-router-dom';
+import isEmpty from 'lodash/isEmpty';
+import noop from 'lodash/noop';
+import isNil from 'lodash/isNil';
+
+import { useStripes } from '@folio/stripes/core';
+import { useShowCallout } from '@folio/stripes-acq-components';
+import { getHeaders } from '@folio/stripes-marc-components';
+
+import { ERROR_TYPES, EXTERNAL_INSTANCE_APIS, MARC_TYPES } from '../../../common';
+import {
+ are010Or1xxUpdated,
+ getFieldIds,
+ hydrateMarcRecord,
+ parseHttpError,
+ recordHasLinks,
+ saveLinksToNewRecord,
+} from '../../utils';
+import getQuickMarcRecordStatus from '../../getQuickMarcRecordStatus';
+import { QuickMarcContext } from '../../../contexts';
+import { useAuthorityLinking } from '../../../hooks';
+import { useMarcRecordMutation } from '../../../queries';
+import { QUICK_MARC_ACTIONS } from '../../constants';
+
+const useSubmitRecord = ({
+ prepareForSubmit,
+ mutator,
+ linksCount,
+ isRequestToCentralTenantFromMember,
+ tenantId,
+ refreshPageData,
+ onClose,
+ onSave,
+}) => {
+ const {
+ externalId: _externalId,
+ instanceId: _instanceId,
+ } = useParams();
+ const history = useHistory();
+ const location = useLocation();
+ const showCallout = useShowCallout();
+ const stripes = useStripes();
+
+ const {
+ action,
+ marcType,
+ basePath,
+ initialValues,
+ instance,
+ validationErrorsRef,
+ continueAfterSave,
+ relatedRecordVersion,
+ } = useContext(QuickMarcContext);
+
+ const { actualizeLinks } = useAuthorityLinking({ marcType, action });
+ const { updateMarcRecord } = useMarcRecordMutation({ tenantId });
+
+ const [httpError, setHttpError] = useState(null);
+
+ const { token, locale } = stripes.okapi;
+ const centralTenantId = stripes.user.user.consortium?.centralTenantId;
+
+ const redirectToRecord = useCallback(async (externalId, instanceId) => {
+ if (marcType === MARC_TYPES.HOLDINGS) {
+ await onSave(`${instanceId}/${externalId}`);
+ } else {
+ await onSave(externalId);
+ }
+ }, [onSave, marcType]);
+
+ const processEditingAfterCreation = useCallback(async (formValues, externalId) => {
+ const fieldIds = getFieldIds(formValues);
+ const searchParams = new URLSearchParams(location.search);
+
+ const routes = {
+ [MARC_TYPES.BIB]: `${basePath}/edit-bibliographic/${externalId}`,
+ [MARC_TYPES.AUTHORITY]: `${basePath}/edit-authority/${externalId}`,
+ [MARC_TYPES.HOLDINGS]: `${basePath}/edit-holdings/${externalId}`,
+ };
+
+ await refreshPageData(fieldIds, QUICK_MARC_ACTIONS.EDIT, externalId);
+
+ history.push({
+ pathname: routes[marcType],
+ search: searchParams.toString(),
+ });
+ }, [basePath, marcType, location, history, refreshPageData]);
+
+ const onCreate = 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);
+ }
+
+ if (continueAfterSave.current) {
+ await processEditingAfterCreation(formValues, externalId);
+
+ return;
+ }
+
+ await redirectToRecord(externalId, instanceId);
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ showCallout({
+ messageId: 'ui-quick-marc.record.saveNew.error',
+ type: 'error',
+ });
+ }
+ })
+ .catch(async (errorResponse) => {
+ const parsedError = await parseHttpError(errorResponse);
+
+ setHttpError(parsedError);
+ });
+ }, [
+ showCallout,
+ prepareForSubmit,
+ actualizeLinks,
+ validationErrorsRef,
+ marcType,
+ continueAfterSave,
+ mutator,
+ processEditingAfterCreation,
+ redirectToRecord,
+ ]);
+
+ const onEdit = 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',
+ });
+ }
+
+ if (continueAfterSave.current) {
+ const fieldIds = getFieldIds(formValuesToHydrate);
+
+ await refreshPageData(fieldIds);
+
+ return;
+ }
+
+ await redirectToRecord(_externalId, _instanceId);
+ })
+ .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,
+ _externalId,
+ _instanceId,
+ continueAfterSave,
+ redirectToRecord,
+ ]);
+
+ const onDerive = 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 }) => {
+ if (!continueAfterSave.current) {
+ 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 (continueAfterSave.current) {
+ if (recordHasLinks(formValuesForDerive.fields)) {
+ await saveLinksToNewRecord(mutator, externalId, formValuesForDerive)
+ .catch(noop);
+ }
+
+ await processEditingAfterCreation(formValues, externalId);
+
+ return;
+ }
+
+ if (recordHasLinks(formValuesForDerive.fields)) {
+ saveLinksToNewRecord(mutator, externalId, formValuesForDerive)
+ .finally(() => onClose(externalId));
+ } else {
+ onClose(externalId);
+ }
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ showCallout({
+ messageId: 'ui-quick-marc.record.saveNew.error',
+ type: 'error',
+ });
+ }
+ })
+ .catch(async (errorResponse) => {
+ const parsedError = await parseHttpError(errorResponse);
+
+ setHttpError(parsedError);
+ });
+ }, [
+ onClose,
+ showCallout,
+ prepareForSubmit,
+ actualizeLinks,
+ validationErrorsRef,
+ continueAfterSave,
+ mutator,
+ processEditingAfterCreation,
+ ]);
+
+ return {
+ onCreate,
+ onEdit,
+ onDerive,
+ httpError,
+ };
+};
+
+export { useSubmitRecord };
diff --git a/src/QuickMarcEditor/utils.js b/src/QuickMarcEditor/utils.js
index 91d70fb6..ac4bd044 100644
--- a/src/QuickMarcEditor/utils.js
+++ b/src/QuickMarcEditor/utils.js
@@ -1259,3 +1259,10 @@ export const getFixedFieldStringPositions = (type, subtype, field, fixedFieldSpe
return [];
};
+
+export const getFieldIds = (formValues) => {
+ return formValues.records
+ .slice(1)
+ .filter(field => !field._isDeleted)
+ .map(field => field.id);
+};
diff --git a/src/contexts/QuickMarcContext/QuickMarcContext.js b/src/contexts/QuickMarcContext/QuickMarcContext.js
index 57106655..f3e8f488 100644
--- a/src/contexts/QuickMarcContext/QuickMarcContext.js
+++ b/src/contexts/QuickMarcContext/QuickMarcContext.js
@@ -10,20 +10,27 @@ import PropTypes from 'prop-types';
const QuickMarcContext = createContext();
const propTypes = {
+ action: PropTypes.string.isRequired,
+ marcType: PropTypes.string.isRequired,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
- relatedRecordVersion: PropTypes.number,
+ basePath: PropTypes.string.isRequired,
};
const QuickMarcProvider = ({
children,
- relatedRecordVersion,
+ action,
+ marcType,
+ basePath,
}) => {
+ const [instance, setInstance] = useState(null);
+ const [marcRecord, setMarcRecord] = useState(null);
const [selectedSourceFile, setSelectedSourceFile] = useState(null);
- const [_relatedRecordVersion, setRelatedRecordVersion] = useState(relatedRecordVersion);
+ const [_relatedRecordVersion, setRelatedRecordVersion] = useState();
const validationErrors = useRef({});
+ const continueAfterSave = useRef(false);
const setValidationErrors = useCallback((newValidationErrors) => {
validationErrors.current = newValidationErrors;
@@ -36,6 +43,14 @@ const QuickMarcProvider = ({
setValidationErrors,
relatedRecordVersion: _relatedRecordVersion,
setRelatedRecordVersion,
+ continueAfterSave,
+ action,
+ marcType,
+ setInstance,
+ instance,
+ initialValues: marcRecord,
+ setMarcRecord,
+ basePath,
}), [
selectedSourceFile,
setSelectedSourceFile,
@@ -43,6 +58,14 @@ const QuickMarcProvider = ({
setValidationErrors,
_relatedRecordVersion,
setRelatedRecordVersion,
+ continueAfterSave,
+ action,
+ marcType,
+ setInstance,
+ instance,
+ marcRecord,
+ setMarcRecord,
+ basePath,
]);
return (
diff --git a/test/jest/helpers/harness.js b/test/jest/helpers/harness.js
index abf84ce4..a7db5e35 100644
--- a/test/jest/helpers/harness.js
+++ b/test/jest/helpers/harness.js
@@ -23,6 +23,12 @@ const queryClient = new QueryClient();
const defaultQuickMarcContextValue = {
validationErrorsRef: { current: {} },
setValidationErrors: jest.fn(),
+ setInstance: jest.fn(),
+ setMarcRecord: jest.fn(),
+ setRelatedRecordVersion: jest.fn(),
+ setSelectedSourceFile: jest.fn(),
+ basePath: '/base-path',
+ continueAfterSave: { current: false },
};
const QuickMarcProviderMock = ({ ctxValue, children }) => (
@@ -37,6 +43,9 @@ const Harness = ({
children,
history = defaultHistory,
quickMarcContext,
+ action,
+ marcType,
+ basePath,
}) => {
const QuickMarcProviderComponent = quickMarcContext
? QuickMarcProviderMock
@@ -47,7 +56,12 @@ const Harness = ({
-
+
{children}