Skip to content

Commit

Permalink
ERM-3297 Block save on invalid date in agreement edit (#1343)
Browse files Browse the repository at this point in the history
* ERM-3297 Block save on invalid date in agreement edit

* convert AgreementPeriodField to functional component
* install and import dayjs
* refactor date fields
* add date plausibility validator
* add translation key

* ERM-3297 Block save on invalid date in agreement edit

* fix lint errors

* * add missing required prop

* Block save on invalid date in agreement edit

* refactor Datepicker fields according to folio-org/stripes-components#2331

* ERM-3297 Block save on invalid date in agreement edit

* move date plausibility validator to stripes-erm-components

* test: Starting on test fixes

* test: More test fixes

* test: Test fixes

* test: Final test fixes

* test: Final test tweaks and fixes

* test: Test passes locally...

* test: This is going to be tedious... wait for button click in beforeEach

* test: Restructured Agreement test actions section

Really we should be testing actions as a block, where the actions button is clicked in a beforeEach. See restructuring for the breakdown

* test: Restructure identifier reassignment form test

Chaining button clicks in a waitfor does not seem to guarantee order of running on github CI, so restructure to be clearer

* test: Fix UrlCustomizer flaky test (Two buttons labelled 'Delete' Caused some issues

* test: Swap to using Button ids, make sure actions are correct (still failing, I think due to changes in Modal)

* test: Small tweak to test comment

---------

Co-authored-by: EthanFreestone <[email protected]>
Co-authored-by: Ethan Freestone <[email protected]>
  • Loading branch information
3 people authored Sep 23, 2024
1 parent e71d566 commit db29a2f
Show file tree
Hide file tree
Showing 49 changed files with 762 additions and 354 deletions.
11 changes: 8 additions & 3 deletions src/components/AddToBasketButton/AddToBasketButton.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button, renderWithIntl } from '@folio/stripes-erm-testing';

import { MemoryRouter } from 'react-router';
import { waitFor } from '@folio/jest-config-stripes/testing-library/react';
import { MemoryRouter } from 'react-router';
import AddToBasketButton from './AddToBasketButton';

import translationsProperties from '../../../test/helpers/translationsProperties';
Expand Down Expand Up @@ -84,7 +84,9 @@ describe('AddToBasketButton', () => {
});

it('invokes the callback with expected value', async () => {
expect(mockAddToBasket.mock.calls.length).toBe(1);
await waitFor(async () => {
expect(mockAddToBasket.mock.calls.length).toBe(1);
});
});

test('renders remove button label', async () => {
Expand All @@ -95,7 +97,10 @@ describe('AddToBasketButton', () => {
await waitFor(async () => {
await Button('Remove button').click();
});
expect(mockRemoveFromBasket.mock.calls.length).toBe(1);

await waitFor(async () => {
expect(mockRemoveFromBasket.mock.calls.length).toBe(1);
});
});
});
});
183 changes: 90 additions & 93 deletions src/components/AgreementPeriodsFieldArray/AgreementPeriodField.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React from 'react';
import React, { useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import { get, isEmpty } from 'lodash';
import { FormattedMessage } from 'react-intl';
import { get } from 'lodash';
import { FormattedMessage, useIntl } from 'react-intl';
import { Field } from 'react-final-form';

import {
AppValidatedDatepicker,
Col,
Datepicker,
Row,
TextArea,
getLocaleDateFormat
} from '@folio/stripes/components';

import { composeValidators } from '@folio/stripes-erm-components';
import { composeValidators, datePlausibilityCheck } from '@folio/stripes-erm-components';
import { validators } from '../utilities';

const multipleOpenEndedPeriods = (...rest) => (
Expand All @@ -22,95 +23,91 @@ const overlappingPeriods = (...rest) => (
validators.overlappingDates(...rest, 'ui-agreements.errors.overlappingPeriod')
);

export default class AgreementPeriodField extends React.Component {
static propTypes = {
index: PropTypes.number.isRequired,
input: PropTypes.shape({
name: PropTypes.string.isRequired,
}).isRequired,
}
const AgreementPeriodField = ({ index, input: { name } }) => {
const startDateInputRef = useRef(null);
const intl = useIntl();
const dateFormat = getLocaleDateFormat({ intl });
const backendDateStandard = 'YYYY-MM-DD';

constructor(props) {
super(props);
this.inputRef = React.createRef();
}

componentDidMount() {
const value = get(this.props, 'input.value');

/* Focus only when add agreement period button is clicked in which case the value object
would look like value:{ _delete: false }. Prevent focus on initial mount (value === {}) or
when value.id is defined */

if (!isEmpty(value) && !value.id && get(this.inputRef, 'current')) {
this.inputRef.current.focus();
useEffect(() => {
const value = get(startDateInputRef, 'current.value');
if ((value === '' || value === undefined) && get(startDateInputRef, 'current')) {
startDateInputRef.current.focus();
}
}
}, [startDateInputRef]);

return (
<div
data-testid="agreementPeriodField"
>
<Row>
<Col xs={4}>
<Field
backendDateStandard={backendDateStandard}
component={AppValidatedDatepicker}
id={`period-start-date-${index}`}
inputRef={startDateInputRef}
label={<FormattedMessage id="ui-agreements.agreements.startDate" />}
name={`${name}.startDate`}
required
timeZone="UTC"
usePortal
validate={composeValidators(
(value) => datePlausibilityCheck(value, dateFormat, backendDateStandard),
validators.requiredStartDate,
validators.dateOrder,
overlappingPeriods,
)}
/>
</Col>
<Col xs={4}>
<Field
backendDateStandard={backendDateStandard}
component={AppValidatedDatepicker}
id={`period-end-date-${index}`}
label={<FormattedMessage id="ui-agreements.agreements.endDate" />}
name={`${name}.endDate`}
parse={v => v}
timeZone="UTC"
usePortal
validate={composeValidators(
(value) => datePlausibilityCheck(value, dateFormat, backendDateStandard),
validators.dateOrder,
multipleOpenEndedPeriods,
overlappingPeriods,
)}
/>
</Col>
<Col xs={4}>
<Field
backendDateStandard={backendDateStandard}
component={AppValidatedDatepicker}
id={`period-cancellation-deadline-${index}`}
label={<FormattedMessage id="ui-agreements.agreements.cancellationDeadline" />}
name={`${name}.cancellationDeadline`}
parse={v => v} // Lets us send an empty string instead of `undefined`
timeZone="UTC"
usePortal
validate={(value) => datePlausibilityCheck(value, dateFormat, backendDateStandard)}
/>
</Col>
</Row>
<Field
component={TextArea}
id={`period-note-${index}`}
label={<FormattedMessage id="ui-agreements.agreementPeriods.periodNote" />}
name={`${name}.note`}
parse={v => v} // Lets us send an empty string instead of `undefined`
/>
</div>
);
};

render = () => {
const { index, input: { name } } = this.props;
AgreementPeriodField.propTypes = {
index: PropTypes.number.isRequired,
input: PropTypes.shape({
name: PropTypes.string.isRequired,
}).isRequired,
};

return (
<div
data-testid="agreementPeriodField"
>
<Row>
<Col xs={4}>
<Field
backendDateStandard="YYYY-MM-DD"
component={Datepicker}
id={`period-start-date-${index}`}
inputRef={this.inputRef}
label={<FormattedMessage id="ui-agreements.agreements.startDate" />}
name={`${name}.startDate`}
required
timeZone="UTC"
usePortal
validate={composeValidators(
validators.requiredStartDate,
validators.dateOrder,
overlappingPeriods,
)}
/>
</Col>
<Col xs={4}>
<Field
backendDateStandard="YYYY-MM-DD"
component={Datepicker}
id={`period-end-date-${index}`}
label={<FormattedMessage id="ui-agreements.agreements.endDate" />}
name={`${name}.endDate`}
parse={v => v} // Lets us send an empty string instead of `undefined`
timeZone="UTC"
usePortal
validate={composeValidators(
validators.dateOrder,
multipleOpenEndedPeriods,
overlappingPeriods,
)}
/>
</Col>
<Col xs={4}>
<Field
backendDateStandard="YYYY-MM-DD"
component={Datepicker}
id={`period-cancellation-deadline-${index}`}
label={<FormattedMessage id="ui-agreements.agreements.cancellationDeadline" />}
name={`${name}.cancellationDeadline`}
parse={v => v} // Lets us send an empty string instead of `undefined`
timeZone="UTC"
usePortal
/>
</Col>
</Row>
<Field
component={TextArea}
id={`period-note-${index}`}
label={<FormattedMessage id="ui-agreements.agreementPeriods.periodNote" />}
name={`${name}.note`}
parse={v => v} // Lets us send an empty string instead of `undefined`
/>
</div>
);
}
}
export default AgreementPeriodField;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { waitFor, within } from '@folio/jest-config-stripes/testing-library/react';
import userEvent from '@folio/jest-config-stripes/testing-library/user-event';

import { Button, TextArea, TextField, renderWithIntl, Datepicker, TestForm } from '@folio/stripes-erm-testing';
import { Button, Datepicker, TestForm, TextArea, TextField, renderWithIntl } from '@folio/stripes-erm-testing';


import { FieldArray } from 'react-final-form-arrays';
Expand Down Expand Up @@ -102,7 +102,9 @@ describe('AgreementPeriodsFieldArray', () => {
await waitFor(async () => {
await Button('Add agreement period').click();
});
expect(queryAllByTestId(/agreementPeriodsFieldArray\[.*\]/i).length).toEqual(2);
await waitFor(async () => {
expect(queryAllByTestId(/agreementPeriodsFieldArray\[.*\]/i).length).toEqual(2);
});
});

test('multiple agreement periods render as expected', async () => {
Expand Down Expand Up @@ -164,7 +166,7 @@ describe('AgreementPeriodsFieldArray', () => {
});

test('expected values are submitted', async () => {
const { getByTestId } = renderWithIntl(
/* const { getByTestId } = */renderWithIntl(
<TestForm
initialValues={{ agreementPeriodsFieldArrayTest:multiplePeriods }}
onSubmit={onSubmit}
Expand All @@ -179,10 +181,12 @@ describe('AgreementPeriodsFieldArray', () => {

await waitFor(async () => {
await Button('Submit').click();
// userEvent.click(getByTestId('submit'));
});

userEvent.click(getByTestId('submit'));
expect(onSubmit.mock.calls.length).toBe(1);
await waitFor(async () => {
expect(onSubmit.mock.calls.length).toBe(1);
});
const submittedValues = onSubmit.mock.calls[0][0];
const expectedPayload = {
agreementPeriodsFieldArrayTest: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { FieldArray } from 'react-final-form-arrays';

import { waitFor } from '@folio/jest-config-stripes/testing-library/react';
import { Button, renderWithIntl, TestForm, IconButton } from '@folio/stripes-erm-testing';
import { Button, IconButton, renderWithIntl, TestForm } from '@folio/stripes-erm-testing';

import translationsProperties from '../../../../test/helpers';
import ContentTypesFieldArray from './ContentTypesFieldArray';
Expand Down Expand Up @@ -42,11 +42,16 @@ describe('ContentTypesFieldArray', () => {
await addButton.click();
});

expect(queryAllByTestId(/contentTypesFieldArray\[.*\]/i).length).toEqual(1);
await waitFor(async () => {
expect(queryAllByTestId(/contentTypesFieldArray\[.*\]/i).length).toEqual(1);
});

await waitFor(async () => {
await IconButton('remove-content-types[0]-undefined').click();
});

expect(queryAllByTestId(/contentTypesFieldArray\[.*\]/i).length).toEqual(0);
await waitFor(async () => {
expect(queryAllByTestId(/contentTypesFieldArray\[.*\]/i).length).toEqual(0);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

import { renderWithIntl, Button, Dropdown, MultiColumnList } from '@folio/stripes-erm-testing';
import { MemoryRouter } from 'react-router-dom';
import { waitFor } from '@folio/jest-config-stripes/testing-library/react';
import { Button, Dropdown, MultiColumnList, renderWithIntl } from '@folio/stripes-erm-testing';
import { MemoryRouter } from 'react-router-dom';
import translationsProperties from '../../../../test/helpers';
import CoveredEResourcesList from './CoveredEResourcesList';
import agreement from './testResources';
Expand Down Expand Up @@ -44,17 +44,26 @@ describe('CoveredEResourcesList', () => {
await waitFor(async () => {
await Button('Future').click();
});
expect(handlers.onFilterEResources.mock.calls.length).toBe(1);

await waitFor(async () => {
expect(handlers.onFilterEResources.mock.calls.length).toBe(1);
});

await waitFor(async () => {
await Button('Dropped').click();
});
expect(handlers.onFilterEResources.mock.calls.length).toBe(2);

await waitFor(async () => {
expect(handlers.onFilterEResources.mock.calls.length).toBe(2);
});

await waitFor(async () => {
await Button('All').click();
});
expect(handlers.onFilterEResources.mock.calls.length).toBe(3);

await waitFor(async () => {
expect(handlers.onFilterEResources.mock.calls.length).toBe(3);
});
});

test('renders the Export dropdown', async () => {
Expand All @@ -65,12 +74,18 @@ describe('CoveredEResourcesList', () => {
await waitFor(async () => {
await Dropdown('Export as...').choose('JSON');
});
expect(handlers.onExportEResourcesAsJSON.mock.calls.length).toBe(1);

await waitFor(async () => {
expect(handlers.onExportEResourcesAsJSON.mock.calls.length).toBe(1);
});

await waitFor(async () => {
await Dropdown('Export as...').choose('KBART');
});
expect(handlers.onExportEResourcesAsKBART.mock.calls.length).toBe(1);

await waitFor(async () => {
expect(handlers.onExportEResourcesAsKBART.mock.calls.length).toBe(1);
});
});

test('renders the CoveredEResourcesList MCL', async () => {
Expand Down
8 changes: 4 additions & 4 deletions src/components/AgreementSections/Lines/Lines.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';

import { Accordion, Badge, Button, Dropdown, DropdownMenu, Icon, Spinner } from '@folio/stripes/components';
import { IfPermission } from '@folio/stripes/core';
import { Button, Accordion, Badge, Spinner, Icon, Dropdown, DropdownMenu } from '@folio/stripes/components';
import { ColumnManagerMenu, useColumnManager } from '@folio/stripes/smart-components';

import { LINE_LISTING_COLUMN_MAPPING } from '../../../constants';
import { urls } from '../../utilities';
import CoveredEResourcesList from '../CoveredEResourcesList';
import LinesList from '../LinesList';
import { urls } from '../../utilities';
import { LINE_LISTING_COLUMN_MAPPING } from '../../../constants';

const propTypes = {
agreement: PropTypes.shape({
Expand Down
Loading

0 comments on commit db29a2f

Please sign in to comment.