import {Model, Types} from "../../lib/index"; import assert from "assert"; import {invalidValuesAsString, eol} from "../../lib/utils"; describe("ModelType", () => { it("child model", () => { class UserModel extends Model { structure() { return { name: Types.String({ trim: true, emptyAsNull: true, required: true }), phone: Types.String({ validate: /^\+7 \(\d\d\d\) \d\d\d-\d\d-\d\d$/ }), email: Types.String({ required: true, validate: /^[\w.-]+@[\w.-]+\.\w+$/i }), age: Types.Number({ required: true, validate: (age) => age > 0 }) }; } } class RegistrationModel extends Model { structure() { return { user: Types.Model({ Model: UserModel, required: true }), date: Types.Date({ required: true }) }; } } const AnyRegistrationModel = RegistrationModel as any; assert.throws( () => { const regModel = new RegistrationModel({ date: Date.now() }); }, (err: Error) => err.message === "required user" ); assert.throws( () => { const regModel = new RegistrationModel({ date: Date.now(), user: null as any }); }, (err: Error) => err.message === "required user" ); assert.throws( () => { const regModel = new AnyRegistrationModel({ date: Date.now(), user: [] }); }, (err: Error) => err.message === "invalid model UserModel for user: []" ); assert.throws( () => { const regModel = new AnyRegistrationModel({ date: Date.now(), user: false }); }, (err: Error) => err.message === "invalid model UserModel for user: false" ); assert.throws( () => { const regModel = new AnyRegistrationModel({ date: Date.now(), user: NaN }); }, (err: Error) => err.message === "invalid model UserModel for user: NaN" ); assert.throws( () => { const regModel = new AnyRegistrationModel({ date: Date.now(), user: /x/ }); }, (err: Error) => err.message === "invalid model UserModel for user: /x/" ); assert.throws( () => { const regModel = new RegistrationModel({ date: Date.now(), user: { name: "10", age: 101 } }); }, (err: Error) => err.message === `invalid model UserModel for user: {"name":"10","age":101},${eol} required email` ); const now = Date.now(); const registrationModel = new RegistrationModel({ date: now, user: { name: "Bob ", age: "99" as any, email: "x@x.x" } }); assert.strictEqual( +registrationModel.row.date!, now ); assert.ok( registrationModel.row.date instanceof Date ); const user = registrationModel.row.user!; assert.strictEqual( user.row.name, "Bob" ); assert.strictEqual( user.row.age, 99 ); assert.strictEqual( user.row.email, "x@x.x" ); assert.strictEqual( user.row.phone, null ); }); it("model.toJSON with custom models", () => { class CarModel extends Model { structure() { return { id: Types.Number, color: Types.String }; } } class UserModel extends Model { structure() { return { name: Types.String, car: CarModel }; } } const userModel = new UserModel({ name: "Jack", car: { id: "1" as any, color: "red" } }); assert.deepEqual( userModel.toJSON(), { name: "Jack", car: { id: 1, color: "red" } } ); }); it("equal models", () => { class SomeModel extends Model { structure() { return { prop: Types.Number }; } } const model1 = new SomeModel({ prop: 1 }); const model2 = new SomeModel({ prop: 1 }); const model3 = new SomeModel({ prop: 3 }); const obj1 = {prop: 1}; const obj2 = {prop: 3}; const pairs: any[][] = [ [null, null, true], [null, model1, false], [null, model2, false], [null, model3, false], [model1, model1, true], [model1, model2, true], [model1, model3, false], [model2, model3, false], [model1, obj1, true], [model1, obj2, false], [model3, obj1, false], [model3, obj2, true] ]; pairs.forEach((pair) => { class TestModel extends Model { structure() { return { model: SomeModel }; } } const testModel1 = new TestModel({ model: pair[0] }); const testModel2 = new TestModel({ model: pair[1] }); assert.strictEqual( testModel1.equal( testModel2 ), pair[2], pair.toString() ); assert.strictEqual( testModel2.equal( testModel1 ), pair[2], pair.toString() ); }); }); it("equal circular models", () => { class SomeModel extends Model { structure() { return { name: Types.String, self: SomeModel }; } } const circular1 = new SomeModel(); circular1.set({self: circular1}); const circular2 = new SomeModel(); circular2.set({self: circular2}); const circular3 = new SomeModel({name: "nice"}); circular3.set({self: circular3}); const pairs: any[][] = [ [circular1, circular1, true], [circular1, circular2, true], [circular2, circular2, true], [circular1, circular3, false], [circular2, circular3, false] ]; pairs.forEach((pair, i) => { class TestModel extends Model { structure() { return { model: SomeModel }; } } const model1 = new TestModel({ model: pair[0] }); const model2 = new TestModel({ model: pair[1] }); assert.strictEqual( model1.equal( model2 ), pair[2], i + ": " + invalidValuesAsString(pair) ); assert.strictEqual( model2.equal( model1 ), pair[2], i + ": " + invalidValuesAsString(pair) ); }); }); // when in type defined BaseModel, and in row we have ChildModel (extends BaseModel), // then clone should be instance of ChildModel it("clone model, should return instance of Child", () => { class FirstLevel extends Model { structure() { return { }; } } class SecondLevel extends FirstLevel { structure() { return { level: Types.Number({ default: 2 }) }; } } class MainModel extends Model { structure() { return { some: FirstLevel }; } } const second = new SecondLevel(); const main = new MainModel({ some: second }); assert.deepEqual( main.toJSON(), { some: { level: 2 } } ); const clone = main.clone(); assert.ok( clone.get("some") instanceof SecondLevel ); }); it("triple extending", () => { class First extends Model { structure() { return { first: Types.Number }; } } class Second extends First { structure() { return { ...super.structure(), second: Types.Number }; } } class Third extends Second { structure() { return { ...super.structure(), third: Types.Number }; } } const first = new First({ first: 1 }); const second = new Second({ first: 1, second: 2 }); const third = new Third({ first: 1, second: 2, third: 3 }); assert.deepStrictEqual( first.toJSON(), { first: 1 }); assert.deepStrictEqual( second.toJSON(), { first: 1, second: 2 }); assert.deepStrictEqual( third.toJSON(), { first: 1, second: 2, third: 3 }); assert.strictEqual( first.get("first"), 1 ); assert.strictEqual( second.get("second"), 2 ); assert.strictEqual( third.get("third"), 3 ); }); it("circular structure to json", () => { class MyModel extends Model { structure() { return { model: MyModel }; } } const model = new MyModel(); model.set({model}); assert.throws( () => { model.toJSON(); }, (err: Error) => err.message === "Cannot converting circular structure to JSON" ); }); it("same model as value in two fields", () => { class User extends Model { structure() { return { name: Types.String }; } } class TestModel extends Model { structure() { return { a: User, b: User }; } } const user = new User({ name: "Jack" }); const test = new TestModel({ a: user, b: user }); assert.deepStrictEqual(test.toJSON(), { a: {name: "Jack"}, b: {name: "Jack"} }); }); it("save parent reference after clone", () => { class User extends Model { structure() { return { name: Types.String }; } } class TestModel extends Model { structure() { return { a: User, b: User }; } } const user = new User({ name: "Jack" }); const test = new TestModel({ a: user, b: user }); const testClone = test.clone(); const cloneUserA = testClone.get("a")!; const cloneUserB = testClone.get("b")!; const cloneUserAParent = cloneUserA.findParentInstance(TestModel); const cloneUserBParent = cloneUserB.findParentInstance(TestModel); assert.ok(cloneUserAParent === testClone, "valid clone parent A"); assert.ok(cloneUserBParent === testClone, "valid clone parent B"); }); });