Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

UISACQCOMP-218: add "Amount must be a positive number" validation for "Set exchange rate" field. #815

Merged
merged 10 commits into from
Sep 30, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* Support using custom list of tenants when open the locations modal. Refs UISACQCOMP-210.
* ECS - Display all consortium tenants in the affiliation selection of the location lookup. Refs UISACQCOMP-202.
* ECS - Add `isLoading` prop for `ConsortiumFieldInventory` component. Refs UISACQCOMP-215.
* Add "Amount must be a positive number" validation for "Set exchange rate" field. Refs UISACQCOMP-218.

## [5.1.2](https://github.com/folio-org/stripes-acq-components/tree/v5.1.2) (2024-09-13)
[Full Changelog](https://github.com/folio-org/stripes-acq-components/compare/v5.1.1...v5.1.2)
Expand Down
5 changes: 2 additions & 3 deletions lib/AcqUnitFilter/AcqUnitFilter.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { fireEvent, render, cleanup } from '@testing-library/react';
import { noop } from 'lodash';
import { fireEvent, render, cleanup } from '@testing-library/react';

import AcqUnitFilter from './AcqUnitFilter';

Expand All @@ -25,7 +24,7 @@ describe('AcqUnitFilter component', () => {
it('should render all passed options', async () => {
const { findAllByText, getByText } = renderAcqUnitFilter(acqUnitsRecords);

await fireEvent.click(getByText('stripes-components.selection.controlLabel'));
fireEvent.click(getByText('stripes-components.selection.controlLabel'));

const renderedFilterOptions = await findAllByText(/Unit #[0-9]/);

Expand Down
3 changes: 2 additions & 1 deletion lib/AffiliationsSelection/AffiliationsSelection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ const renderAffiliationsSelection = (props = {}) => render(
);

describe('AffiliationsSelection', () => {
it('should render affiliation selection with provided options', () => {
it('should render affiliation selection with provided options', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not async

renderAffiliationsSelection();

expect(
within(document.getElementById('test-affiliations-select'))
.getByText(affiliations[2].tenantName),
).toBeInTheDocument();

affiliations.forEach(({ tenantName, isPrimary }) => {
expect(
within(document.getElementById('test-affiliations-select'))
Expand Down
5 changes: 2 additions & 3 deletions lib/CountryFilter/CountryFilter.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import { noop } from 'lodash';
import { fireEvent, render, cleanup } from '@testing-library/react';

import CountryFilter from './CountryFilter';

Expand All @@ -25,7 +24,7 @@ describe('CountryFilter component', () => {
expect(getByText('ui-organizations.filterConfig.country')).toBeDefined();
});

it('should invoke onChange callback when something is selected', async () => {
it('should invoke onChange callback when something is selected', () => {
const onChangeFilter = jest.fn();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

const { container, getByText } = renderFilter(false, onChangeFilter);
const button = container.querySelector('[id="org-filter-country-selection"]');
Expand Down
16 changes: 12 additions & 4 deletions lib/CurrencyExchangeRateFields/CurrencyExchangeRateFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from '../VisibilityControl';
import CurrentExchangeRate from './CurrentExchangeRate';
import { FILED_NAMES } from './constants';
import { validateRequired } from '../utils';
import { validateRequired, validateRequiredPositiveAmount } from '../utils';

const CurrencyExchangeRateFields = ({
currencyFieldName,
Expand Down Expand Up @@ -65,6 +65,14 @@ const CurrencyExchangeRateFields = ({
resetExchangeRate();
}, [change, resetExchangeRate, currencyFieldName]);

const validateExchangeRate = (value) => {
if (value === undefined || value === null) {
return validateRequired(value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where did you find such requirement? doing this you introduce inconsistency

}

return validateRequiredPositiveAmount(value);
};

const systemCurrency = stripes.currency;
const filledCurrency = get(values, currencyFieldName);
const isSetUseExangeRateDisabled = isUseExangeRateDisabled ||
Expand Down Expand Up @@ -118,9 +126,9 @@ const CurrencyExchangeRateFields = ({
<TooltippedControl
controlComponent={Checkbox}
checked={isExchangeRateEnabled}
data-testid="use-set-exhange-rate"
data-testid="use-set-exchange-rate"
readOnly={isSetUseExangeRateDisabled}
id="use-set-exhange-rate"
id="use-set-exchange-rate"
label={<FormattedMessage id="stripes-acq-components.useSetExchangeRate" />}
onChange={enableExchangeRate}
vertical
Expand All @@ -146,7 +154,7 @@ const CurrencyExchangeRateFields = ({
readOnly={!isExchangeRateEnabled}
tooltipText={tooltipTextExchangeRate}
required={isExchangeRateRequired}
validate={isExchangeRateRequired ? validateRequired : undefined}
validate={isExchangeRateRequired ? validateExchangeRate : undefined}
key={isExchangeRateRequired ? 1 : 0}
isNonInteractive={isSetExchangeRateNonIntaractive ?? isNonInteractive}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MemoryRouter } from 'react-router-dom';
import { Button } from '@folio/stripes/components';
import stripesFinalForm from '@folio/stripes/final-form';

import CurrentExchangeRate from './CurrentExchangeRate';
import CurrencyExchangeRateFields from './CurrencyExchangeRateFields';

jest.mock('./CurrentExchangeRate', () => {
Expand All @@ -34,14 +35,34 @@ const renderComponent = (props = {}) => (render(
));

describe('CurrencyExchangeRateFields', () => {
it('should display validation messages', async () => {
const onSubmit = jest.fn();

renderComponent({ onSubmit });

await user.click(screen.getByText('stripes-components.selection.controlLabel'));
await user.click(screen.getByText('BYN (BYN)'));
await user.click(screen.getByTestId('use-set-exchange-rate'));

await waitFor(() => expect(screen.getByTestId('use-set-exchange-rate')).toBeChecked());

CurrentExchangeRate.mock.calls[0][0].setExchangeRateRequired(true);
await user.click(screen.getByText('Save'));

await waitFor(() => expect(screen.getByText('stripes-acq-components.validation.required')).toBeInTheDocument());
await user.type(screen.getByTestId('exchange-rate'), '-1');

await waitFor(() => expect(screen.getByText('stripes-acq-components.validation.shouldBePositiveAmount')).toBeInTheDocument());
});

it('should pass user input to API', async () => {
const onSubmit = jest.fn();

renderComponent({ onSubmit });

user.click(screen.getByText('stripes-components.selection.controlLabel'));
user.click(screen.getByText('BYN (BYN)'));
user.click(screen.getByTestId('use-set-exhange-rate'));
user.click(screen.getByTestId('use-set-exchange-rate'));
user.type(screen.getByTestId('exchange-rate'), '2.66');
user.click(screen.getByText('Save'));

Expand Down
3 changes: 2 additions & 1 deletion lib/DynamicSelection/DynamicSelection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('DynamicSelection', () => {
await user.type(input, '1');
jest.advanceTimersByTime(1500);
});
await user.click(screen.getByText('stripes-components.selection.controlLabel'));

expect(kyMock.get).toHaveBeenCalledWith(ORDERS_API, expect.objectContaining({}));
});
Expand All @@ -63,7 +64,7 @@ describe('DynamicSelection', () => {
await user.type(input, '1');
jest.advanceTimersByTime(1500);
});

user.click(screen.getByText('stripes-components.selection.controlLabel'));
user.click(screen.getByText(/11111/));

Expand Down
7 changes: 6 additions & 1 deletion lib/FieldHolding/FieldHolding.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { render, screen, within, fireEvent } from '@testing-library/react';
import keyBy from 'lodash/keyBy';
import {
fireEvent,
render,
screen,
within,
} from '@testing-library/react';
import { Form } from 'react-final-form';

import { useInstanceHoldings } from '../hooks';
Expand Down
12 changes: 8 additions & 4 deletions lib/FieldLocation/FieldLocationFinalContainer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ const renderFieldLocationFinalContainer = (props = {}, formProps = {}) => (rende
));

describe('FieldLocationFinalContainer component', () => {
beforeEach(() => {
renderFieldLocationFinalContainer();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you do it in before each, why do you keep render in tests? if you want to avoid such code duplication - update render function, it's not a place for beforeEach


const button = screen.getByText('stripes-components.selection.controlLabel');

fireEvent.click(button);
});

it('should display passed label', () => {
renderFieldLocationFinalContainer({ labelId: 'Location' });

Expand All @@ -41,8 +49,6 @@ describe('FieldLocationFinalContainer component', () => {
it('should render options based on passed locationIds', async () => {
renderFieldLocationFinalContainer();

fireEvent.click(screen.getByText('stripes-components.selection.controlLabel'));

const renderedLocationOptions = await screen.findAllByText(/Location #[0-9]/);

expect(renderedLocationOptions.length).toBe(locationsIds.length);
Expand All @@ -53,8 +59,6 @@ describe('FieldLocationFinalContainer component', () => {
filterLocations: (records) => records.slice(0, 2),
});

fireEvent.click(screen.getByText('stripes-components.selection.controlLabel'));

const renderedLocationOptions = await screen.findAllByText(/Location #[0-9]/);

expect(renderedLocationOptions).toHaveLength(2);
Expand Down
5 changes: 2 additions & 3 deletions lib/FundFilter/FundFilter.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { noop } from 'lodash';
import { fireEvent, render } from '@testing-library/react';
import { IntlProvider } from 'react-intl';

import FundFilter from './FundFilter';

Expand Down
9 changes: 5 additions & 4 deletions lib/LanguageFilter/LanguageFilter.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import {
cleanup,

Check warning on line 2 in lib/LanguageFilter/LanguageFilter.test.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

'cleanup' is defined but never used. Allowed unused vars must match /React/u

Check warning on line 2 in lib/LanguageFilter/LanguageFilter.test.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

'cleanup' is defined but never used. Allowed unused vars must match /React/u
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you removed function call, but not import...

fireEvent,
render,
} from '@testing-library/react';
import { noop } from 'lodash';

import LanguageFilter from './LanguageFilter';
Expand All @@ -17,8 +20,6 @@
));

describe('LanguageFilter component', () => {
afterEach(cleanup);

it('should display passed title', () => {
const { getByText } = renderFilter();

Expand Down
Loading