import {Contains, IsDefined, MinLength, ValidateNested} from "../../src/decorator/decorators"; import {Validator} from "../../src/validation/Validator"; import {ValidationTypes} from "../../src/validation/ValidationTypes"; const validator = new Validator(); describe("nested validation", () => { it("should not validate missing nested objects", () => { expect.assertions(4); class MySubClass { @MinLength(5) name: string; } class MyClass { @Contains("hello") title: string; @ValidateNested() @IsDefined() mySubClass: MySubClass; } const model: MyClass = new MyClass(); model.title = "helo"; return validator.validate(model).then(errors => { expect(errors[1].target).toEqual(model); expect(errors[1].value).toBeUndefined(); expect(errors[1].property).toEqual("mySubClass"); expect(errors[1].constraints).toEqual({isDefined: "mySubClass should not be null or undefined"}); }); }); it("should validate nested objects", () => { expect.assertions(55); class MySubClass { @MinLength(5) name: string; } class MyClass { @Contains("hello") title: string; @ValidateNested() mySubClass: MySubClass; @ValidateNested() mySubClasses: MySubClass[]; @ValidateNested() mySubSubClasses: MySubClass[][]; @ValidateNested() mySubSubSubClasses: MySubClass[][][]; } const model = new MyClass(); model.title = "helo world"; model.mySubClass = new MySubClass(); model.mySubClass.name = "my"; model.mySubClasses = [new MySubClass(), new MySubClass()]; model.mySubClasses[0].name = "my"; model.mySubClasses[1].name = "not-short"; model.mySubSubClasses = [[new MySubClass()]]; model.mySubSubClasses[0][0].name = "sub"; model.mySubSubSubClasses = [[[new MySubClass()]]]; model.mySubSubSubClasses[0][0][0].name = "sub"; return validator.validate(model).then(errors => { expect(errors.length).toEqual(5); expect(errors[0].target).toEqual(model); expect(errors[0].property).toEqual("title"); expect(errors[0].constraints).toEqual({contains: "title must contain a hello string"}); expect(errors[0].value).toEqual("helo world"); expect(errors[1].target).toEqual(model); expect(errors[1].property).toEqual("mySubClass"); expect(errors[1].value).toEqual(model.mySubClass); expect(errors[1].constraints).toBeUndefined(); const subError1 = errors[1].children[0]; expect(subError1.target).toEqual(model.mySubClass); expect(subError1.property).toEqual("name"); expect(subError1.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subError1.value).toEqual("my"); expect(errors[2].target).toEqual(model); expect(errors[2].property).toEqual("mySubClasses"); expect(errors[2].value).toEqual(model.mySubClasses); expect(errors[2].constraints).toBeUndefined(); const subError2 = errors[2].children[0]; expect(subError2.target).toEqual(model.mySubClasses); expect(subError2.value).toEqual(model.mySubClasses[0]); expect(subError2.property).toEqual("0"); const subSubError = subError2.children[0]; expect(subSubError.target).toEqual(model.mySubClasses[0]); expect(subSubError.property).toEqual("name"); expect(subSubError.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subSubError.value).toEqual("my"); expect(errors[3].target).toEqual(model); expect(errors[3].property).toEqual("mySubSubClasses"); expect(errors[3].value).toEqual(model.mySubSubClasses); expect(errors[3].constraints).toBeUndefined(); const subError3 = errors[3].children[0]; expect(subError3.target).toEqual(model.mySubSubClasses); expect(subError3.value).toEqual(model.mySubSubClasses[0]); expect(subError3.property).toEqual("0"); const subSubError3 = subError3.children[0]; expect(subSubError3.target).toEqual(model.mySubSubClasses[0]); expect(subSubError3.value).toEqual(model.mySubSubClasses[0][0]); expect(subSubError3.property).toEqual("0"); const subSubSubError3 = subSubError3.children[0]; expect(subSubSubError3.target).toEqual(model.mySubSubClasses[0][0]); expect(subSubSubError3.property).toEqual("name"); expect(subSubSubError3.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subSubSubError3.value).toEqual("sub"); expect(errors[4].target).toEqual(model); expect(errors[4].property).toEqual("mySubSubSubClasses"); expect(errors[4].value).toEqual(model.mySubSubSubClasses); expect(errors[4].constraints).toBeUndefined(); const subError4 = errors[4].children[0]; expect(subError4.target).toEqual(model.mySubSubSubClasses); expect(subError4.value).toEqual(model.mySubSubSubClasses[0]); expect(subError4.property).toEqual("0"); const subSubError4 = subError4.children[0]; expect(subSubError4.target).toEqual(model.mySubSubSubClasses[0]); expect(subSubError4.value).toEqual(model.mySubSubSubClasses[0][0]); expect(subSubError4.property).toEqual("0"); const subSubSubError4 = subSubError4.children[0]; expect(subSubSubError4.target).toEqual(model.mySubSubSubClasses[0][0]); expect(subSubSubError4.value).toEqual(model.mySubSubSubClasses[0][0][0]); expect(subSubSubError4.property).toEqual("0"); const subSubSubSubError4 = subSubSubError4.children[0]; expect(subSubSubSubError4.target).toEqual(model.mySubSubSubClasses[0][0][0]); expect(subSubSubSubError4.property).toEqual("name"); expect(subSubSubSubError4.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subSubSubSubError4.value).toEqual("sub"); }); }); it("should validate when nested is not object", () => { expect.assertions(4); class MySubClass { @MinLength(5) name: string; } class MyClass { @ValidateNested() mySubClass: MySubClass; } const model = new MyClass(); model.mySubClass = "invalidnested object" as any; return validator.validate(model).then(errors => { expect(errors[0].target).toEqual(model); expect(errors[0].property).toEqual("mySubClass"); expect(errors[0].children.length).toEqual(1); const subError = errors[0].children[0]; expect(subError.constraints).toEqual({[ValidationTypes.NESTED_VALIDATION]: "nested property mySubClass must be either object or array"}); }); }); it("should validate nested set", () => { expect.assertions(24); class MySubClass { @MinLength(5) name: string; } class MyClass { @Contains("hello") title: string; @ValidateNested() mySubClass: MySubClass; @ValidateNested() mySubClasses: Set; } const model = new MyClass(); model.title = "helo world"; model.mySubClass = new MySubClass(); model.mySubClass.name = "my"; model.mySubClasses = new Set(); const submodel1 = new MySubClass(); submodel1.name = "my"; model.mySubClasses.add(submodel1); const submodel2 = new MySubClass(); submodel2.name = "not-short"; model.mySubClasses.add(submodel2); return validator.validate(model).then(errors => { expect(errors.length).toEqual(3); expect(errors[0].target).toEqual(model); expect(errors[0].property).toEqual("title"); expect(errors[0].constraints).toEqual({contains: "title must contain a hello string"}); expect(errors[0].value).toEqual("helo world"); expect(errors[1].target).toEqual(model); expect(errors[1].property).toEqual("mySubClass"); expect(errors[1].value).toEqual(model.mySubClass); expect(errors[1].constraints).toBeUndefined(); const subError1 = errors[1].children[0]; expect(subError1.target).toEqual(model.mySubClass); expect(subError1.property).toEqual("name"); expect(subError1.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subError1.value).toEqual("my"); expect(errors[2].target).toEqual(model); expect(errors[2].property).toEqual("mySubClasses"); expect(errors[2].value).toEqual(model.mySubClasses); expect(errors[2].constraints).toBeUndefined(); const subError2 = errors[2].children[0]; expect(subError2.target).toEqual(model.mySubClasses); expect(subError2.value).toEqual(submodel1); expect(subError2.property).toEqual("0"); const subSubError = subError2.children[0]; expect(subSubError.target).toEqual(submodel1); expect(subSubError.property).toEqual("name"); expect(subSubError.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subSubError.value).toEqual("my"); }); }); it("should validate nested map", () => { expect.assertions(24); class MySubClass { @MinLength(5) name: string; } class MyClass { @Contains("hello") title: string; @ValidateNested() mySubClass: MySubClass; @ValidateNested() mySubClasses: Map; } const model = new MyClass(); model.title = "helo world"; model.mySubClass = new MySubClass(); model.mySubClass.name = "my"; model.mySubClasses = new Map(); const submodel1 = new MySubClass(); submodel1.name = "my"; model.mySubClasses.set("key1", submodel1); const submodel2 = new MySubClass(); submodel2.name = "not-short"; model.mySubClasses.set("key2", submodel2); return validator.validate(model).then(errors => { expect(errors.length).toEqual(3); expect(errors[0].target).toEqual(model); expect(errors[0].property).toEqual("title"); expect(errors[0].constraints).toEqual({contains: "title must contain a hello string"}); expect(errors[0].value).toEqual("helo world"); expect(errors[1].target).toEqual(model); expect(errors[1].property).toEqual("mySubClass"); expect(errors[1].value).toEqual(model.mySubClass); expect(errors[1].constraints).toBeUndefined(); const subError1 = errors[1].children[0]; expect(subError1.target).toEqual(model.mySubClass); expect(subError1.property).toEqual("name"); expect(subError1.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subError1.value).toEqual("my"); expect(errors[2].target).toEqual(model); expect(errors[2].property).toEqual("mySubClasses"); expect(errors[2].value).toEqual(model.mySubClasses); expect(errors[2].constraints).toBeUndefined(); const subError2 = errors[2].children[0]; expect(subError2.target).toEqual(model.mySubClasses); expect(subError2.value).toEqual(submodel1); expect(subError2.property).toEqual("key1"); const subSubError = subError2.children[0]; expect(subSubError.target).toEqual(submodel1); expect(subSubError.property).toEqual("name"); expect(subSubError.constraints).toEqual({minLength: "name must be longer than or equal to 5 characters"}); expect(subSubError.value).toEqual("my"); }); }); });