Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add target field placeholder #2363

Merged
merged 5 commits into from Sep 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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');
});
});