Skip to content

Commit

Permalink
feat: add target field placeholder (#2363)
Browse files Browse the repository at this point in the history
* feat: add helper function to get target field name

* test: add tests for `_target_` placeholder population

* docs: add `_target_` placeholder information

* feat: add support for multiple target placeholders in error messages

* test: add tests for multiple target placeholders in error messages
  • Loading branch information
davestewart authored and logaretm committed Sep 22, 2019
1 parent f535616 commit b905b85
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 2 deletions.
3 changes: 2 additions & 1 deletion docs/guide/displaying-errors.md
Expand Up @@ -202,6 +202,7 @@ You can use any names for your placeholders, except for:
- `{_field_}` which is the field name.
- `{_value_}` which is the field value.
- `{_rule_}` which is the rule name.
- `{_target_}` which is any related target field name.

Which are provided internally.
:::
Expand All @@ -216,7 +217,7 @@ interface ValidationMessageGenerator {
}
```

The `field` is the field name, the `values` argument is an object containing the placeholder values used in string interpolation. Meaning it will contain `_value_`, `_field_` and `_rule_` values as well as any other params previously declared.
The `field` is the field name, the `values` argument is an object containing the placeholder values used in string interpolation. Meaning it will contain `_value_`, `_field_`, `_rule_` and `_target_` values as well as any other params previously declared.

You can use this feature to create dynamic messages for your rules which is helpful for [providing multiple reasons for failing a rule](./advanced-validation.md#dynamic-messages), or [localization](./localization.md).

Expand Down
28 changes: 27 additions & 1 deletion src/validate.ts
Expand Up @@ -215,7 +215,8 @@ function _generateFieldError(
...(data || {}),
_field_: field.name,
_value_: value,
_rule_: ruleName
_rule_: ruleName,
..._getTargetName(field, ruleSchema, ruleName)
};

if (
Expand All @@ -241,6 +242,31 @@ function _generateFieldError(
};
}

function _getTargetName(
field: FieldContext,
ruleSchema: ValidationRuleSchema,
ruleName: string
): Record<string, string> {
if (ruleSchema.params) {
const hasMultiple = ruleSchema.params.filter(param => (param as RuleParamConfig).isTarget).length > 1;
const names: Record<string, string> = {};
for (let index = 0; index < ruleSchema.params.length; index++) {
const param: RuleParamConfig = ruleSchema.params[index] as RuleParamConfig;
if (param.isTarget) {
const key = field.rules[ruleName][index];
const name = field.names[key] || key;
if (hasMultiple) {
names[`_${param.name}Target_`] = name;
} else {
names._target_ = name;
}
}
}
return names;
}
return {};
}

function _normalizeMessage(template: ValidationMessageTemplate, field: string, values: Record<string, any>) {
if (typeof template === 'function') {
return template(field, values);
Expand Down
57 changes: 57 additions & 0 deletions tests/validate.spec.js
@@ -1,5 +1,6 @@
import { validate } from '@/validate';
import { extend } from '@/extend';
import { confirmed } from '@/rules';

test('returns custom error messages passed in ValidationOptions', async () => {
extend('truthy', {
Expand All @@ -20,3 +21,59 @@ test('returns custom error messages passed in ValidationOptions', async () => {

expect(result.errors[0]).toEqual(customMessage);
});

describe('target field placeholder', () => {
extend('confirmed', {
...confirmed,
message: '{_field_} must match {_target_}'
});

const names = { foo: 'Foo', bar: 'Bar', baz: 'Baz' };

test('uses target field name, if supplied in options', async () => {
const values = { foo: 10, bar: 20 };
const rules = 'confirmed:foo';
const options = {
name: names.bar,
values,
names
};
const result = await validate(values.bar, rules, options);
expect(result.errors[0]).toEqual('Bar must match Foo');
});

test('uses target field key, if target field name not supplied in options', async () => {
const values = { foo: 10, bar: 20 };
const rules = 'confirmed:foo';
const options = {
name: names.bar,
values
};
const result = await validate(values.bar, rules, options);
expect(result.errors[0]).toEqual('Bar must match foo');
});

test('works for multiple targets', async () => {
extend('sum_of', {
message: '{_field_} must be the sum of {_aTarget_} and {_bTarget_}',
// eslint-disable-next-line prettier/prettier
params: [
{ name: 'a', isTarget: true },
{ name: 'b', isTarget: true }
],
validate: (value, { a, b }) => value === parseInt(a, 10) + parseInt(b, 10)
});

const values = { foo: 10, bar: 10, baz: 10 };
const names = { foo: 'Foo', bar: 'Bar', baz: 'Baz' };
const rules = 'sum_of:bar,baz';
const options = {
name: names.foo,
values,
names
};

const result = await validate(values.foo, rules, options);
expect(result.errors[0]).toEqual('Foo must be the sum of Bar and Baz');
});
});

0 comments on commit b905b85

Please sign in to comment.