Skip to content

Commit c686c1b

Browse files
committed
feat(renderer): add schemaValidatorMapper
1 parent f128f78 commit c686c1b

File tree

5 files changed

+218
-11
lines changed

5 files changed

+218
-11
lines changed

packages/react-form-renderer/demo/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import FormRenderer, { validatorTypes } from '../src';
55
import componentMapper from './form-fields-mapper';
66
import FormTemplate from './form-template';
77
// import sandboxSchema from './sandbox';
8+
import DefaultSchemaError from '../src/parsers/schema-errors';
89

910
const intl = (name) => `translated ${name}`;
1011

@@ -59,6 +60,17 @@ const App = () => {
5960
schema={asyncValidatorSchema}
6061
FormTemplate={FormTemplate}
6162
actionMapper={actionMapper}
63+
schemaValidatorMapper={{
64+
actions: {
65+
loadLabel: (action, fieldName) => {
66+
if (typeof action[1] !== 'string') {
67+
throw new DefaultSchemaError(
68+
`Second argument of loadLabel action has to be a string: ID of the text from the translated database. Error found in ${fieldName}`
69+
);
70+
}
71+
}
72+
}
73+
}}
6274
/>
6375
</div>
6476
);

packages/react-form-renderer/src/components/form-renderer.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const FormRenderer = ({
2424
schema,
2525
validatorMapper,
2626
actionMapper,
27+
schemaValidatorMapper,
2728
debug,
2829
...props
2930
}) => {
@@ -34,7 +35,7 @@ const FormRenderer = ({
3435
try {
3536
const validatorTypes = Object.keys(validatorMapperMerged);
3637
const actionTypes = actionMapper ? Object.keys(actionMapper) : [];
37-
defaultSchemaValidator(schema, componentMapper, validatorTypes, actionTypes);
38+
defaultSchemaValidator(schema, componentMapper, validatorTypes, actionTypes, schemaValidatorMapper);
3839
} catch (error) {
3940
schemaError = error;
4041
console.error(error);
@@ -113,7 +114,18 @@ FormRenderer.propTypes = {
113114
actionMapper: PropTypes.shape({
114115
[PropTypes.string]: PropTypes.func
115116
}),
116-
debug: PropTypes.func
117+
debug: PropTypes.func,
118+
schemaValidatorMapper: PropTypes.shape({
119+
components: PropTypes.shape({
120+
[PropTypes.string]: PropTypes.func
121+
}),
122+
validators: PropTypes.shape({
123+
[PropTypes.string]: PropTypes.func
124+
}),
125+
actions: PropTypes.shape({
126+
[PropTypes.string]: PropTypes.func
127+
})
128+
})
117129
};
118130

119131
FormRenderer.defaultProps = {

packages/react-form-renderer/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export { default as Form } from './components/form';
1212
export { default as FieldArray } from './components/field-array';
1313
export { default as FieldProvider } from './components/field-provider';
1414
export { default as useFieldApi } from './hooks/use-field-api';
15+
export { default as DefaultSchemaError } from './parsers/schema-errors';

packages/react-form-renderer/src/parsers/default-schema-validator.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const checkCondition = (condition, fieldName) => {
7272
}
7373
};
7474

75-
const checkValidators = (validate, fieldName, validatorTypes) => {
75+
const checkValidators = (validate, fieldName, validatorTypes, validatorMapper = {}) => {
7676
if (validate === undefined) {
7777
return;
7878
}
@@ -107,6 +107,10 @@ const checkValidators = (validate, fieldName, validatorTypes) => {
107107
Received "${validator.type}", expected one of: [${validatorTypes}].
108108
`);
109109
}
110+
111+
if (validatorMapper.hasOwnProperty(validator.type)) {
112+
validatorMapper[validator.type](validator, fieldName);
113+
}
110114
}
111115
});
112116
};
@@ -127,7 +131,7 @@ const checkDataType = (type, fieldName) => {
127131
}
128132
};
129133

130-
const checkActions = (actions, name, actionTypes) => {
134+
const checkActions = (actions, name, actionTypes, actionsValidator = {}) => {
131135
Object.keys(actions).forEach((prop) => {
132136
if (!Array.isArray(actions[prop])) {
133137
throw new DefaultSchemaError(`
@@ -152,13 +156,17 @@ const checkActions = (actions, name, actionTypes) => {
152156
Use one of them or define new action in the mapper.
153157
`);
154158
}
159+
160+
if (actionsValidator.hasOwnProperty(actions[prop][0])) {
161+
actionsValidator[actions[prop][0]](actions[prop], name);
162+
}
155163
});
156164
};
157165

158-
const iterateOverFields = (fields, componentMapper, validatorTypes, actionTypes, parent = {}) => {
166+
const iterateOverFields = (fields, componentMapper, validatorTypes, actionTypes, schemaValidatorMapper, parent = {}) => {
159167
fields.forEach((field) => {
160168
if (Array.isArray(field)) {
161-
return iterateOverFields(field, componentMapper, validatorTypes);
169+
return iterateOverFields(field, componentMapper, validatorTypes, actionTypes, schemaValidatorMapper);
162170
}
163171

164172
if (parent.component !== componentTypes.WIZARD) {
@@ -191,30 +199,34 @@ const iterateOverFields = (fields, componentMapper, validatorTypes, actionTypes,
191199
}
192200

193201
if (field.hasOwnProperty('validate')) {
194-
checkValidators(field.validate, field.name, validatorTypes);
202+
checkValidators(field.validate, field.name, validatorTypes, schemaValidatorMapper.validators);
195203
}
196204

197205
if (field.hasOwnProperty('dataType')) {
198206
checkDataType(field.dataType, field.name);
199207
}
200208

201209
if (field.hasOwnProperty('fields')) {
202-
iterateOverFields(field.fields, componentMapper, validatorTypes, actionTypes, field);
210+
iterateOverFields(field.fields, componentMapper, validatorTypes, actionTypes, schemaValidatorMapper, field);
203211
}
204212

205213
if (field.hasOwnProperty('actions')) {
206-
checkActions(field.actions, field.name, actionTypes);
214+
checkActions(field.actions, field.name, actionTypes, schemaValidatorMapper.actions);
215+
}
216+
217+
if (schemaValidatorMapper.components && schemaValidatorMapper.components.hasOwnProperty(field.component)) {
218+
schemaValidatorMapper.components[field.component](field);
207219
}
208220
});
209221
};
210222

211-
const defaultSchemaValidator = (schema, componentMapper, validatorTypes = [], actionTypes = []) => {
223+
const defaultSchemaValidator = (schema, componentMapper, validatorTypes = [], actionTypes = [], schemaValidatorMapper = {}) => {
212224
if (Array.isArray(schema) || typeof schema !== 'object') {
213225
throw new DefaultSchemaError(`Form Schema must be an object, received ${Array.isArray(schema) ? 'array' : typeof schema}!`);
214226
}
215227

216228
checkFieldsArray(schema, 'schema');
217-
iterateOverFields(schema.fields, componentMapper, validatorTypes, actionTypes);
229+
iterateOverFields(schema.fields, componentMapper, validatorTypes, actionTypes, schemaValidatorMapper);
218230
};
219231

220232
export default defaultSchemaValidator;
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/* eslint-disable no-console */
2+
import React from 'react';
3+
import { mount } from 'enzyme';
4+
import FormRenderer from '../../components/form-renderer';
5+
import componentTypes from '../../components/component-types';
6+
import DefaultSchemaError from '../../parsers/schema-errors';
7+
import SchemaErrorComponent from '../../form-renderer/schema-error-component';
8+
9+
describe('schemaValidatorMapper', () => {
10+
let initialProps;
11+
let schemaValidatorMapper;
12+
let _console;
13+
14+
beforeEach(() => {
15+
initialProps = {
16+
componentMapper: {
17+
[componentTypes.TEXT_FIELD]: () => <div>heeeellooo</div>,
18+
[componentTypes.SUB_FORM]: () => <div>heeeellooo</div>
19+
},
20+
validatorMapper: {
21+
custom: () => () => undefined
22+
},
23+
actionMapper: {
24+
translateString: () => 'string'
25+
},
26+
FormTemplate: () => <div>heeeellooo</div>,
27+
onSubmit: jest.fn(),
28+
schema: {
29+
fields: [
30+
{
31+
component: componentTypes.TEXT_FIELD,
32+
name: 'field-input',
33+
validate: [{ type: 'custom' }],
34+
actions: {
35+
label: ['translateString']
36+
}
37+
}
38+
]
39+
}
40+
};
41+
schemaValidatorMapper = {};
42+
_console = console.error;
43+
console.error = jest.fn();
44+
});
45+
46+
afterEach(() => {
47+
console.error = _console;
48+
});
49+
50+
it('component schema error', () => {
51+
schemaValidatorMapper = {
52+
components: {
53+
[componentTypes.TEXT_FIELD]: (field) => {
54+
if (!field.customProp) {
55+
throw new DefaultSchemaError(`Please include "customProp" in ${field.name}`);
56+
}
57+
}
58+
}
59+
};
60+
61+
const wrapper = mount(<FormRenderer {...initialProps} schemaValidatorMapper={schemaValidatorMapper} />);
62+
63+
expect(wrapper.find(SchemaErrorComponent).text()).toEqual(expect.stringContaining('Please include "customProp" in field-input'));
64+
});
65+
66+
it('validator schema error', () => {
67+
schemaValidatorMapper = {
68+
validators: {
69+
custom: (validator, fieldName) => {
70+
if (!validator.customProp) {
71+
throw new DefaultSchemaError(`Please include "customProp" in custom validator in ${fieldName}`);
72+
}
73+
}
74+
}
75+
};
76+
77+
const wrapper = mount(<FormRenderer {...initialProps} schemaValidatorMapper={schemaValidatorMapper} />);
78+
79+
expect(wrapper.find(SchemaErrorComponent).text()).toEqual(
80+
expect.stringContaining('Please include "customProp" in custom validator in field-input')
81+
);
82+
});
83+
84+
it('action schema error', () => {
85+
schemaValidatorMapper = {
86+
actions: {
87+
translateString: (action, fieldName) => {
88+
if (!action.length < 2) {
89+
throw new DefaultSchemaError(`TranslateString actions has to have two arguments in: ${fieldName}`);
90+
}
91+
}
92+
}
93+
};
94+
95+
const wrapper = mount(<FormRenderer {...initialProps} schemaValidatorMapper={schemaValidatorMapper} />);
96+
97+
expect(wrapper.find(SchemaErrorComponent).text()).toEqual(
98+
expect.stringContaining('TranslateString actions has to have two arguments in: field-input')
99+
);
100+
});
101+
102+
describe('nested components', () => {
103+
beforeEach(() => {
104+
initialProps = {
105+
...initialProps,
106+
schema: {
107+
fields: [
108+
{
109+
component: componentTypes.SUB_FORM,
110+
name: 'subform',
111+
...initialProps.schema
112+
}
113+
]
114+
}
115+
};
116+
});
117+
118+
it('component schema error', () => {
119+
schemaValidatorMapper = {
120+
components: {
121+
[componentTypes.TEXT_FIELD]: (field) => {
122+
if (!field.customProp) {
123+
throw new DefaultSchemaError(`Please include "customProp" in ${field.name}`);
124+
}
125+
}
126+
}
127+
};
128+
129+
const wrapper = mount(<FormRenderer {...initialProps} schemaValidatorMapper={schemaValidatorMapper} />);
130+
131+
expect(wrapper.find(SchemaErrorComponent).text()).toEqual(expect.stringContaining('Please include "customProp" in field-input'));
132+
});
133+
134+
it('validator schema error', () => {
135+
schemaValidatorMapper = {
136+
validators: {
137+
custom: (validator, fieldName) => {
138+
if (!validator.customProp) {
139+
throw new DefaultSchemaError(`Please include "customProp" in custom validator in ${fieldName}`);
140+
}
141+
}
142+
}
143+
};
144+
145+
const wrapper = mount(<FormRenderer {...initialProps} schemaValidatorMapper={schemaValidatorMapper} />);
146+
147+
expect(wrapper.find(SchemaErrorComponent).text()).toEqual(
148+
expect.stringContaining('Please include "customProp" in custom validator in field-input')
149+
);
150+
});
151+
152+
it('action schema error', () => {
153+
schemaValidatorMapper = {
154+
actions: {
155+
translateString: (action, fieldName) => {
156+
if (!action.length < 2) {
157+
throw new DefaultSchemaError(`TranslateString actions has to have two arguments in: ${fieldName}`);
158+
}
159+
}
160+
}
161+
};
162+
163+
const wrapper = mount(<FormRenderer {...initialProps} schemaValidatorMapper={schemaValidatorMapper} />);
164+
165+
expect(wrapper.find(SchemaErrorComponent).text()).toEqual(
166+
expect.stringContaining('TranslateString actions has to have two arguments in: field-input')
167+
);
168+
});
169+
});
170+
});

0 commit comments

Comments
 (0)