model-layer
Version:
1,129 lines (913 loc) • 30.9 kB
text/typescript
import {IArrayType, IStringType, Model, Types} from "../../lib/index";
import assert from "assert";
import {eol} from "../../lib/utils";
describe("ArrayType", () => {
it("array of numbers", () => {
class CompanyModel extends Model<CompanyModel> {
structure() {
return {
name: Types.String,
managersIds: Types.Array({
element: Types.Number
})
};
}
}
const AnyCompany = CompanyModel as any;
assert.throws(
() => {
const company = new AnyCompany({
managersIds: false
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: false"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: true
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: true"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: {}
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: {}"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: "1,2"
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: \"1,2\""
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: NaN
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: NaN"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: /x/
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: /x/"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: Infinity
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: Infinity"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: -Infinity
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: -Infinity"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: 0
});
},
(err: Error) =>
err.message === "invalid array number[] for managersIds: 0"
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: [false]
});
},
(err: Error) =>
err.message === `invalid element for array number[] at 0 for model field managersIds: [false],${eol} invalid number for 0: false`
);
assert.throws(
() => {
const company = new AnyCompany({
managersIds: ["1", "wrong"]
});
},
(err: Error) =>
err.message === `invalid element for array number[] at 1 for model field managersIds: ["1","wrong"],${eol} invalid number for 1: "wrong"`
);
const outsideArr = ["1", 2] as number[];
const companyModel = new CompanyModel({
managersIds: outsideArr
});
const managersIds = companyModel.get("managersIds")!;
assert.deepEqual( managersIds, [1, 2] );
assert.strictEqual( managersIds[0], 1 );
assert.ok( outsideArr !== managersIds );
// array should be frozen
assert.throws(
() => {
managersIds[0] = 10;
},
(err: Error) =>
/Cannot assign to read only property/.test(err.message)
);
assert.strictEqual( managersIds[0], 1 );
});
it("emptyAsNull", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
colors: Types.Array({
element: Types.String,
default: [],
emptyAsNull: true
})
};
}
}
const model = new SomeModel();
assert.strictEqual( model.row.colors, null );
model.set({colors: ["red"]});
assert.deepEqual( model.row.colors, ["red"] );
model.set({colors: []});
assert.strictEqual( model.row.colors, null );
});
it("nullAsEmpty", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
colors: Types.Array({
element: Types.String,
nullAsEmpty: true
})
};
}
}
const model = new SomeModel();
assert.deepEqual( model.row.colors, [] );
model.set({colors: ["red"]});
assert.deepEqual( model.row.colors, ["red"] );
model.set({colors: null as any});
assert.deepEqual( model.row.colors, [] );
});
it("array[boolean]", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
answers: Types.Array({
element: Types.Boolean
})
};
}
}
const AnyModel = SomeModel as any;
assert.throws(
() => {
const anyModel = new AnyModel({
answers: [null, false, true, "wrong"]
});
},
(err: Error) =>
err.message === `invalid element for array boolean[] at 3 for model field answers: [null,false,true,"wrong"],${eol} invalid boolean for 3: "wrong"`
);
const someAnswers = [false, true] as boolean[];
const model = new SomeModel({
answers: someAnswers
});
const answers = model.row.answers!;
assert.deepEqual( answers, [false, true] );
assert.strictEqual( answers[0], false );
assert.strictEqual( answers[1], true );
});
it("array[date]", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
pays: Types.Array({
element: Types.Date
})
};
}
}
assert.throws(
() => {
const AnyModel = SomeModel as any;
const someModel = new AnyModel({
pays: ["wrong"]
});
},
(err: Error) =>
err.message === `invalid element for array date[] at 0 for model field pays: ["wrong"],${eol} invalid date for 0: "wrong"`
);
const now = Date.now();
const model = new SomeModel({
pays: [now]
});
const pays = model.row.pays!;
assert.strictEqual( +pays[0], now );
assert.ok( pays[0] instanceof Date );
});
it("array[ChildModel]", () => {
class UserModel extends Model<UserModel> {
structure() {
return {
name: Types.String({
trim: true
})
};
}
}
class CompanyModel extends Model<CompanyModel> {
structure() {
return {
managers: Types.Array({
element: UserModel
})
};
}
}
assert.throws(
() => {
const AnyModel = CompanyModel as any;
const model = new AnyModel({
managers: [false]
});
},
(err: Error) =>
err.message === `invalid element for array UserModel[] at 0 for model field managers: [false],${eol} invalid model UserModel for 0: false`
);
const companyModel = new CompanyModel({
managers: [{
name: " Bob "
}]
});
const managers = companyModel.row.managers as UserModel[];
assert.ok( managers[0] instanceof UserModel );
assert.equal( managers[0].get("name"), "Bob" );
});
it("array[string]", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
colors: Types.Array({
element: Types.String({
upper: true
})
}),
names: Types.Array({
element: Types.String({
trim: true
})
})
};
}
}
assert.throws(
() => {
const AnyModel = SomeModel as any;
const someModel = new AnyModel({
names: [false]
});
},
(err: Error) =>
err.message === `invalid element for array string[] at 0 for model field names: [false],${eol} invalid string for 0: false`
);
const model = new SomeModel({
colors: ["red"],
names: [" Bob "]
});
assert.deepEqual(model.row, {
colors: ["RED"],
names: ["Bob"]
});
});
it("array[object]", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
users: Types.Array({
element: Types.Object({
element: Types.Any
})
})
};
}
}
assert.throws(
() => {
const AnyModel = SomeModel as any;
const someModel = new AnyModel({
users: [{id: 1}, false]
});
},
(err: Error) =>
err.message === `invalid element for array object[] at 1 for model field users: [{"id":1},false],${eol} invalid object {*: any} for 1: false`
);
const model = new SomeModel({
users: [{id: 1}]
});
const users = model.row.users;
assert.deepEqual( users, [{id: 1}] );
});
it("unique primitive", () => {
class CompanyModel extends Model<CompanyModel> {
structure() {
return {
managersIds: Types.Array({
element: Types.Number,
unique: true
})
};
}
}
assert.throws(
() => {
const someCompany = new CompanyModel({
managersIds: [1, 2, 1]
});
},
(err: Error) =>
err.message === "managersIds is not unique, duplicated value 1 inside arr: [1,2,1]"
);
const companyModel = new CompanyModel({
managersIds: [1, 2]
});
assert.throws(
() => {
companyModel.set({managersIds: [2, 2]});
},
(err: Error) =>
err.message === "managersIds is not unique, duplicated value 2 inside arr: [2,2]"
);
assert.deepEqual(companyModel.row.managersIds, [1, 2]);
// in unique validation null != null
companyModel.set({managersIds: [null, undefined, null] as any});
assert.deepEqual(companyModel.row.managersIds, [null, null, null]);
});
it("unique ChildModel", () => {
class UserModel extends Model<UserModel> {
structure() {
return {
name: Types.String
};
}
}
class CompanyModel extends Model<CompanyModel> {
structure() {
return {
managers: Types.Array({
element: UserModel,
unique: true
})
};
}
}
const userModel1 = new UserModel({
name: "test"
});
assert.throws(
() => {
const someCompany = new CompanyModel({
managers: [userModel1, userModel1]
});
},
(err: Error) =>
err.message === "managers is not unique, duplicated value {\"name\":\"test\"} inside arr: [{\"name\":\"test\"},{\"name\":\"test\"}]"
);
const userModel2 = new UserModel({
name: "test"
});
const companyModel = new CompanyModel({
managers: [userModel1, userModel2]
});
let managers = companyModel.row.managers!;
assert.ok( managers[0] === userModel1 );
assert.ok( managers[1] === userModel2 );
assert.throws(
() => {
companyModel.set({managers: [userModel2, userModel2]});
},
(err: Error) =>
err.message === "managers is not unique, duplicated value {\"name\":\"test\"} inside arr: [{\"name\":\"test\"},{\"name\":\"test\"}]"
);
managers = companyModel.row.managers!;
assert.ok( managers[0] === userModel1 );
assert.ok( managers[1] === userModel2 );
});
it("sort", () => {
class CompanyModel extends Model<CompanyModel> {
structure() {
return {
managersIds: Types.Array({
element: Types.Number,
sort: true
})
};
}
}
const companyModel = new CompanyModel({
managersIds: ["30" as any, 2, -10]
});
assert.deepEqual(companyModel.row.managersIds, [-10, 2, 30]);
});
it("sort by custom comparator", () => {
interface ISomeData {
names: string[];
}
class SomeModel extends Model<SomeModel> {
structure() {
return {
names: Types.Array({
element: Types.String,
// sort by second letter
sort: (a, b) =>
+a[1] > +b[1] ?
1 :
-1
})
};
}
}
const model = new SomeModel({
names: ["a3", "d4", "b2", "c1"]
});
assert.deepEqual(model.row.names, ["c1", "b2", "a3", "d4"]);
});
it("matrix", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
matrix: Types.Array({
element: Types.Array({
element: Types.Number
})
})
};
}
}
const model = new SomeModel({
matrix: [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
});
assert.deepEqual(model.row.matrix, [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]);
});
it("model.toJSON with array of models", () => {
class TaskModel extends Model<TaskModel> {
structure() {
return {
name: Types.String
};
}
}
class UserModel extends Model<UserModel> {
structure() {
return {
name: Types.String,
tasks: Types.Array({
element: TaskModel
})
};
}
}
const userModel = new UserModel({
name: "Jack",
tasks: [
{name: "task 1"},
{name: "task 2"}
]
});
assert.deepEqual(
userModel.toJSON(),
{
name: "Jack",
tasks: [
{name: "task 1"},
{name: "task 2"}
]
}
);
});
it("model.clone with array", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
ids: Types.Array({
element: Types.Number
})
};
}
}
const model = new SomeModel({
ids: [1, 2, 3]
});
assert.deepEqual(model.row, {
ids: [1, 2, 3]
});
const clone = model.clone();
assert.ok( clone instanceof SomeModel );
assert.ok( clone !== model );
assert.ok( clone.row.ids !== model.row.ids );
assert.deepEqual(clone.row, {
ids: [1, 2, 3]
});
// change clone model
clone.set({
ids: [3, 4]
});
assert.deepEqual(model.row, {
ids: [1, 2, 3]
});
assert.deepEqual(clone.row, {
ids: [3, 4]
});
// change original model
model.set({
ids: [8]
});
assert.deepEqual(model.row, {
ids: [8]
});
assert.deepEqual(clone.row, {
ids: [3, 4]
});
});
it("test base validate as prior validation", () => {
const testArr1 = [1];
const testArr2 = [2];
class SomeModel extends Model<SomeModel> {
structure() {
return {
ids: Types.Array({
element: Types.Number,
unique: true,
enum: [testArr1, testArr2]
})
};
}
}
assert.throws(
() => {
const someModel = new SomeModel({
ids: [3, 3]
});
},
(err: Error) =>
err.message === "invalid ids: [3,3]"
);
});
it("test array of any values", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
arr: Types.Array
};
}
}
const model = new SomeModel({
arr: [1, "nice"]
});
assert.deepEqual(model.row, {
arr: [1, "nice"]
});
});
it("equal arrays", () => {
const pairs: any[][] = [
[null, null, true],
[null, [], false],
[[], [], true],
[[1, 2], [1, 2], true],
[[2, 1], [1, 2], false],
[[1], [1, 2], false]
];
pairs.forEach((pair) => {
class TestModel extends Model<TestModel> {
structure() {
return {
arr: Types.Array({
element: Types.Number
})
};
}
}
const model1 = new TestModel({
arr: pair[0]
});
const model2 = new TestModel({
arr: pair[1]
});
assert.strictEqual(
model1.equal( model2 ),
pair[2],
pair.toString()
);
assert.strictEqual(
model2.equal( model1 ),
pair[2],
pair.toString()
);
});
});
it("equal circular arrays", () => {
const circular1: any = [];
circular1[0] = circular1;
const circular2: any = [];
circular2[0] = circular2;
const circular3: any = [];
circular3[0] = circular3;
circular3[1] = [];
const circular4 = [circular2, circular2];
const pairs: any[][] = [
[circular1, circular1, true],
[circular1, circular2, true],
[circular2, circular2, true],
[circular1, circular3, false],
[circular2, circular3, false],
[circular2, circular4, false],
[circular4, circular4, true]
];
pairs.forEach((pair) => {
class TestModel extends Model<TestModel> {
structure() {
return {
arr: Types.Array({
element: Types.Array
})
};
}
}
const model1 = new TestModel({
arr: pair[0]
});
const model2 = new TestModel({
arr: pair[1]
});
assert.strictEqual(
model1.equal( model2 ),
pair[2],
pair.toString()
);
assert.strictEqual(
model2.equal( model1 ),
pair[2],
pair.toString()
);
});
});
// when in type defined BaseModel, and in row we have ChildModel (extends BaseModel),
// then clone should be instance of ChildModel
it("clone array of models, should return array of instance of Child", () => {
class FirstLevel extends Model<FirstLevel> {
structure() {
return {
};
}
}
class SecondLevel extends FirstLevel {
structure() {
return {
level: Types.Number({
default: 2
})
};
}
}
class MainModel extends Model<MainModel> {
structure() {
return {
arr: Types.Array({
element: FirstLevel
})
};
}
}
const second = new SecondLevel();
const main = new MainModel({
arr: [second]
});
assert.deepEqual(
main.toJSON(),
{
arr: [{
level: 2
}]
}
);
const clone = main.clone();
assert.ok( clone.get("arr")![0] instanceof SecondLevel );
});
it("conflicting parameters: nullAsZero and zeroAsNull", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
arr: Types.Array({
element: Types.Number,
emptyAsNull: true,
nullAsEmpty: true
})
};
}
}
assert.throws(
() => {
const model = new SomeModel();
},
(err: Error) =>
err.message === "arr: conflicting parameters: use only nullAsEmpty or only emptyAsNull"
);
});
it("circular structure to json", () => {
class MyModel extends Model<MyModel> {
structure() {
return {
arr: Types.Array({
element: Types.Array({
element: Types.Array
})
})
};
}
}
const arr: any = [];
arr[0] = arr;
const model = new MyModel({
arr
});
assert.throws(
() => {
model.toJSON();
},
(err: Error) =>
err.message === "Cannot converting circular structure to JSON"
);
});
it("same array as value in two fields", () => {
class TestModel extends Model<TestModel> {
structure() {
return {
a: Types.Array({
element: Types.String
}),
b: Types.Array({
element: Types.String
})
};
}
}
const arr = ["test"];
const test = new TestModel({
a: arr,
b: arr
});
assert.deepStrictEqual(test.toJSON(), {
a: ["test"],
b: ["test"]
});
});
it("same array as value in two elements of array", () => {
class TestModel extends Model<TestModel> {
structure() {
return {
a: Types.Array({
element: Types.Array({
element: Types.String
})
})
};
}
}
const arrFirst = ["test"];
const arr = [arrFirst, arrFirst];
const test = new TestModel({
a: arr
});
assert.deepStrictEqual(test.toJSON(), {
a: [["test"], ["test"]]
});
});
it("same model as value in two elements of array", () => {
class TestModel extends Model<TestModel> {
structure(): {
arr: IArrayType<TestModel>;
} {
return {
arr: Types.Array({
element: TestModel
})
};
}
}
const child = new TestModel();
const test = new TestModel({
arr: [child, child]
});
assert.deepStrictEqual(test.toJSON(), {
arr: [{arr: null}, {arr: null}]
});
});
it("circular structure to json", () => {
class TestModel extends Model<TestModel> {
structure(): {
arr: IArrayType<TestModel>
} {
return {
arr: Types.Array({
element: TestModel
})
};
}
}
const model = new TestModel();
model.set({
arr: [model]
});
assert.throws(
() => {
model.toJSON();
},
(err: Error) =>
err.message === "Cannot converting circular structure to JSON"
);
});
it("array of models, check parent reference", () => {
class TestModel extends Model<TestModel> {
structure(): {
name: IStringType;
children: IArrayType<TestModel>;
} {
return {
name: Types.String,
children: Types.Array({
element: TestModel
})
};
}
}
const parent = new TestModel({
name: "parent",
children: [
new TestModel({name: "a"}),
new TestModel({name: "b"})
]
});
const a = parent.get("children")![0] as TestModel;
const b = parent.get("children")![1] as TestModel;
const aParent = a.findParentInstance(TestModel);
const bParent = b.findParentInstance(TestModel);
assert.ok(aParent === parent, "a parent is valid model");
assert.ok(bParent === parent, "b parent is valid model");
});
it("clone array of models and check parent reference", () => {
class TestModel extends Model<TestModel> {
structure(): {
name: IStringType;
children: IArrayType<TestModel>
} {
return {
name: Types.String,
children: Types.Array({
element: TestModel
})
};
}
}
const parent = new TestModel({
name: "parent",
children: [
new TestModel({name: "a"}),
new TestModel({name: "b"})
]
});
const parentClone = parent.clone();
const aClone = parentClone.get("children")![0] as TestModel;
const bClone = parentClone.get("children")![1] as TestModel;
const aCloneParent = aClone.findParentInstance(TestModel);
const bCloneParent = bClone.findParentInstance(TestModel);
assert.ok(aCloneParent === parentClone, "a clone parent is valid model");
assert.ok(bCloneParent === parentClone, "b clone parent is valid model");
});
});