diff --git a/index.js b/index.js
index 13a57bc7..a70394da 100644
--- a/index.js
+++ b/index.js
@@ -5,6 +5,8 @@ export { default as CustomMetaSection } from './lib/CustomMetaSection';
export { default as DateFilter } from './lib/DateFilter';
export { default as DocumentCard } from './lib/DocumentCard';
export { default as DocumentFilter } from './lib/DocumentFilter';
+export { default as DocumentFilterForm } from './lib/DocumentFilter/DocumentFilterForm';
+export { default as DocumentFilterFieldArray } from './lib/DocumentFilter/DocumentFilterFieldArray';
export { default as DocumentsFieldArray } from './lib/DocumentsFieldArray';
export { default as DuplicateModal } from './lib/DuplicateModal';
export { default as EditCard } from './lib/EditCard';
diff --git a/lib/DocumentFilter/DocumentFilter.js b/lib/DocumentFilter/DocumentFilter.js
index 531ff6d1..d654ec22 100644
--- a/lib/DocumentFilter/DocumentFilter.js
+++ b/lib/DocumentFilter/DocumentFilter.js
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
-import { useState } from 'react';
+
import {
Accordion,
FilterAccordionHeader,
@@ -8,18 +8,26 @@ import {
import { FormattedMessage } from 'react-intl';
import {
- deparseKiwtQueryFilters,
parseKiwtQueryFilters,
} from '@k-int/stripes-kint-components';
import DocumentFilterForm from './DocumentFilterForm';
-const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) => {
- // atTypeValues are only passed for SupplementaryDocumentFilter
- const filterType = atTypeValues.length > 0 ? 'supplementaryDocuments' : 'documents';
- const [editingFilters, setEditingFilters] = useState(false);
- const openEditModal = () => setEditingFilters(true);
- const closeEditModal = () => setEditingFilters(false);
+const DocumentFilter = ({
+ activeFilters,
+ atTypeValues = [], // DEPRECATED
+ categoryValues = [], // Use this instead of atTypeValues
+ filterHandlers,
+ filterLabel,
+ filterModalProps = {},
+ filterName = 'docs'
+}) => {
+ let categoryValuesToUse = categoryValues;
+ if (categoryValuesToUse.length === 0 && atTypeValues.length !== 0) {
+ // eslint-disable-next-line no-console
+ console.error('atTypeValues have been deprecated from Ramsons. Use categoryValues instead');
+ categoryValuesToUse = atTypeValues;
+ }
// Due to how filters are handled within SearchAndSortQuery the filter string needs to be parsed back into a usual object
const parseQueryString = (filterArray) => {
@@ -55,52 +63,16 @@ const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) =>
return [];
};
- const parsedFilterData = parseQueryString(activeFilters?.[filterType] || []);
-
- const handleSubmit = (values) => {
- // In order to convert the form values into the shape for them to be deparsed we do the inverse of the above
- // Adding a || operator between all elements of the filters array and a && operator between all elements of the nested arrays
- // With special logic to ensure that operators are not added infront of the first elements of any arrays, to ensure no grouping errors
- const kiwtQueryShape = values?.filters?.reduce((acc, curr, index) => {
- let newAcc = [...acc];
-
- if (index !== 0) {
- newAcc = [...newAcc, '||'];
- }
-
- newAcc = [
- ...newAcc,
- curr.rules.reduce((a, c, i) => {
- return [
- ...a,
- i !== 0 ? '&&' : null, // Don't group on first entry
- c,
- ].filter(Boolean);
- }, []),
- ];
-
- return newAcc;
- }, []);
-
- filterHandlers.state({
- ...activeFilters,
- [filterType]: [
- // Currently the deparse function returns a query string containing whitespace which leads to grouping errors
- // This regex removes all whitespace from the querystring
- deparseKiwtQueryFilters(kiwtQueryShape),
- ],
- });
- setEditingFilters(false);
- };
+ const parsedFilterData = parseQueryString(activeFilters?.[filterName] || []);
return (
}
- onClearFilter={() => filterHandlers.state({ ...activeFilters, [filterType]: [] })
+ id={`clickable-agreement-${filterName}-filter`}
+ label={filterLabel ?? }
+ onClearFilter={() => filterHandlers.state({ ...activeFilters, [filterName]: [] })
}
separator={false}
>
@@ -113,14 +85,12 @@ const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) =>
)}
);
@@ -129,7 +99,13 @@ const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) =>
DocumentFilter.propTypes = {
activeFilters: PropTypes.object,
atTypeValues: PropTypes.arrayOf(PropTypes.object),
+ categoryValues: PropTypes.arrayOf(PropTypes.object),
filterHandlers: PropTypes.object,
+ filterLabel: PropTypes.string,
+ filterName: PropTypes.string,
+ filterModalProps: PropTypes.shape({
+ label: PropTypes.string,
+ }),
};
export default DocumentFilter;
diff --git a/lib/DocumentFilter/DocumentFilter.test.js b/lib/DocumentFilter/DocumentFilter.test.js
new file mode 100644
index 00000000..b9ba7779
--- /dev/null
+++ b/lib/DocumentFilter/DocumentFilter.test.js
@@ -0,0 +1,143 @@
+import { MemoryRouter } from 'react-router-dom';
+import {
+ Button,
+ Accordion,
+ renderWithIntl,
+} from '@folio/stripes-erm-testing';
+
+import { Button as MockStripesButton } from '@folio/stripes/components';
+
+import { waitFor } from '@folio/jest-config-stripes/testing-library/react';
+import { translationsProperties } from '../../test/jest/helpers';
+import DocumentFilter from './DocumentFilter';
+
+import { documentFilterParsing } from './testResources';
+
+const mockPFD = jest.fn();
+
+jest.mock('./DocumentFilterForm', () => ({
+ filters,
+}) => {
+ return (
+
+ mockPFD(filters)}>
+ Test parsed filter data
+
+ DocumentFilterForm
+
+ );
+});
+
+const stateMock = jest.fn();
+const filterHandlers = {
+ state: stateMock,
+ checkbox: () => {},
+ clear: () => {},
+ clearGroup: () => {},
+ reset: () => {},
+};
+
+
+// Todo these should be centralised
+const categoryValues = [
+ {
+ 'id': '2c9180a09262108601926219be050022',
+ 'value': 'consortium_negotiation_document',
+ 'label': 'Consortium negotiation document',
+ },
+ {
+ 'id': '2c9180a09262108601926219bdfc0020',
+ 'value': 'license',
+ 'label': 'License',
+ },
+ {
+ 'id': '2c9180a09262108601926219be010021',
+ 'value': 'misc',
+ 'label': 'Misc',
+ },
+];
+
+
+let renderComponent;
+
+describe('DocumentFilter', () => {
+ describe.each([
+ [
+ 'with active filters',
+ documentFilterParsing.find(dfp => dfp.name === 'simple').deparsed,
+ '1 document filters applied',
+ documentFilterParsing.find(dfp => dfp.name === 'simple').parsed,
+ ],
+ [
+ 'without active filters',
+ { docs: [] },
+ null,
+ []
+ ],
+ [
+ 'with multiple active filters',
+ documentFilterParsing.find(dfp => dfp.name === 'complex').deparsed,
+ '2 document filters applied',
+ documentFilterParsing.find(dfp => dfp.name === 'complex').parsed,
+ ],
+ ])('ActiveFilter tests', (
+ activeFilterTitle,
+ activeFilterState,
+ expectedLayoutText,
+ expectedParsedFilterData
+ ) => {
+ describe(activeFilterTitle, () => {
+ beforeEach(() => {
+ renderComponent = renderWithIntl(
+
+ ,
+ ,
+ translationsProperties
+ );
+ });
+
+ test('renders the document filters accordion', async () => {
+ await Accordion('Documents').exists();
+ });
+
+ test('renders expected filters text', async () => {
+ const baseLayoutText = /document filters applied/;
+ const { queryByText } = renderComponent;
+ if (expectedLayoutText != null) {
+ expect(queryByText(expectedLayoutText)).toBeInTheDocument();
+ } else {
+ expect(queryByText(baseLayoutText)).not.toBeInTheDocument();
+ }
+ });
+
+ it('renders DocumentFilterForm component', () => {
+ const { getByText } = renderComponent;
+ expect(getByText('DocumentFilterForm')).toBeInTheDocument();
+ });
+
+ // TODO
+ // Add test for clearing accordion, whether clear button exists etc (mock filterHandlers.state)
+ // and check the right value are passed when it's clicked
+
+ describe('testing parsedFilterData', () => {
+ beforeEach(async () => {
+ mockPFD.mockClear();
+ await waitFor(async () => {
+ await Button('Test parsed filter data').click();
+ });
+ });
+
+ test('parsed filter data is as expected', () => {
+ expect(mockPFD.mock.calls[0][0]).toEqual(expectedParsedFilterData);
+ });
+ });
+ });
+ });
+
+ // TODO
+ // Add test for passing in/not passing in labels
+});
diff --git a/lib/DocumentFilter/DocumentFilterField.js b/lib/DocumentFilter/DocumentFilterField.js
index 63b7cc75..f835a68f 100644
--- a/lib/DocumentFilter/DocumentFilterField.js
+++ b/lib/DocumentFilter/DocumentFilterField.js
@@ -5,7 +5,12 @@ import { FieldArray } from 'react-final-form-arrays';
import { FormattedMessage } from 'react-intl';
import DocumentFilterRule from './DocumentFilterRule';
-const DocumentFilterField = ({ atTypeValues, index, name }) => {
+const DocumentFilterField = ({
+ categoryValues,
+ filterName,
+ index,
+ name
+}) => {
const {
mutators: { push },
} = useForm();
@@ -15,7 +20,8 @@ const DocumentFilterField = ({ atTypeValues, index, name }) => {
ruleFields.remove(ruleFieldIndex)}
@@ -63,9 +69,10 @@ const DocumentFilterField = ({ atTypeValues, index, name }) => {
};
DocumentFilterField.propTypes = {
- atTypeValues: PropTypes.arrayOf(PropTypes.object),
+ categoryValues: PropTypes.arrayOf(PropTypes.object),
index: PropTypes.number,
name: PropTypes.string,
+ filterName: PropTypes.string,
};
export default DocumentFilterField;
diff --git a/lib/DocumentFilter/DocumentFilterField.test.js b/lib/DocumentFilter/DocumentFilterField.test.js
new file mode 100644
index 00000000..c4314393
--- /dev/null
+++ b/lib/DocumentFilter/DocumentFilterField.test.js
@@ -0,0 +1,96 @@
+import { MemoryRouter } from 'react-router-dom';
+import { FieldArray } from 'react-final-form-arrays';
+
+import { renderWithIntl, Button, TestForm } from '@folio/stripes-erm-testing';
+
+import { translationsProperties } from '../../test/jest/helpers';
+import DocumentFilterField from './DocumentFilterField';
+import { documentFilterParsing } from './testResources';
+
+const onSubmit = jest.fn();
+
+// These should be centralised
+const categoryValues = [
+ {
+ 'id': '2c9180a09262108601926219be050022',
+ 'value': 'consortium_negotiation_document',
+ 'label': 'Consortium negotiation document',
+ },
+ {
+ 'id': '2c9180a09262108601926219bdfc0020',
+ 'value': 'license',
+ 'label': 'License',
+ },
+ {
+ 'id': '2c9180a09262108601926219be010021',
+ 'value': 'misc',
+ 'label': 'Misc',
+ },
+];
+
+let renderComponent;
+describe('DocumentFilterField', () => {
+ describe.each([
+ {
+ initalFilters: documentFilterParsing.find(dfp => dfp.name === 'complex').parsed,
+ expectedFields: 2
+ },
+ {
+ initalFilters: documentFilterParsing.find(dfp => dfp.name === 'simple').parsed,
+ expectedFields: 1
+ },
+ {
+ initalFilters: [],
+ expectedFields: 0
+ }
+ ])('Render DocumentFilterField with $expectedFields in the array', ({ initalFilters, expectedFields }) => {
+ beforeEach(() => {
+ renderComponent = renderWithIntl(
+
+
+
+ {({ fields }) => fields.map((name, index) => (
+
+ ))}
+
+
+ ,
+ ,
+ translationsProperties
+ );
+ });
+
+ it('displays attibute label(s)', () => {
+ const { queryAllByText } = renderComponent;
+ expect(queryAllByText('Attribute')).toHaveLength(expectedFields);
+ });
+
+ it('displays operator label(s)', () => {
+ const { queryAllByText } = renderComponent;
+ expect(queryAllByText('Operator')).toHaveLength(expectedFields);
+ });
+
+ it('displays value label(s)', () => {
+ const { queryAllByText } = renderComponent;
+ expect(queryAllByText('Value')).toHaveLength(expectedFields);
+ });
+
+ if (expectedFields > 0) {
+ test('renders the add rule button', async () => {
+ await Button('Add rule').exists();
+ });
+ // TODO Mock DocumentFilterRule and check that you can add rules, that the right number show up and that they're separated by ANDs
+ }
+ });
+});
diff --git a/lib/DocumentFilter/DocumentFilterFieldArray.js b/lib/DocumentFilter/DocumentFilterFieldArray.js
index 64fd52aa..a55d9ced 100644
--- a/lib/DocumentFilter/DocumentFilterFieldArray.js
+++ b/lib/DocumentFilter/DocumentFilterFieldArray.js
@@ -13,10 +13,14 @@ import { FormattedMessage } from 'react-intl';
import DocumentFilterField from './DocumentFilterField';
const propTypes = {
- atTypeValues: PropTypes.arrayOf(PropTypes.object),
+ categoryValues: PropTypes.arrayOf(PropTypes.object),
+ filterName: PropTypes.string
};
-const DocumentFilterFieldArray = ({ atTypeValues }) => {
+const DocumentFilterFieldArray = ({
+ categoryValues,
+ filterName
+}) => {
const {
mutators: { push },
} = useForm();
@@ -63,8 +67,9 @@ const DocumentFilterFieldArray = ({ atTypeValues }) => {
marginBottom0={index !== fields.length - 1}
>
diff --git a/lib/DocumentFilter/DocumentFilterFieldArray.test.js b/lib/DocumentFilter/DocumentFilterFieldArray.test.js
new file mode 100644
index 00000000..2e74fe0e
--- /dev/null
+++ b/lib/DocumentFilter/DocumentFilterFieldArray.test.js
@@ -0,0 +1,53 @@
+import { MemoryRouter } from 'react-router-dom';
+
+import { renderWithIntl, Button, TestForm } from '@folio/stripes-erm-testing';
+
+import { translationsProperties } from '../../test/jest/helpers';
+import DocumentFilterFieldArray from './DocumentFilterFieldArray';
+
+const onSubmit = jest.fn();
+
+const categoryValues = [
+ {
+ 'id': '2c9180a09262108601926219be050022',
+ 'value': 'consortium_negotiation_document',
+ 'label': 'Consortium negotiation document',
+ },
+ {
+ 'id': '2c9180a09262108601926219bdfc0020',
+ 'value': 'license',
+ 'label': 'License',
+ },
+ {
+ 'id': '2c9180a09262108601926219be010021',
+ 'value': 'misc',
+ 'label': 'Misc',
+ },
+];
+
+describe('DocumentFilterFieldArray', () => {
+ beforeEach(() => {
+ renderWithIntl(
+
+
+
+
+ ,
+ ,
+ translationsProperties
+ );
+ });
+
+ test('renders the document filters button', async () => {
+ await Button('Add filter').exists();
+ });
+
+ // TODO mock DocumentFilterField
+ // Test adding renders card as expected
+ // Test card delete removes an item
+ // Test card heading shows right label
+ // Test multiples are separated by OR (check this scales with right number of ORs for 2/3/4 items)
+});
diff --git a/lib/DocumentFilter/DocumentFilterForm.js b/lib/DocumentFilter/DocumentFilterForm.js
index 502640b5..f8a3e14b 100644
--- a/lib/DocumentFilter/DocumentFilterForm.js
+++ b/lib/DocumentFilter/DocumentFilterForm.js
@@ -1,18 +1,64 @@
+import { useState } from 'react';
import PropTypes from 'prop-types';
-import { Button } from '@folio/stripes/components';
-import arrayMutators from 'final-form-arrays';
-import { FormModal } from '@k-int/stripes-kint-components';
import { FormattedMessage } from 'react-intl';
+
+import arrayMutators from 'final-form-arrays';
+
+import { deparseKiwtQueryFilters, FormModal } from '@k-int/stripes-kint-components';
+
+import { Button } from '@folio/stripes/components';
+
import DocumentFilterFieldArray from './DocumentFilterFieldArray';
const DocumentFilterForm = ({
- atTypeValues,
- editingFilters,
+ activeFilters,
+ categoryValues,
+ filterHandlers,
+ filterModalProps,
+ filterName,
filters,
- handlers: { openEditModal, closeEditModal },
- onSubmit,
}) => {
- const filterBuilder = atTypeValues.length > 0 ? 'supplementaryDocumentFilterBuilder' : 'coreDocumentFilterBuilder';
+ // categoryValues are only passed for SupplementaryDocumentFilter
+ const [editingFilters, setEditingFilters] = useState(false);
+ const openEditModal = () => setEditingFilters(true);
+ const closeEditModal = () => setEditingFilters(false);
+
+ const handleSubmit = (values) => {
+ // In order to convert the form values into the shape for them to be deparsed we do the inverse of the above
+ // Adding a || operator between all elements of the filters array and a && operator between all elements of the nested arrays
+ // With special logic to ensure that operators are not added infront of the first elements of any arrays, to ensure no grouping errors
+ const kiwtQueryShape = values?.filters?.reduce((acc, curr, index) => {
+ let newAcc = [...acc];
+
+ if (index !== 0) {
+ newAcc = [...newAcc, '||'];
+ }
+
+ newAcc = [
+ ...newAcc,
+ curr.rules.reduce((a, c, i) => {
+ return [
+ ...a,
+ i !== 0 ? '&&' : null, // Don't group on first entry
+ c,
+ ].filter(Boolean);
+ }, []),
+ ];
+
+ return newAcc;
+ }, []);
+
+ filterHandlers.state({
+ ...activeFilters,
+ [filterName]: [
+ // Currently the deparse function returns a query string containing whitespace which leads to grouping errors
+ // This regex removes all whitespace from the querystring
+ deparseKiwtQueryFilters(kiwtQueryShape),
+ ],
+ });
+ closeEditModal();
+ };
+
return (
<>