Skip to content

Commit

Permalink
Merge pull request #677 from folio-org/release_9.1.1
Browse files Browse the repository at this point in the history
Release 9.1.1
  • Loading branch information
EthanFreestone authored Apr 19, 2024
2 parents 6b6e0db + 12d52f1 commit 49ad8d0
Show file tree
Hide file tree
Showing 43 changed files with 577 additions and 98 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Change history for stripes-erm-components

## 9.1.1 2024-04-19
* ERM-3186 Change default search options for LocalKB titles to exclude identifiers
* Added defaultSearchKey to useSASQQIndex, allowing for differing behaviour of default qIndex and default search (With no qIndex specified)
* ERM-3172 The failure of reference object when displaying agreement line should be handled elegantly
* New ErrorCard components
* formatJS dependency updated
* Translations
* Added number field utilities
* preventMinusKey -- prevent typing of `-` key in number text field
* preventPasteNegative -- prevent pasting of negative number in number text field

## 9.1.0 2024-03-22
* ERM-3129 Remove explicit typescript version
* ERM-3126 Misleading tailing comma in OrganizationsArrayDisplay
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as DocumentsFieldArray } from './lib/DocumentsFieldArray';
export { default as DuplicateModal } from './lib/DuplicateModal';
export { default as EditCard } from './lib/EditCard';
export { default as ERMForm } from './lib/ERMForm';
export { default as ErrorCard } from './lib/ErrorCard';
export { default as Embargo } from './lib/Embargo';
export { default as EResourceType } from './lib/EResourceType';
export { default as FileUploader } from './lib/FileUploader';
Expand Down
36 changes: 36 additions & 0 deletions lib/ErrorCard/ErrorCard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@import "@folio/stripes-components/lib/variables.css";

.titleWrap {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
}

h1.title {
font-size: var(--font-size-large);
text-align: center;
padding: 15px;
line-height: 125%;
}

h3.title {
font-size: var(--font-size-medium);
text-align: center;
}

@media screen and (width <= 900px) {
h1.title {
font-size: var(--font-size-medium);
}

h3.title {
font-size: var(--font-size-small);
}
}

.fadeIcon {
opacity: 0.5;
transition: opacity 0.3s ease;
}
75 changes: 75 additions & 0 deletions lib/ErrorCard/ErrorCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Card, Headline, Icon } from '@folio/stripes/components';

import css from './ErrorCard.css';

const propTypes = {
cardStyle: PropTypes.oneOf(['default', 'negative', 'positive']),
error: PropTypes.shape({
number: PropTypes.number,
message: PropTypes.string,
}).isRequired,
headerEnd: PropTypes.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]),
headerStart: PropTypes.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]),
};

const defaultHeaderStart = (
<Icon icon="exclamation-circle" size="small">
<strong>
<FormattedMessage id="stripes-erm-components.errors.errorCard.defaultErrorHeader" />
</strong>
</Icon>
);
const ErrorCard = ({
cardStyle = 'default',
error,
headerEnd,
headerStart = defaultHeaderStart,
}) => {
return (
<Card
cardStyle={cardStyle}
data-test-error-card
data-testid="errorCard"
headerEnd={headerEnd}
headerProps={{ 'data-test-error-card-header': true }}
headerStart={headerStart}
roundedBorder
>
<div className={css.titleWrap}>
<Icon icon="exclamation-circle" iconClassName={css.fadeIcon} size="large" />
<Headline
className={css.title}
faded
margin="none"
tag="h1"
>
{(error?.number || error?.message) ?
<FormattedMessage id="stripes-erm-components.errors.errorCard.title" values={{ number: error?.number, message: error?.message }} />
:
<FormattedMessage id="stripes-erm-components.errors.errorCard.defaultTitle" />
}
</Headline>
<Headline
className={css.title}
faded
margin="none"
tag="h3"
>
<FormattedMessage id="stripes-erm-components.errors.errorCard.text" />
</Headline>
</div>
</Card>
);
};

ErrorCard.propTypes = propTypes;
export default ErrorCard;
78 changes: 78 additions & 0 deletions lib/ErrorCard/ErrorCard.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

import { StaticRouter as Router } from 'react-router-dom';
import { renderWithIntl, Headline } from '@folio/stripes-erm-testing';
import { translationsProperties } from '../../test/jest/helpers';
import ErrorCard from './ErrorCard';

const resourceError = { number: 400, message: 'Bad Request' };
const resourceHeaderStart = 'EKB-TITLE: 22-1887786-11234147a';

const commonError = {};

const headerEnd = null;

