@ukhomeoffice/cop-react-form-renderer 7.0.0-echo → 7.1.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/components/CheckYourAnswers/CheckYourAnswers.js +5 -1
- package/dist/components/CheckYourAnswers/CheckYourAnswers.stories.mdx +6 -7
- package/dist/components/FormComponent/FormComponent.stories.mdx +6 -4
- package/dist/components/FormPage/FormPage.stories.mdx +6 -4
- package/dist/components/FormRenderer/FormRenderer.stories.mdx +7 -12
- package/dist/components/FormRenderer/onPageAction.js +36 -0
- package/dist/components/FormRenderer/onPageAction.test.js +113 -0
- package/dist/components/PageActions/PageActions.stories.mdx +3 -3
- package/dist/components/SummaryList/SummaryList.stories.mdx +4 -10
- package/dist/components/TaskList/TaskList.stories.mdx +2 -2
- package/dist/utils/Container/setupNesting.js +1 -0
- package/dist/utils/Validate/validateComponent.js +5 -2
- package/dist/utils/Validate/validateTextArea.js +5 -4
- package/dist/utils/Validate/validateTextArea.test.js +33 -5
- package/dist/utils/canOverrideFieldRequired.js +22 -0
- package/package.json +31 -17
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Runs `eslint` against the project to check for coding issues.
|
|
|
28
28
|
### `yarn test`
|
|
29
29
|
|
|
30
30
|
Launches the test runner in the interactive watch mode.\
|
|
31
|
-
See the section about [running tests](https://
|
|
31
|
+
See the section about [running tests](https://jestjs.io/docs/cli) for more information.
|
|
32
32
|
|
|
33
33
|
### `yarn compile`
|
|
34
34
|
|
|
@@ -41,7 +41,7 @@ The build is minified and the filenames include the hashes.
|
|
|
41
41
|
|
|
42
42
|
Runs Storybook on [http://localhost:6007](http://localhost:6007), which showcases the components within this library.
|
|
43
43
|
|
|
44
|
-
### `yarn storybook
|
|
44
|
+
### `yarn build-storybook`
|
|
45
45
|
|
|
46
46
|
Builds Storybook for production to the `storybook-static` folder.
|
|
47
47
|
|
|
@@ -139,12 +139,16 @@ const CheckYourAnswers = _ref => {
|
|
|
139
139
|
})]
|
|
140
140
|
});
|
|
141
141
|
};
|
|
142
|
+
const getDedupedErrors = () => {
|
|
143
|
+
const dedupedErrors = Array.from(new Set(errors.map(a => a.id))).map(id => errors.find(b => b.id === id));
|
|
144
|
+
return dedupedErrors;
|
|
145
|
+
};
|
|
142
146
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
143
147
|
className: DEFAULT_CLASS,
|
|
144
148
|
children: [title && !hide_title && /*#__PURE__*/(0, _jsxRuntime.jsx)(_copReactComponents.LargeHeading, {
|
|
145
149
|
children: title
|
|
146
150
|
}, 'heading'), errors && errors.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_copReactComponents.ErrorSummary, {
|
|
147
|
-
errors:
|
|
151
|
+
errors: getDedupedErrors()
|
|
148
152
|
}), type === _models.FormTypes.TASK_CYA && pages.length > 0 && sections && sections.map(section => section.tasks.filter(task => task.state !== _models.TaskStates.TYPES.SKIPPED).map((task, taskIndex) => {
|
|
149
153
|
const filterPages = pages.filter(page => task.pages.some(p => p === page.name));
|
|
150
154
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_react.Fragment, {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
{/* Global imports */}
|
|
2
2
|
|
|
3
3
|
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
|
|
4
4
|
import { Details, Heading, Link, Tag } from '@ukhomeoffice/cop-react-components';
|
|
5
5
|
import withMock from 'storybook-addon-mock';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
{/* Local imports */}
|
|
8
8
|
import { HooksContextProvider, ValidationContextProvider } from '../../context';
|
|
9
9
|
import Utils from '../../utils';
|
|
10
10
|
import CheckYourAnswers from './CheckYourAnswers';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
{/* JSON documents */}
|
|
13
13
|
import CIVIL_SERVANT from '../../json/areYouACivilServant.json';
|
|
14
14
|
import GRADE from '../../json/grade.json';
|
|
15
15
|
import TEAMS from '../../json/team.json';
|
|
@@ -21,16 +21,15 @@ import GROUP_OF_ROW_DATA from '../../json/groupOfRowData.json';
|
|
|
21
21
|
import GROUP from'../../json/group.json';
|
|
22
22
|
import GROUP_DATA from '../../json/group.data.json';
|
|
23
23
|
|
|
24
|
+
export const decorators = [withMock];
|
|
25
|
+
|
|
24
26
|
<Meta
|
|
25
27
|
title='Components/Check your answers'
|
|
26
28
|
id='D-CheckYourAnswers'
|
|
27
29
|
component={CheckYourAnswers}
|
|
28
|
-
decorators={[withMock]}
|
|
29
30
|
/>
|
|
30
31
|
|
|
31
|
-
<Heading size='xl' caption='Components'>
|
|
32
|
-
Check your answers
|
|
33
|
-
</Heading>
|
|
32
|
+
<Heading size='xl' caption='Components'> Check your answers </Heading>
|
|
34
33
|
|
|
35
34
|
Renders the **Check your answers** screen for a form.
|
|
36
35
|
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
{/* Global imports */}
|
|
2
2
|
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
|
|
3
3
|
import { Details, Heading, Link } from '@ukhomeoffice/cop-react-components';
|
|
4
4
|
import withMock from 'storybook-addon-mock';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
{/* Local imports */}
|
|
7
7
|
import { HooksContextProvider, ValidationContextProvider } from '../../context';
|
|
8
8
|
import FormComponent from './FormComponent';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
{/* JSON documents */}
|
|
11
11
|
import CIVIL_SERVANT from '../../json/areYouACivilServant.json';
|
|
12
12
|
import GRADE from '../../json/grade.json';
|
|
13
13
|
import TEAMS from '../../json/team.json';
|
|
14
14
|
import USER_PROFILE_DATA from '../../json/userProfile.data.json';
|
|
15
15
|
import USER_PROFILE from '../../json/userProfile.json';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
export const decorators = [withMock];
|
|
18
|
+
|
|
19
|
+
<Meta title="Components/Form component" id="D-FormComponent" component={ FormComponent } />
|
|
18
20
|
|
|
19
21
|
<Heading size="xl" caption="Components">Form component</Heading>
|
|
20
22
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
{/* Global imports */}
|
|
2
2
|
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
|
|
3
3
|
import { Details, Heading, Link } from '@ukhomeoffice/cop-react-components';
|
|
4
4
|
import withMock from 'storybook-addon-mock';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
{/* Local imports */}
|
|
7
7
|
import { HooksContextProvider, ValidationContextProvider } from '../../context';
|
|
8
8
|
import Utils from '../../utils';
|
|
9
9
|
import FormPage from './FormPage';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
{/* JSON documents */}
|
|
12
12
|
import ADDRESS from '../../json/addressDetails.json'
|
|
13
13
|
import CIVIL_SERVANT from '../../json/areYouACivilServant.json';
|
|
14
14
|
import GRADE from '../../json/grade.json';
|
|
@@ -16,7 +16,9 @@ import TEAMS from '../../json/team.json';
|
|
|
16
16
|
import USER_PROFILE_DATA from '../../json/userProfile.data.json';
|
|
17
17
|
import USER_PROFILE from '../../json/userProfile.json';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
export const decorators = [withMock];
|
|
20
|
+
|
|
21
|
+
<Meta title="Components/Form page" id="D-FormPage" component={ FormPage } />
|
|
20
22
|
|
|
21
23
|
<Heading size="xl" caption="Components">Form page</Heading>
|
|
22
24
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
{/* Global imports */}
|
|
2
2
|
|
|
3
3
|
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
|
|
4
4
|
import {
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
import withMock from 'storybook-addon-mock';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
{/* Local imports */}
|
|
15
15
|
import FormRenderer from './FormRenderer';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
{/* JSON documents */}
|
|
18
18
|
import CIVIL_SERVANT from '../../json/areYouACivilServant.json';
|
|
19
19
|
import FIRST_FORM from '../../json/firstForm.json';
|
|
20
20
|
import GRADE from '../../json/grade.json';
|
|
@@ -22,16 +22,11 @@ import TEAMS from '../../json/team.json';
|
|
|
22
22
|
import USER_PROFILE_DATA from '../../json/userProfile.data.json';
|
|
23
23
|
import USER_PROFILE from '../../json/userProfile.json';
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
title='Components/Form renderer'
|
|
27
|
-
id='D-FormRenderer'
|
|
28
|
-
component={FormRenderer}
|
|
29
|
-
decorators={[withMock]}
|
|
30
|
-
/>
|
|
25
|
+
export const decorators = [withMock];
|
|
31
26
|
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</Heading>
|
|
27
|
+
<Meta title='Components/Form renderer' id='D-FormRenderer' component={FormRenderer}/>
|
|
28
|
+
|
|
29
|
+
<Heading size='xl' caption='Components'>Form renderer</Heading>
|
|
35
30
|
|
|
36
31
|
Renders a form with <Link href="https://ukhomeoffice.github.io/cop-react-components/?path=/docs/d-alert--default-story">COP React components</Link>,
|
|
37
32
|
on the basis of a <Link href="/?path=/docs/f-json-form">JSON</Link> that describes which elements are required.
|
|
@@ -72,6 +72,13 @@ const onPageAction = (action, patch, patchLabel, hooks, data, formState, validat
|
|
|
72
72
|
break;
|
|
73
73
|
case _models.PageAction.TYPES.COLLECTION_ADD:
|
|
74
74
|
_utils.default.CollectionPage.addEntry(action.collection, form.page.formData);
|
|
75
|
+
|
|
76
|
+
// We need to delete the collection entry fields from formData as it will be holding previous values.
|
|
77
|
+
if (form.page.formData["".concat(action.collection)] && form.page.formData["".concat(action.collection)].length > 0) {
|
|
78
|
+
const lastIndex = form.page.formData["".concat(action.collection)].length - 1;
|
|
79
|
+
const fieldsToDelete = Object.keys(form.page.formData["".concat(action.collection)][lastIndex]).filter(k => k in form.page.formData && k !== 'id');
|
|
80
|
+
fieldsToDelete.forEach(i => delete form.page.formData[i]);
|
|
81
|
+
}
|
|
75
82
|
pageUpdate = () => _handlers.default.navigate(action, pageId, onPageChange);
|
|
76
83
|
break;
|
|
77
84
|
case _models.PageAction.TYPES.COLLECTION_DUPLICATE:
|
|
@@ -86,6 +93,35 @@ const onPageAction = (action, patch, patchLabel, hooks, data, formState, validat
|
|
|
86
93
|
if (action.recordRemoval) {
|
|
87
94
|
form.page.formData["".concat(action.collection.split('.').pop(), "LastRemoved")] = _objectSpread({}, removedEntry);
|
|
88
95
|
}
|
|
96
|
+
|
|
97
|
+
// The formData needs to be tidied up after removing the collection entry:
|
|
98
|
+
|
|
99
|
+
// 1. Retrieve the formData ID as it will be overwritten by a field with the same name in the collection entry.
|
|
100
|
+
const formDataId = form.page.formData.id;
|
|
101
|
+
|
|
102
|
+
// 2. Build the list of field names to delete and delete them from formData.
|
|
103
|
+
if (removedEntry !== null) {
|
|
104
|
+
const removedEntryFieldsToDelete = Object.keys(removedEntry).filter(k => k in form.page.formData);
|
|
105
|
+
removedEntryFieldsToDelete.forEach(i => delete form.page.formData[i]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3. Retrieve the collection array from formData.
|
|
109
|
+
const collectionArray = form.page.formData[action.collection];
|
|
110
|
+
|
|
111
|
+
// 4. Assign the fields in the last collection entry to formData.
|
|
112
|
+
Object.assign(form.page.formData, collectionArray[collectionArray.length - 1]);
|
|
113
|
+
|
|
114
|
+
// 5. Assign the formData ID back to the formData.
|
|
115
|
+
form.page.formData.id = formDataId;
|
|
116
|
+
|
|
117
|
+
// 6. If the collection array has entries set the correct ActiveId
|
|
118
|
+
// otherwise delete it as well as the collection array.
|
|
119
|
+
if (collectionArray.length > 0) {
|
|
120
|
+
form.page.formData["".concat(action.collection, "ActiveId")] = collectionArray[collectionArray.length - 1].id;
|
|
121
|
+
} else {
|
|
122
|
+
delete form.page.formData["".concat(action.collection, "ActiveId")];
|
|
123
|
+
delete form.page.formData[action.collection];
|
|
124
|
+
}
|
|
89
125
|
break;
|
|
90
126
|
default:
|
|
91
127
|
break;
|
|
@@ -380,6 +380,35 @@ describe('components.FormRenderer.onPageAction', () => {
|
|
|
380
380
|
// that's called as part of the onSubmit hook's onSuccess callback.
|
|
381
381
|
expect(_handlers.default.navigateCalls).toEqual(1);
|
|
382
382
|
});
|
|
383
|
+
it("should clean previous collection entry fields at top level of formData", () => {
|
|
384
|
+
const FORM_STATE = {
|
|
385
|
+
page: {
|
|
386
|
+
formData: {
|
|
387
|
+
'testCollection': [{
|
|
388
|
+
'flightNumber': '1'
|
|
389
|
+
}],
|
|
390
|
+
'flightNumber': '1'
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const ACTION = {
|
|
395
|
+
type: _models.PageAction.TYPES.COLLECTION_ADD,
|
|
396
|
+
collection: 'testCollection'
|
|
397
|
+
};
|
|
398
|
+
const CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
|
|
399
|
+
action: ACTION,
|
|
400
|
+
formState: FORM_STATE
|
|
401
|
+
});
|
|
402
|
+
_onPageAction.default.apply(void 0, Object.values(CUSTOM_ARGS));
|
|
403
|
+
preActionChecks();
|
|
404
|
+
expect(FORM_STATE.page.formData["".concat(ACTION.collection, "ActiveId")]).toBeDefined();
|
|
405
|
+
expect(FORM_STATE.page.formData.flightNumber).toBeUndefined();
|
|
406
|
+
postActionChecks(CUSTOM_ARGS);
|
|
407
|
+
|
|
408
|
+
// This action type also sets up a call to handlers.navigate
|
|
409
|
+
// that's called as part of the onSubmit hook's onSuccess callback.
|
|
410
|
+
expect(_handlers.default.navigateCalls).toEqual(1);
|
|
411
|
+
});
|
|
383
412
|
describe("should handle the ".concat(_models.PageAction.TYPES.COLLECTION_DUPLICATE, " Page Action type"), () => {
|
|
384
413
|
it("calling handlers.navigate if the duplication was successful", () => {
|
|
385
414
|
const FORM_STATE = {
|
|
@@ -533,6 +562,90 @@ describe('components.FormRenderer.onPageAction', () => {
|
|
|
533
562
|
expect(FORM_STATE.page.formData.testCollectionLastRemoved).toBeUndefined();
|
|
534
563
|
postActionChecks(CUSTOM_ARGS);
|
|
535
564
|
|
|
565
|
+
// This action type uses the default onPageChange.
|
|
566
|
+
expect(onPageChangeCalls).toEqual(1);
|
|
567
|
+
});
|
|
568
|
+
it("should clean collection fields at top level of formData when entries remain", () => {
|
|
569
|
+
// After removing the active entry, the collection data is assigned to the
|
|
570
|
+
// top level of formData and the activeId is reset.
|
|
571
|
+
const FORM_STATE = {
|
|
572
|
+
page: {
|
|
573
|
+
formData: {
|
|
574
|
+
testCollectionActiveId: 'removeMe',
|
|
575
|
+
testCollection: [{
|
|
576
|
+
id: 'leaveMeAlone',
|
|
577
|
+
flightNumber: '1'
|
|
578
|
+
}, {
|
|
579
|
+
id: 'removeMe',
|
|
580
|
+
flightNumber: '2'
|
|
581
|
+
}],
|
|
582
|
+
flightNumber: '2',
|
|
583
|
+
testCollectionLastRemoved: 'previousOne'
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
const ACTION = {
|
|
588
|
+
type: _models.PageAction.TYPES.COLLECTION_REMOVE,
|
|
589
|
+
collection: 'testCollection',
|
|
590
|
+
recordRemoval: true
|
|
591
|
+
};
|
|
592
|
+
const CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
|
|
593
|
+
action: ACTION,
|
|
594
|
+
formState: FORM_STATE
|
|
595
|
+
});
|
|
596
|
+
_onPageAction.default.apply(void 0, Object.values(CUSTOM_ARGS));
|
|
597
|
+
preActionChecks();
|
|
598
|
+
expect(FORM_STATE.page.formData.testCollection).toEqual([{
|
|
599
|
+
id: 'leaveMeAlone',
|
|
600
|
+
flightNumber: '1'
|
|
601
|
+
}]);
|
|
602
|
+
expect(FORM_STATE.page.formData.testCollectionLastRemoved).toEqual({
|
|
603
|
+
id: 'removeMe',
|
|
604
|
+
flightNumber: '2'
|
|
605
|
+
});
|
|
606
|
+
expect(FORM_STATE.page.formData.flightNumber).toEqual('1');
|
|
607
|
+
expect(FORM_STATE.page.formData.testCollectionActiveId).toEqual('leaveMeAlone');
|
|
608
|
+
postActionChecks(CUSTOM_ARGS);
|
|
609
|
+
|
|
610
|
+
// This action type uses the default onPageChange.
|
|
611
|
+
expect(onPageChangeCalls).toEqual(1);
|
|
612
|
+
});
|
|
613
|
+
it("should clean collection fields at top level of formData after last entry removed", () => {
|
|
614
|
+
// After removing the last active entry, the collection array and activeID are deleted,
|
|
615
|
+
// and the collection data is removed from top level of formData.
|
|
616
|
+
const FORM_STATE = {
|
|
617
|
+
page: {
|
|
618
|
+
formData: {
|
|
619
|
+
testCollectionActiveId: 'removeMe',
|
|
620
|
+
testCollection: [{
|
|
621
|
+
id: 'removeMe',
|
|
622
|
+
flightNumber: '1'
|
|
623
|
+
}],
|
|
624
|
+
flightNumber: '1',
|
|
625
|
+
testCollectionLastRemoved: 'previousOne'
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
const ACTION = {
|
|
630
|
+
type: _models.PageAction.TYPES.COLLECTION_REMOVE,
|
|
631
|
+
collection: 'testCollection',
|
|
632
|
+
recordRemoval: true
|
|
633
|
+
};
|
|
634
|
+
const CUSTOM_ARGS = _objectSpread(_objectSpread({}, ARGS), {}, {
|
|
635
|
+
action: ACTION,
|
|
636
|
+
formState: FORM_STATE
|
|
637
|
+
});
|
|
638
|
+
_onPageAction.default.apply(void 0, Object.values(CUSTOM_ARGS));
|
|
639
|
+
preActionChecks();
|
|
640
|
+
expect(FORM_STATE.page.formData.testCollection).toBeUndefined();
|
|
641
|
+
expect(FORM_STATE.page.formData.testCollectionLastRemoved).toEqual({
|
|
642
|
+
id: 'removeMe',
|
|
643
|
+
flightNumber: '1'
|
|
644
|
+
});
|
|
645
|
+
expect(FORM_STATE.page.formData.flightNumber).toBeUndefined();
|
|
646
|
+
expect(FORM_STATE.page.formData.testCollectionActiveId).toBeUndefined();
|
|
647
|
+
postActionChecks(CUSTOM_ARGS);
|
|
648
|
+
|
|
536
649
|
// This action type uses the default onPageChange.
|
|
537
650
|
expect(onPageChangeCalls).toEqual(1);
|
|
538
651
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
{/* Global imports */}
|
|
2
2
|
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
|
|
3
3
|
import { Details, Heading, Link } from '@ukhomeoffice/cop-react-components';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
{/* Local imports */}
|
|
6
6
|
import Utils from '../../utils';
|
|
7
7
|
import PageActions from './PageActions';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
{/* JSON documents */}
|
|
10
10
|
import CIVIL_SERVANT from '../../json/areYouACivilServant.json';
|
|
11
11
|
import GRADE from '../../json/grade.json';
|
|
12
12
|
import TEAMS from '../../json/team.json';
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
{/* Global imports */}
|
|
2
2
|
|
|
3
3
|
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
|
|
4
4
|
import { Details, Heading } from '@ukhomeoffice/cop-react-components';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
{/* Local imports */}
|
|
7
7
|
|
|
8
8
|
import Utils from '../../utils';
|
|
9
9
|
import SummaryList from './SummaryList';
|
|
10
10
|
|
|
11
|
-
<Meta
|
|
12
|
-
title='Components/Summary list'
|
|
13
|
-
id='D-SummaryList'
|
|
14
|
-
component={SummaryList}
|
|
15
|
-
/>
|
|
11
|
+
<Meta title='Components/Summary list' id='D-SummaryList' component={SummaryList} />
|
|
16
12
|
|
|
17
|
-
<Heading size='xl' caption='Components'>
|
|
18
|
-
Summary list
|
|
19
|
-
</Heading>
|
|
13
|
+
<Heading size='xl' caption='Components'> Summary list </Heading>
|
|
20
14
|
|
|
21
15
|
Renders a list of key-value pairs, most commonly on the **Check your answers** screen, and optionally
|
|
22
16
|
display action links.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
{/* Global imports */}
|
|
2
2
|
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
|
|
3
3
|
import { Details, Heading } from '@ukhomeoffice/cop-react-components';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
{/* Local imports */}
|
|
6
6
|
import Utils from '../../utils';
|
|
7
7
|
import TaskList from './TaskList';
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ const nestComponents = container => {
|
|
|
15
15
|
return container.components.map(component => {
|
|
16
16
|
// eslint-disable-next-line camelcase
|
|
17
17
|
const full_path = containerPath ? "".concat(containerPath, ".").concat(component.fieldId) : component.fieldId;
|
|
18
|
+
// eslint-disable-next-line camelcase
|
|
18
19
|
const ret = _objectSpread(_objectSpread({}, component), {}, {
|
|
19
20
|
full_path
|
|
20
21
|
});
|
|
@@ -18,6 +18,7 @@ var _validateRegex = _interopRequireDefault(require("./validateRegex"));
|
|
|
18
18
|
var _validateRequired = _interopRequireDefault(require("./validateRequired"));
|
|
19
19
|
var _validateTextArea = _interopRequireDefault(require("./validateTextArea"));
|
|
20
20
|
var _validateTime = _interopRequireDefault(require("./validateTime"));
|
|
21
|
+
var _canOverrideFieldRequired = _interopRequireDefault(require("../canOverrideFieldRequired"));
|
|
21
22
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
22
23
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
23
24
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
@@ -47,7 +48,9 @@ const validateComponent = (component, outerData, formData) => {
|
|
|
47
48
|
value = (0, _getCollectionPageData.default)(component.collectionName, fd);
|
|
48
49
|
}
|
|
49
50
|
if (component.required) {
|
|
50
|
-
|
|
51
|
+
if (!component.requiredOverrideConditions || !(0, _canOverrideFieldRequired.default)(component.requiredOverrideConditions, fd)) {
|
|
52
|
+
error = (0, _validateRequired.default)(value, component.label, component.custom_errors);
|
|
53
|
+
}
|
|
51
54
|
}
|
|
52
55
|
if (!error) {
|
|
53
56
|
switch (component.type) {
|
|
@@ -91,7 +94,7 @@ const validateComponent = (component, outerData, formData) => {
|
|
|
91
94
|
error = (0, _validateMultifile.default)(value, component.custom_errors);
|
|
92
95
|
break;
|
|
93
96
|
case _models.ComponentTypes.TEXT_AREA:
|
|
94
|
-
error = (0, _validateTextArea.default)(component.label, value, component.showCharacterCount, component.custom_errors, component.maxLength);
|
|
97
|
+
error = (0, _validateTextArea.default)(component.label, value, component.showCharacterCount, component.custom_errors, component.required, component.maxLength);
|
|
95
98
|
break;
|
|
96
99
|
default:
|
|
97
100
|
break;
|
|
@@ -11,15 +11,16 @@ exports.default = void 0;
|
|
|
11
11
|
* @param {*} value The value to validate.
|
|
12
12
|
* @param {string} showCharacterCount The flag to enable character count validation.
|
|
13
13
|
* @param {array} customErrors An array of custom errors for the component.
|
|
14
|
+
* @param {boolean} required If the component is required.
|
|
14
15
|
* @param {number} maxLength The maximum allowable number of characters (by default, this is 1000 characters).
|
|
15
16
|
* @returns An error if the value exceeds the maximum allowable number of characters.
|
|
16
17
|
*/
|
|
17
|
-
const validateTextArea = function (label, value, showCharacterCount, customErrors) {
|
|
18
|
-
let maxLength = arguments.length >
|
|
19
|
-
if (!showCharacterCount) {
|
|
18
|
+
const validateTextArea = function (label, value, showCharacterCount, customErrors, required) {
|
|
19
|
+
let maxLength = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1000;
|
|
20
|
+
if (!showCharacterCount || !required && !value) {
|
|
20
21
|
return undefined;
|
|
21
22
|
}
|
|
22
|
-
const hasError = value.length > maxLength;
|
|
23
|
+
const hasError = required && !value || value.length > maxLength;
|
|
23
24
|
if (hasError) {
|
|
24
25
|
if (Array.isArray(customErrors)) {
|
|
25
26
|
const result = customErrors.filter(error => error.type === 'length');
|
|
@@ -15,25 +15,53 @@ describe('utils', () => {
|
|
|
15
15
|
}];
|
|
16
16
|
it('should return no error when the character count flag is disabled', () => {
|
|
17
17
|
const VALUE = 'ALPHA BRAVO';
|
|
18
|
-
expect((0, _validateTextArea.default)(LABEL, VALUE, false, CUSTOM_ERRORS, MAX_LENGTH)).toBeUndefined();
|
|
18
|
+
expect((0, _validateTextArea.default)(LABEL, VALUE, false, CUSTOM_ERRORS, true, MAX_LENGTH)).toBeUndefined();
|
|
19
19
|
});
|
|
20
20
|
it('should return no error when value length has not exceeded the maximum allowable character length', () => {
|
|
21
21
|
const VALUE = 'ALPHA BRA';
|
|
22
|
-
expect((0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, MAX_LENGTH)).toBeUndefined();
|
|
22
|
+
expect((0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, true, MAX_LENGTH)).toBeUndefined();
|
|
23
23
|
});
|
|
24
24
|
it('should return no error when value length has not exceeded the default maximum allowable character length', () => {
|
|
25
25
|
const VALUE = 'ALPHA BRAVO';
|
|
26
|
-
expect((0, _validateTextArea.default)(LABEL, VALUE, true, undefined)).toBeUndefined();
|
|
26
|
+
expect((0, _validateTextArea.default)(LABEL, VALUE, true, undefined, true)).toBeUndefined();
|
|
27
27
|
});
|
|
28
28
|
it('should return an error when value length has exceeded the maximum allowable character length', () => {
|
|
29
29
|
const VALUE = 'ALPHA BRAVO';
|
|
30
|
-
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, undefined, MAX_LENGTH);
|
|
30
|
+
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, undefined, true, MAX_LENGTH);
|
|
31
31
|
expect(error).toBeDefined();
|
|
32
32
|
expect(error).toEqual(ERROR_MSG);
|
|
33
33
|
});
|
|
34
34
|
it('should return a custom error when one is provided', () => {
|
|
35
35
|
const VALUE = 'ALPHA BRAVO CHARLIE';
|
|
36
|
-
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, MAX_LENGTH);
|
|
36
|
+
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, true, MAX_LENGTH);
|
|
37
|
+
expect(error).toBeDefined();
|
|
38
|
+
expect(error).toEqual(CUSTOM_ERROR);
|
|
39
|
+
});
|
|
40
|
+
it('Should return a custom error when one is provided and the value is undefined', () => {
|
|
41
|
+
const VALUE = undefined;
|
|
42
|
+
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, true, MAX_LENGTH);
|
|
43
|
+
expect(error).toBeDefined();
|
|
44
|
+
expect(error).toEqual(CUSTOM_ERROR);
|
|
45
|
+
});
|
|
46
|
+
it('Should return undefined when the component is not required and value is undefined', () => {
|
|
47
|
+
const VALUE = undefined;
|
|
48
|
+
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, false, MAX_LENGTH);
|
|
49
|
+
expect(error).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
it('Should return undefined when component required is undefined and value is undefined', () => {
|
|
52
|
+
const VALUE = undefined;
|
|
53
|
+
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, undefined, MAX_LENGTH);
|
|
54
|
+
expect(error).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
it('Should return error when component required is false and value is over max length', () => {
|
|
57
|
+
const VALUE = 'ALPHA BRAVO CHARLIE';
|
|
58
|
+
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, false, MAX_LENGTH);
|
|
59
|
+
expect(error).toBeDefined();
|
|
60
|
+
expect(error).toEqual(CUSTOM_ERROR);
|
|
61
|
+
});
|
|
62
|
+
it('Should return error when component is required and value is undefined', () => {
|
|
63
|
+
const VALUE = undefined;
|
|
64
|
+
const error = (0, _validateTextArea.default)(LABEL, VALUE, true, CUSTOM_ERRORS, true, MAX_LENGTH);
|
|
37
65
|
expect(error).toBeDefined();
|
|
38
66
|
expect(error).toEqual(CUSTOM_ERROR);
|
|
39
67
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _Condition = _interopRequireDefault(require("./Condition"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
/**
|
|
10
|
+
* Check to see if the field required status can be overridden.
|
|
11
|
+
* All conditions need to be met in order for the override to be permitted.
|
|
12
|
+
* @param {array} requiredOverrideConditions The conditions to evaluate.
|
|
13
|
+
* @param {object} data The form data to check.
|
|
14
|
+
* @returns true or false.
|
|
15
|
+
*/
|
|
16
|
+
const canOverrideFieldRequired = (requiredOverrideConditions, data) => {
|
|
17
|
+
if ((requiredOverrideConditions === null || requiredOverrideConditions === void 0 ? void 0 : requiredOverrideConditions.type) === "or") {
|
|
18
|
+
return _Condition.default.meetsOne(requiredOverrideConditions.conditions, data);
|
|
19
|
+
}
|
|
20
|
+
return _Condition.default.meetsAll(requiredOverrideConditions.conditions, data);
|
|
21
|
+
};
|
|
22
|
+
var _default = exports.default = canOverrideFieldRequired;
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ukhomeoffice/cop-react-form-renderer",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0-alpha",
|
|
4
4
|
"private": false,
|
|
5
5
|
"scripts": {
|
|
6
|
+
"build-storybook": "storybook build",
|
|
6
7
|
"clean": "rimraf dist",
|
|
7
8
|
"compile": "yarn clean && cross-env NODE_ENV=production babel src --out-dir dist --copy-files && yarn post-compile",
|
|
8
9
|
"compile-with-maps": "yarn clean && cross-env NODE_ENV=production babel src --out-dir dist --source-maps true --copy-files && yarn post-compile",
|
|
@@ -10,16 +11,14 @@
|
|
|
10
11
|
"lint": "eslint --ext .js,.jsx src",
|
|
11
12
|
"lint-fix": "eslint --ext .js,.jsx src --fix",
|
|
12
13
|
"post-compile": "rimraf dist/*.test.* dist/**/*.test.* dist/**/*.stories.* dist/docs dist/assets",
|
|
13
|
-
"storybook": "
|
|
14
|
-
"storybook:build": "build-storybook --docs -s src/assets",
|
|
15
|
-
"storybook:start": "start-storybook --docs -s src/assets --no-manager-cache -p 6007",
|
|
14
|
+
"storybook": "storybook dev -p 6007",
|
|
16
15
|
"test": "jest --silent=false --noStackTrace",
|
|
17
16
|
"test-coverage": "jest --coverage --watchAll=false",
|
|
18
17
|
"test-coverage-watch": "jest --coverage --watchAll=true",
|
|
19
18
|
"yalc-publish": "yarn compile-with-maps && cp -r src dist/src && yalc publish --push"
|
|
20
19
|
},
|
|
21
20
|
"dependencies": {
|
|
22
|
-
"@ukhomeoffice/cop-react-components": "5.0.0
|
|
21
|
+
"@ukhomeoffice/cop-react-components": "5.0.0",
|
|
23
22
|
"axios": "^0.23.0",
|
|
24
23
|
"dayjs": "^1.11.0",
|
|
25
24
|
"govuk-frontend": "5.10.2",
|
|
@@ -29,20 +28,23 @@
|
|
|
29
28
|
"devDependencies": {
|
|
30
29
|
"@babel/cli": "7.27.2",
|
|
31
30
|
"@babel/core": "7.27.4",
|
|
32
|
-
"@babel/eslint-parser": "7.
|
|
31
|
+
"@babel/eslint-parser": "7.27.0",
|
|
33
32
|
"@babel/preset-env": "7.27.2",
|
|
34
33
|
"@babel/preset-react": "7.27.1",
|
|
35
34
|
"@monaco-editor/react": "4.7.0",
|
|
36
|
-
"@storybook/addon-a11y": "
|
|
37
|
-
"@storybook/addon-actions": "
|
|
38
|
-
"@storybook/addon-docs": "
|
|
39
|
-
"@storybook/addon-essentials": "
|
|
40
|
-
"@storybook/addon-
|
|
41
|
-
"@storybook/addon-links": "
|
|
42
|
-
"@storybook/
|
|
43
|
-
"@storybook/
|
|
35
|
+
"@storybook/addon-a11y": "7.6.17",
|
|
36
|
+
"@storybook/addon-actions": "7.6.17",
|
|
37
|
+
"@storybook/addon-docs": "7.6.17",
|
|
38
|
+
"@storybook/addon-essentials": "7.6.17",
|
|
39
|
+
"@storybook/addon-interactions": "7.6.17",
|
|
40
|
+
"@storybook/addon-links": "7.6.17",
|
|
41
|
+
"@storybook/addons": "7.6.17",
|
|
42
|
+
"@storybook/builder-vite": "7.6.17",
|
|
43
|
+
"@storybook/node-logger": "7.6.17",
|
|
44
|
+
"@storybook/react": "7.6.17",
|
|
45
|
+
"@storybook/react-vite": "7.6.17",
|
|
44
46
|
"@storybook/storybook-deployer": "2.8.16",
|
|
45
|
-
"@storybook/theming": "
|
|
47
|
+
"@storybook/theming": "7.6.17",
|
|
46
48
|
"@testing-library/jest-dom": "^5.11.4",
|
|
47
49
|
"@testing-library/react": "^11.1.0",
|
|
48
50
|
"@testing-library/react-hooks": "^7.0.2",
|
|
@@ -51,8 +53,12 @@
|
|
|
51
53
|
"axios-mock-adapter": "^1.18.1",
|
|
52
54
|
"babel-jest": "29.7.0",
|
|
53
55
|
"cross-env": "7.0.3",
|
|
56
|
+
"eslint": "8.57.1",
|
|
54
57
|
"eslint-config-airbnb": "19.0.4",
|
|
55
|
-
"eslint-config-prettier": "
|
|
58
|
+
"eslint-config-prettier": "10.1.2",
|
|
59
|
+
"eslint-plugin-import": "2.31.0",
|
|
60
|
+
"eslint-plugin-jsx-a11y": "6.10.2",
|
|
61
|
+
"eslint-plugin-react": "7.37.5",
|
|
56
62
|
"html-react-parser": "^0.10.5",
|
|
57
63
|
"identity-obj-proxy": "3.0.0",
|
|
58
64
|
"jest": "29.7.0",
|
|
@@ -64,7 +70,8 @@
|
|
|
64
70
|
"react": "^16.13.1",
|
|
65
71
|
"react-dom": "^16.13.1",
|
|
66
72
|
"sass": "1.89.1",
|
|
67
|
-
"storybook
|
|
73
|
+
"storybook": "7.6.17",
|
|
74
|
+
"storybook-addon-mock": "5.0.0",
|
|
68
75
|
"vite": "6.3.5",
|
|
69
76
|
"vite-plugin-static-copy": "^2.3.1",
|
|
70
77
|
"vite-plugin-svgr": "4.3.0"
|
|
@@ -77,6 +84,13 @@
|
|
|
77
84
|
"babel-loader": "8.3.0",
|
|
78
85
|
"webpack": "4.44.2"
|
|
79
86
|
},
|
|
87
|
+
"resolutions": {
|
|
88
|
+
"braces": "3.0.3",
|
|
89
|
+
"esbuild": "0.25.0",
|
|
90
|
+
"micromatch": "4.0.8",
|
|
91
|
+
"parse-url": "8.1.0",
|
|
92
|
+
"pbkdf2": "3.1.3"
|
|
93
|
+
},
|
|
80
94
|
"main": "dist/index.js",
|
|
81
95
|
"files": [
|
|
82
96
|
"dist",
|