@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 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://facebook.github.io/create-react-app/docs/running-tests) for more information.
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:build`
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: 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
- <!-- Global imports -->
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
- <!-- Local imports -->
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
- <!-- JSON documents -->
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
- <!-- Global imports -->
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
- <!-- Local imports -->
6
+ {/* Local imports */}
7
7
  import { HooksContextProvider, ValidationContextProvider } from '../../context';
8
8
  import FormComponent from './FormComponent';
9
9
 
10
- <!-- JSON documents -->
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
- <Meta title="Components/Form component" id="D-FormComponent" component={ FormComponent } decorators={[withMock]} />
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
- <!-- Global imports -->
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
- <!-- Local imports -->
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
- <!-- JSON documents -->
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
- <Meta title="Components/Form page" id="D-FormPage" component={ FormPage } decorators={[withMock]} />
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
- <!-- Global imports -->
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
- <!-- Local imports -->
14
+ {/* Local imports */}
15
15
  import FormRenderer from './FormRenderer';
16
16
 
17
- <!-- JSON documents -->
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
- <Meta
26
- title='Components/Form renderer'
27
- id='D-FormRenderer'
28
- component={FormRenderer}
29
- decorators={[withMock]}
30
- />
25
+ export const decorators = [withMock];
31
26
 
32
- <Heading size='xl' caption='Components'>
33
- Form renderer
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
- <!-- Global imports -->
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
- <!-- Local imports -->
5
+ {/* Local imports */}
6
6
  import Utils from '../../utils';
7
7
  import PageActions from './PageActions';
8
8
 
9
- <!-- JSON documents -->
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
- <!-- Global imports -->
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
- <!-- Local imports -->
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
- <!-- Global imports -->
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
- <!-- Local imports -->
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
- error = (0, _validateRequired.default)(value, component.label, component.custom_errors);
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 > 4 && arguments[4] !== undefined ? arguments[4] : 1000;
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.0.0-echo",
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": "yarn storybook:start",
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-foxtrot",
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.19.1",
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": "^6.3.8",
37
- "@storybook/addon-actions": "^6.3.8",
38
- "@storybook/addon-docs": "^6.3.8",
39
- "@storybook/addon-essentials": "^6.3.8",
40
- "@storybook/addon-knobs": "^6.3.1",
41
- "@storybook/addon-links": "^6.3.8",
42
- "@storybook/node-logger": "^6.3.8",
43
- "@storybook/react": "^6.3.8",
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": "^6.3.8",
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": "^8.6.0",
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-addon-mock": "^2.0.1",
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",