Skip to content

Commit

Permalink
fix(validation): accessing nested property value
Browse files Browse the repository at this point in the history
  • Loading branch information
Sayan751 committed Dec 19, 2019
1 parent 728aca3 commit 22698f0
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 12 deletions.
3 changes: 2 additions & 1 deletion packages/__tests__/validation/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
'../.eslintrc.js'
],
rules: {
'no-template-curly-in-string': 'off'
'no-template-curly-in-string': 'off',
'no-useless-escape': 'off'
}
};
2 changes: 1 addition & 1 deletion packages/__tests__/validation/_test-resources.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface Address {
export interface Address {
line1: string;
line2?: string;
city: string;
Expand Down
75 changes: 72 additions & 3 deletions packages/__tests__/validation/validator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
ValidationResult,
IValidationRules,
RegexRule,
LengthRule
LengthRule,
RequiredRule
} from '@aurelia/validation';
import { assert } from '@aurelia/testing';
import { Person } from './_test-resources';
import { Person, Address } from './_test-resources';

describe.only('IValidator', function () {
function setup(validator?: Class<IValidator>) {
Expand Down Expand Up @@ -92,7 +93,7 @@ describe.only('StandardValidator', function () {
assert.instanceOf(result[1].rule, LengthRule);
});

it('validates only given rules for an object property', async function () {
it('if given, validates only the specific rules for an object property', async function () {
const { sut, validationRules } = setup();
const message1 = 'message1', message2 = 'message2';
const obj: Person = new Person('test', (void 0)!, (void 0)!);
Expand All @@ -119,4 +120,72 @@ describe.only('StandardValidator', function () {
assert.equal(result[0].object, obj);
assert.instanceOf(result[0].rule, RegexRule);
});

it('validates all rules by default for an object', async function () {
const { sut, validationRules } = setup();
const message1 = 'message1', message2 = 'message2';
const obj: Person = new Person((void 0)!, (void 0)!, { line1: 'invalid' } as any as Address);
validationRules
.on(obj)

.ensure(o => o.name)
.required()
.withMessage(message1)

.ensure(o => o.age)
.required()
.withMessage(message2)

.ensure(o => o.address.line1)
.matches(/foo/)
.withMessage("\${$value} does not match pattern");

const result = await sut.validateObject(obj);
assert.equal(result.length, 3);

assert.equal(result[0].valid, false, 'expected name to be invalid');
assert.equal(result[0].propertyName, 'name');
assert.equal(result[0].message, message1);
assert.equal(result[0].object, obj);
assert.instanceOf(result[0].rule, RequiredRule);

assert.equal(result[1].valid, false, 'expected age to be invalid');
assert.equal(result[1].propertyName, 'age');
assert.equal(result[1].message, message2);
assert.equal(result[1].object, obj);
assert.instanceOf(result[1].rule, RequiredRule);

assert.equal(result[2].valid, false, 'expected address.line1 to be invalid');
assert.equal(result[2].propertyName, 'address.line1');
assert.equal(result[2].message, "invalid does not match pattern");
assert.equal(result[2].object, obj);
assert.instanceOf(result[2].rule, RegexRule);
});

it('if given, validates only the specific rules for an object', async function () {
const { sut, validationRules } = setup();
const message1 = 'message1', message2 = 'message2';
const obj: Person = new Person((void 0)!, (void 0)!, (void 0)!);
const rules = validationRules
.on(obj)

.ensure(o => o.name)
.required()
.withMessage(message1)

.ensure(o => o.age)
.required()
.withMessage(message2)

.rules;

const result = await sut.validateObject(obj, [rules[0]]);
assert.equal(result.length, 1);

assert.equal(result[0].valid, false);
assert.equal(result[0].propertyName, 'name');
assert.equal(result[0].message, message1);
assert.equal(result[0].object, obj);
assert.instanceOf(result[0].rule, RequiredRule);
});
});
23 changes: 16 additions & 7 deletions packages/validation/src/validator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DI } from '@aurelia/kernel';
import { IValidateable, PropertyRule, validationRules, ValidationResult } from './rule';
import { LifecycleFlags } from '@aurelia/runtime';

export const IValidator = DI.createInterface<IValidator>("IValidator").noDefault();
/**
Expand All @@ -15,7 +16,8 @@ export interface IValidator {
* @param rules - Optional. If unspecified, the implementation should lookup the rules for the
* specified object. This may not be possible for all implementations of this interface.
*/
validateProperty(object: IValidateable, propertyName: string, rules?: PropertyRule[]): Promise<ValidationResult[]>;
validateProperty<T extends IValidateable = IValidateable>(object: T, propertyName: keyof T, rules?: PropertyRule[]): Promise<ValidationResult[]>;
validateProperty<T extends IValidateable = IValidateable>(object: T, propertyName: string, rules?: PropertyRule[]): Promise<ValidationResult[]>;

/**
* Validates all rules for specified object and it's properties.
Expand All @@ -40,7 +42,7 @@ export class StandardValidator implements IValidator {
* @param {*} [rules] - If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
public async validateProperty(object: IValidateable, propertyName: string | number, rules?: PropertyRule[]): Promise<ValidationResult[]> {
public async validateProperty<T extends IValidateable = IValidateable>(object: T, propertyName: keyof T | string, rules?: PropertyRule[]): Promise<ValidationResult[]> {
// TODO support filter by tags
return this.validate(object, propertyName, rules);
}
Expand All @@ -57,18 +59,25 @@ export class StandardValidator implements IValidator {
return this.validate(object, void 0, rules);
}

private async validate(
object: IValidateable,
propertyName?: string | number,
private async validate<T extends IValidateable = IValidateable>(
object: T,
propertyName?: keyof T | string,
rules?: PropertyRule[],
): Promise<ValidationResult[]> {
rules = rules ?? validationRules.get(object);
const validateAllProperties = propertyName === void 0;

const result = await Promise.all(rules.reduce((acc: Promise<ValidationResult[]>[], rule) => {
const { name, expression } = rule.property;
// eslint-disable-next-line eqeqeq
if (validateAllProperties || rule.property.name == propertyName) {
const value = rule.property.name === void 0 ? object : object[rule.property.name];
if (validateAllProperties || name == propertyName) {
let value: unknown;
if (expression === void 0) {
value = object;
} else {
const scope = { bindingContext: object, parentScope: null, scopeParts: [], overrideContext: (void 0)! };
value = expression.evaluate(LifecycleFlags.none, scope, null!);
}
acc.push(rule.validate(value, object));
}
return acc;
Expand Down

0 comments on commit 22698f0

Please sign in to comment.