let renderComponent;
describe('ErrorCard', () => {
describe('resource error', () => {
beforeEach(() => {
renderComponent = renderWithIntl(
<Router>
<ErrorCard
error={resourceError}
headerEnd={headerEnd}
headerStart={resourceHeaderStart}
/>
</Router>,
translationsProperties
);
});
test('renders ErrorCard component', () => {
const { getByTestId } = renderComponent;
expect(getByTestId('errorCard')).toBeInTheDocument();
});

test('renders correct card header', () => {
const { getByText } = renderComponent;
expect(getByText('EKB-TITLE: 22-1887786-11234147a')).toBeInTheDocument();
});

test('renders the expected error number and message', async () => {
await Headline('Error 400: Bad Request').exists();
});

test('renders the expected error text', async () => {
await Headline('Refresh the page. If the problem persists contact your administrator.').exists();
});
});

describe('common error', () => {
beforeEach(() => {
renderComponent = renderWithIntl(
<Router>
<ErrorCard
error={commonError}
/>
</Router>,
translationsProperties
);
});
test('renders ErrorCard component', () => {
const { getByTestId } = renderComponent;
expect(getByTestId('errorCard')).toBeInTheDocument();
});

test('renders correct card header', () => {
const { getByText } = renderComponent;
expect(getByText('Error')).toBeInTheDocument();
});


test('renders the expected common error message', async () => {
await Headline('Something went wrong.').exists();
});

test('renders the expected error text', async () => {
await Headline('Refresh the page. If the problem persists contact your administrator.').exists();
});
});
});
1 change: 1 addition & 0 deletions lib/ErrorCard/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ErrorCard';
20 changes: 18 additions & 2 deletions lib/hooks/useSASQQIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@ import { useQIndex } from '@k-int/stripes-kint-components';
// the props etc needed to utilise that within SASQ (and generateKiwtQueryParams)
const useSASQQIndex = ({
defaultQIndex = '',
defaultQuery = '' // This is mainly here so we can avoid extra boilerplate fiddling if we want a default query at some point
/*
* defaultQuery is mainly here so we can avoid extra boilerplate fiddling
* if we want a default query at some point
*/
defaultQuery = '',
/*
* In some circumstances you may wish the searchKey to _not_ be
* equivalent to the defaultQIndex when no qIndex exists in the url,
* such as when the defaultQindex is a subset of the options available
* to the user in a SearchKeyControl. In that case deselecting all options
* is assumed to be equivalent to selecting all options.
*/
defaultSearchKey = '',
} = {}) => {
const [qIndex, setQIndex] = useQIndex();

Expand Down Expand Up @@ -38,8 +50,12 @@ const useSASQQIndex = ({
return qIndex;
}

if (!!defaultSearchKey && defaultSearchKey !== '') {
return defaultSearchKey;
}

return defaultQIndex;
}, [defaultQIndex, qIndex]);
}, [defaultQIndex, defaultSearchKey, qIndex]);

return {
initialSearchState,
Expand Down
1 change: 1 addition & 0 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ export { default as selectifyRefdata } from './selectifyRefdata';
export { default as getSiblingIdentifier } from './getSiblingIdentifier';
export { default as getResourceIdentifier } from './getResourceIdentifier';
export { default as getIdsFromUrl } from './getIdsFromUrl';
export * from './numberFieldUtils';
19 changes: 19 additions & 0 deletions lib/utils/numberFieldUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const preventMinusKey = (e) => {
if (e.code === 'Minus') {
e.preventDefault();
}
};

const preventPasteNegative = (e) => {
const clipboardData = e.clipboardData || window.clipboardData;
const pastedData = parseFloat(clipboardData.getData('text'));

if (pastedData < 0) {
e.preventDefault();
}
};

export {
preventMinusKey,
preventPasteNegative
};
113 changes: 113 additions & 0 deletions lib/utils/numberFieldUtils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { waitFor } from '@folio/jest-config-stripes/testing-library/react';
import user from '@folio/jest-config-stripes/testing-library/user-event';

import { TestForm, TextField, renderWithIntl } from '@folio/stripes-erm-testing';
import { Field } from 'react-final-form';
import { TextField as TFComponent } from '@folio/stripes/components';

import {
preventMinusKey,
preventPasteNegative
} from './numberFieldUtils';

import { translationsProperties } from '../../test/jest/helpers';

const fieldLabel = 'TEST FIELD LABEL';
const testId = 'test-field';

const renderTextFieldWithProps = (props) => {
return renderWithIntl(
<TestForm onSubmit={jest.fn()}>
<Field
component={TFComponent}
data-testid={testId}
label={fieldLabel}
name="testField"
type="number"
{...props}
/>
</TestForm>,
translationsProperties
);
};

let renderComponent;
describe('numberFieldUtils', () => {
describe('preventMinusKey', () => {
beforeEach(() => {
renderComponent = renderTextFieldWithProps({
onKeyDown: preventMinusKey
});
});

test('renders the TextField component', async () => {
await TextField(fieldLabel).exists();
});

test('can enter a positive number', async () => {
await waitFor(async () => {
await TextField(fieldLabel).fillIn('20');
});

await TextField(fieldLabel).has({ value: '20' });
});

test('entering a minus key does not work', async () => {
await waitFor(async () => {
await TextField(fieldLabel).fillIn('-');
});

await TextField(fieldLabel).has({ value: '' });
});

test('can\'t enter a negative number', async () => {
await waitFor(async () => {
await TextField(fieldLabel).fillIn('-40');
});

await TextField(fieldLabel).has({ value: '40' });
});
});

describe('preventPasteNegative', () => {
beforeEach(() => {
renderComponent = renderTextFieldWithProps({
onPaste: preventPasteNegative
});
});

test('renders the TextField component', async () => {
await TextField(fieldLabel).exists();
});

test('TextField exists by testId', () => {
const { getByTestId } = renderComponent;

expect(getByTestId(testId)).toBeInTheDocument();
});

test('can paste a positive number', async () => {
const { getByTestId } = renderComponent;
const textField = getByTestId(testId);
await waitFor(async () => {
// Have to click and then paste
await user.click(textField);
await user.paste('20');
});

await TextField(fieldLabel).has({ value: '20' });
});

test('can\'t paste a negative number', async () => {
const { getByTestId } = renderComponent;
const textField = getByTestId(testId);
await waitFor(async () => {
// Have to click and then paste
await user.click(textField);
await user.paste('-20');
});

await TextField(fieldLabel).has({ value: '' });
});
});
});
Loading

0 comments on commit 49ad8d0

Please sign in to comment.