Skip to content

Commit fbfc26c

Browse files
committed
Rules: Updates field prop references in sync rules using new API
1 parent 28e3b35 commit fbfc26c

File tree

4 files changed

+67
-36
lines changed

4 files changed

+67
-36
lines changed

cypress/integration/validation/SyncValidation/Form.props.rules.spec.jsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('Form.props.rules', function () {
1010
.focus()
1111
.blur()
1212
.should('not.have.class', 'is-valid')
13-
.should('not.have.class', 'is-invalid');
13+
.should('not.have.class', 'is-invalid')
1414
});
1515

1616
it('clearing optional unexpected field resets validation status', () => {
@@ -19,7 +19,7 @@ describe('Form.props.rules', function () {
1919
.should('have.class', 'is-invalid')
2020
.clear()
2121
.should('not.have.class', 'is-invalid')
22-
.should('not.have.class', 'is-valid');
22+
.should('not.have.class', 'is-valid')
2323
});
2424

2525
it('clearing required unexpected field retains validation status', () => {
@@ -30,35 +30,35 @@ describe('Form.props.rules', function () {
3030
.should('have.class', 'is-invalid')
3131
.clear()
3232
.should('have.class', 'is-invalid')
33-
.should('not.have.class', 'is-valid');
33+
.should('not.have.class', 'is-valid')
3434
});
3535

3636
it('optional field with name-specific matching value resolves', () => {
3737
cy.get(fieldSelector)
3838
.clear().type('some').should('have.value', 'some')
3939
.should('have.class', 'is-valid')
40-
.should('not.have.class', 'is-invalid');
40+
.should('not.have.class', 'is-invalid')
4141
});
4242

4343
it('optional field with name-specific unmatching value rejects', () => {
4444
cy.get(fieldSelector)
4545
.clear().type('foo').should('have.value', 'foo')
4646
.should('have.class', 'is-invalid')
47-
.should('not.have.class', 'is-valid');
47+
.should('not.have.class', 'is-valid')
4848
});
4949

5050
it('optional field with type-specific matching value resolves', () => {
5151
cy.get(fieldSelector)
5252
.clear().type('some').should('have.value', 'some')
5353
.should('have.class', 'is-valid')
54-
.should('not.have.class', 'is-invalid');
54+
.should('not.have.class', 'is-invalid')
5555
});
5656

5757
it('optional field with type-specific unmatching value rejects', () => {
5858
cy.get(fieldSelector)
5959
.clear().type('123').should('have.value', '123')
6060
.should('have.class', 'is-invalid')
61-
.should('not.have.class', 'is-valid');
61+
.should('not.have.class', 'is-valid')
6262
});
6363

6464
it('required field with name-specific matching value resolves', () => {
@@ -67,7 +67,7 @@ describe('Form.props.rules', function () {
6767
cy.get(fieldSelector)
6868
.clear().type('some').should('have.value', 'some')
6969
.should('have.class', 'is-valid')
70-
.should('not.have.class', 'is-invalid');
70+
.should('not.have.class', 'is-invalid')
7171
});
7272

7373
it('required field with name-specific unmatching value rejects', () => {
@@ -76,6 +76,19 @@ describe('Form.props.rules', function () {
7676
cy.get(fieldSelector)
7777
.clear().type('foo').should('have.value', 'foo')
7878
.should('have.class', 'is-invalid')
79-
.should('not.have.class', 'is-valid');
79+
.should('not.have.class', 'is-valid')
8080
});
81+
82+
it('re-evaluates rule when referenced field prop updates', () => {
83+
cy.get('[name="fieldOne"]')
84+
.clear()
85+
.type('something').should('have.value', 'something')
86+
87+
cy.get('[name="fieldTwo"]')
88+
.type('foo').should('have.value', 'foo')
89+
.should('have.class', 'is-invalid')
90+
.clear()
91+
.type('something').should('have.value', 'something')
92+
.should('have.class', 'is-valid')
93+
});
8194
});

examples/validation/SyncValidation/Form.props.rules.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const rules = {
1212
fieldOne: ({ value, fieldProps }) => {
1313
const { ref: { props } } = fieldProps;
1414
return (value !== 'foo');
15+
},
16+
fieldTwo: ({ value, getFieldProp }) => {
17+
return (value === getFieldProp(['fieldOne', 'value']));
1518
}
1619
}
1720
};
@@ -25,6 +28,10 @@ export default class FormPropsRules extends React.Component {
2528
name="fieldOne"
2629
label="Field one"
2730
hint="Must be more than 3 characters and not equal to `foo`" />
31+
<Input
32+
name="fieldTwo"
33+
label="Field two"
34+
hint="Valid when equals to `fieldOne` value" />
2835
</Form>
2936
);
3037
}

src/utils/fieldUtils/validateSync.js

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/**
22
* Synchronous validation of a field.
33
*/
4+
import createPropGetter from './createPropGetter';
45
import { commonErrorTypes, createRejectedRule, composeResult } from './validate';
56
import { ruleSelectors } from '../formUtils/getFieldRules';
6-
import ensafeMap from '../ensafeMap';
77
import dispatch from '../dispatch';
88

99
/**
@@ -18,7 +18,7 @@ import dispatch from '../dispatch';
1818
*/
1919
function getRejectedRules(resolverArgs) {
2020
const rejectedRules = [];
21-
const { fieldProps, fields: fieldsOrigin, form } = resolverArgs;
21+
const { fieldProps, form } = resolverArgs;
2222

2323
/* Iterating through each rule selector ("name" and "type") */
2424
ruleSelectors.forEach((ruleSelector) => {
@@ -33,11 +33,8 @@ function getRejectedRules(resolverArgs) {
3333
}
3434

3535
rules.forEach((rule) => {
36-
const { refs, name, selector, resolver } = rule;
37-
const fields = ensafeMap(fieldsOrigin, refs);
38-
39-
// Cannot use Immutable instances in the resolver since it's unclear how to proxy them
40-
const isExpected = dispatch(resolver, { ...resolverArgs, fields }, { withImmutable: false });
36+
const { name, selector, resolver } = rule;
37+
const isExpected = dispatch(resolver, resolverArgs, form.context);
4138

4239
if (isExpected) {
4340
return;
@@ -90,20 +87,21 @@ export default function validateSync({ fieldProps, fields, form }) {
9087
return composeResult(true);
9188
}
9289

90+
const resolverArgs = {
91+
[valuePropName]: value,
92+
fieldProps,
93+
getFieldProp: createPropGetter(fields),
94+
fields,
95+
form
96+
};
97+
9398
if (rule) {
9499
//
95100
// TODO Make observable and ensafe {Field.props.rule} resolver as well.
96101
//
97102
const isExpected = (typeof rule === 'function')
98103
/* Enfore mutability of args for fields proxying */
99-
? dispatch(rule, {
100-
[valuePropName]: value,
101-
fieldProps,
102-
fields,
103-
form
104-
}, {
105-
withImmutable: false
106-
})
104+
? dispatch(rule, resolverArgs, { withImmutable: false })
107105
: rule.test(value);
108106

109107
if (!isExpected) {
@@ -118,12 +116,7 @@ export default function validateSync({ fieldProps, fields, form }) {
118116
* A form-wide validation provided by "rules" property of the Form.
119117
* The latter property is also inherited from the context passed by FormProvider.
120118
*/
121-
const rejectedRules = getRejectedRules({
122-
[valuePropName]: value,
123-
fieldProps,
124-
fields,
125-
form
126-
});
119+
const rejectedRules = getRejectedRules(resolverArgs);
127120

128121
if (rejectedRules.length > 0) {
129122
return composeResult(false, rejectedRules);

src/utils/rxUtils/createRulesSubscriptions.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,30 @@ import makeObservable from './makeObservable';
22
import flushFieldRefs from '../flushFieldRefs';
33
import getFieldRules from '../formUtils/getFieldRules';
44

5+
//
6+
// TODO
7+
// Change rule subscriptions according to new "getFieldProp" API
8+
//
9+
510
export default function createRulesSubscriptions({ fieldProps, fields, form }) {
611
const { rxRules } = form.state;
712
const value = fieldProps.get(fieldProps.get('valuePropName'));
8-
const resolverArgs = { value, fieldProps, fields, form };
13+
14+
const resolverArgs = {
15+
value,
16+
fieldProps,
17+
fields,
18+
form
19+
};
920

1021
const ruleGroups = getFieldRules({
1122
fieldProps,
1223
schema: form.formRules,
13-
transformRule(defaultProps) {
14-
const { refs } = flushFieldRefs(defaultProps.resolver, resolverArgs);
15-
return { ...defaultProps, refs };
24+
transformRule(ruleParams) {
25+
return {
26+
...ruleParams,
27+
refs: flushFieldRefs(ruleParams.resolver, resolverArgs)
28+
};
1629
}
1730
});
1831

@@ -21,7 +34,9 @@ export default function createRulesSubscriptions({ fieldProps, fields, form }) {
2134
}
2235

2336
/**
24-
* Create observable for each rule resolver function to watch for the referenced fields.
37+
* Create observable for each rule where another field(s) is referenced.
38+
* The observable will listen for the referenced props change event and re-evaluate
39+
* the validation rule(s) where that prop is referenced.
2540
*/
2641
ruleGroups.forEach((ruleGroup) => {
2742
ruleGroup.forEach((rule) => {
@@ -31,7 +46,10 @@ export default function createRulesSubscriptions({ fieldProps, fields, form }) {
3146

3247
makeObservable(rule.resolver, resolverArgs, {
3348
subscribe() {
34-
form.eventEmitter.emit('validateField', { fieldProps, force: true });
49+
form.eventEmitter.emit('validateField', {
50+
fieldProps,
51+
force: true
52+
});
3553
}
3654
});
3755
});

0 commit comments

Comments
 (0)