model-layer
Version:
798 lines (655 loc) • 20.9 kB
text/typescript
import {Model, Types} from "../../lib/index";
import assert from "assert";
import {invalidValuesAsString} from "../../lib/utils";
describe("OrType", () => {
it("constructor with string or number", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
id: Types.Or({
or: [Types.Number, Types.String]
})
};
}
}
const model1 = new SomeModel({
id: 1
});
const model2 = new SomeModel({
id: "b"
});
assert.strictEqual(model1.row.id, 1);
assert.strictEqual(model2.row.id, "b");
});
it("set string or number", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
id: Types.Or({
or: [Types.Number, Types.String]
})
};
}
}
const model1 = new SomeModel();
const model2 = new SomeModel();
assert.strictEqual(model1.row.id, null);
assert.strictEqual(model2.row.id, null);
model1.set({
id: 1
});
model2.set({
id: "b"
});
assert.strictEqual(model1.row.id, 1);
assert.strictEqual(model2.row.id, "b");
});
it("id is a number or string, but we set object", () => {
class SomeModel extends Model<SomeModel> {
structure() {
return {
id: Types.Or({
or: [Types.Number, Types.String]
})
};
}
}
assert.throws(
() => {
const model = new SomeModel({
id: {} as any
});
},
(err: Error) =>
err.message === "invalid value for types: number or string, for id: {}"
);
});
it("expected 'or' array of type descriptions", () => {
assert.throws(
() => {
class WrongModel extends Model<WrongModel> {
structure() {
return {
id: Types.Or({
} as any)
};
}
}
const model = new WrongModel();
},
(err: Error) =>
err.message === "id: required 'or' array of type descriptions"
);
});
it("invalid type description", () => {
assert.throws(
() => {
class WrongModel extends Model<WrongModel> {
structure() {
return {
id: Types.Or({
or: [{wrong: true}]
} as any)
};
}
}
const model = new WrongModel();
},
(err: Error) =>
err.message === "id: invalid type description or[0]: {\"wrong\":true}"
);
});
it("empty array of type description", () => {
assert.throws(
() => {
class WrongModel extends Model<WrongModel> {
structure() {
return {
id: Types.Or({
or: []
} as any)
};
}
}
const model = new WrongModel();
},
(err: Error) =>
err.message === "id: empty 'or' array of type descriptions"
);
});
it("prior type", () => {
class StringFirst extends Model<StringFirst> {
structure() {
return {
value: Types.Or({
or: [Types.String, Types.Number]
})
};
}
}
class NumberFirst extends Model<NumberFirst> {
structure() {
return {
value: Types.Or({
or: [Types.Number, Types.String]
})
};
}
}
const stringModel = new StringFirst({
value: "1"
});
const numberModel = new NumberFirst({
value: "1"
});
assert.strictEqual(stringModel.row.value, "1");
assert.strictEqual(numberModel.row.value, 1);
});
it("model to json", () => {
class User extends Model<User> {
structure() {
return {
id: Types.Number,
name: Types.String
};
}
}
class MyModel extends Model<MyModel> {
structure() {
return {
user: Types.Or({
or: [Types.Number, User]
})
};
}
}
const model = new MyModel({
user: {
id: 1,
name: "Bob"
}
});
assert.ok(model.row.user instanceof User);
assert.deepStrictEqual(model.toJSON(), {
user: {
id: 1,
name: "Bob"
}
});
model.set({
user: 2
});
assert.strictEqual(model.row.user, 2);
assert.deepStrictEqual(model.toJSON(), {
user: 2
});
});
it("array to json", () => {
class MyModel extends Model<MyModel> {
structure() {
return {
value: Types.Or({
or: [Types.Number, Types.Array({
element: Types.Number
})]
})
};
}
}
const model = new MyModel({
value: [1, 2]
});
assert.deepStrictEqual(model.row.value, [1, 2]);
const json = model.toJSON();
assert.deepStrictEqual(json, {
value: [1, 2]
});
assert.ok(json.value !== model.row.value);
model.set({
value: 3
});
assert.strictEqual(model.row.value, 3);
assert.deepStrictEqual(model.toJSON(), {
value: 3
});
});
it("date to json", () => {
const date = new Date();
const isoDate = date.toISOString();
class MyModel extends Model<MyModel> {
structure() {
return {
value: Types.Or({
or: [Types.Number, Types.Date]
})
};
}
}
const model = new MyModel({
value: date
});
assert.ok(model.row.value instanceof Date);
assert.strictEqual(+model.row.value!, +date);
assert.deepStrictEqual(model.toJSON(), {
value: isoDate
});
model.set({
value: 1
});
assert.strictEqual(model.row.value, 1);
assert.deepStrictEqual(model.toJSON(), {
value: 1
});
});
it("object to json", () => {
class MyModel extends Model<MyModel> {
structure() {
return {
value: Types.Or({
or: [Types.Number, Types.Object({
element: Types.Number
})]
})
};
}
}
const model = new MyModel({
value: {a: 1, b: 2}
});
assert.deepStrictEqual(model.row.value, {a: 1, b: 2});
const json = model.toJSON();
assert.deepStrictEqual(json, {
value: {a: 1, b: 2}
});
assert.ok(json.value !== model.row.value);
model.set({
value: 3
});
assert.strictEqual(model.row.value, 3);
assert.deepStrictEqual(model.toJSON(), {
value: 3
});
});
it("recursive to json", () => {
const date = new Date();
const isoDate = date.toISOString();
class User extends Model<User> {
structure() {
return {
name: Types.String,
bornDate: Types.Date
};
}
}
class MyModel extends Model<MyModel> {
structure() {
return {
value: Types.Or({
or: [Types.Number, Types.Array({
element: Types.Object({
element: User
})
})]
})
};
}
}
const model = new MyModel({
value: [{
user: {
name: "Bob",
bornDate: date
}
}]
});
const json = model.toJSON();
assert.deepStrictEqual(json, {
value: [{
user: {
name: "Bob",
bornDate: isoDate
}
}]
});
assert.ok(json.value !== model.row.value);
assert.ok((json as any).value[0] !== (model.row as any).value[0]);
});
it("circular structure to json, or: [Model]", () => {
class MyModel extends Model<MyModel> {
structure(): {
model: typeof MyModel;
} {
return {
model: Types.Or({
or: [MyModel]
})
};
}
}
const model = new MyModel({
model: {}
});
model.set({model});
assert.throws(
() => {
model.toJSON();
},
(err: Error) =>
err.message === "Cannot converting circular structure to JSON"
);
});
it("circular structure to json, or: [Array]", () => {
class MyModel extends Model<MyModel> {
structure() {
return {
value: Types.Or({
or: [
Types.Array,
Types.Object
]
})
};
}
}
const model = new MyModel();
// self model inside array
model.set({
value: [
model
]
});
assert.throws(
() => {
model.toJSON();
},
(err: Error) =>
err.message === "Cannot converting circular structure to JSON"
);
// circular array
const arr: any = [];
arr[0] = arr;
model.set({
value: arr
});
assert.throws(
() => {
model.toJSON();
},
(err: Error) =>
err.message === "Cannot converting circular structure to JSON"
);
});
it("circular structure to json, or: [Object]", () => {
class MyModel extends Model<MyModel> {
structure() {
return {
value: Types.Or({
or: [
Types.Array,
Types.Object
]
})
};
}
}
const model = new MyModel();
// self model inside array
model.set({
value: {model}
});
assert.throws(
() => {
model.toJSON();
},
(err: Error) =>
err.message === "Cannot converting circular structure to JSON"
);
// circular object
const obj: any = {};
obj.obj = obj;
model.set({
value: obj
});
assert.throws(
() => {
model.toJSON();
},
(err: Error) =>
err.message === "Cannot converting circular structure to JSON"
);
// but we can convert primitives to json
model.set({
value: [{
x: "",
a: 1,
b: true,
c: {
x: "",
a: 1,
b: true,
c: {
x: "",
a: 1,
b: true
}
}
}]
});
assert.deepStrictEqual(model.toJSON(), {
value: [{
x: "",
a: 1,
b: true,
c: {
x: "",
a: 1,
b: true,
c: {
x: "",
a: 1,
b: true
}
}
}]
});
});
it("equal any values", () => {
const now1 = new Date();
const now2 = new Date( +now1 );
const date2000 = new Date(2000);
class CustomClass {}
const obj1 = new CustomClass();
const obj2 = new CustomClass();
class SomeModel extends Model<SomeModel> {
structure() {
return {
"*": Types.Any
};
}
}
const emptyModel1 = new SomeModel();
const emptyModel2 = new SomeModel();
const sameModel1 = new SomeModel({
x: {a: 1}
});
const sameModel2 = new SomeModel({
x: {a: 1}
});
// circular object
const circularObj1: any = {};
circularObj1.self = circularObj1;
const circularObj2: any = {};
circularObj2.self = circularObj2;
const circularObj3: any = {x: 1};
circularObj3.self = circularObj3;
const circularObj4: any = {};
circularObj4.self = circularObj2;
// circular arr
const circularArr1: any = [];
circularArr1[0] = circularArr1;
const circularArr2: any = [];
circularArr2[0] = circularArr2;
const circularArr3: any = [];
circularArr3[0] = circularArr3;
circularArr3[1] = 0;
// circular model
const circularModel1 = new SomeModel();
circularModel1.set({self: circularModel1});
const circularModel2 = new SomeModel();
circularModel2.set({self: circularModel2});
const circularModel3 = new SomeModel({x: 1});
circularModel3.set({self: circularModel3});
const pairs = [
[null, null, true],
[undefined, undefined, true],
[null, false, false],
[false, false, true],
[false, 0, false],
[0, 0, true],
[null, "", false],
["", "", true],
["x", "x", true],
["1", 1, false],
["0", 0, false],
[null, Infinity, false],
[Infinity, Infinity, true],
[-Infinity, +Infinity, false],
[null, NaN, false],
[NaN, NaN, true],
[null, [], false],
[0, [], false],
[0, [0], false],
[[], [], true],
[[2], [2], true],
[[1], [2], false],
[[1, 2], [2], false],
[[[]], [[]], true],
[[[1]], [[1]], true],
[[[1]], [[2]], false],
[null, /xx/, false],
[/xx/, /xx/, true],
[/xx/g, /xx/i, false],
[/xx/, /xxx/, false],
[null, now1, false],
[now1, now1, true],
[now1, now2, true],
[now1, date2000, false],
[null, {}, false],
[{}, {}, true],
[{}, {x: 1}, false],
[{x: 1}, {x: 1}, true],
[{x: 1}, {x: 1, y: 2}, false],
[{x: {a: 1}}, {x: {a: 1}}, true],
[
[{x: {a: 1}}, 1],
[{x: {a: 1}}, 1],
true
],
[
[{x: {a: 1}}, 1],
[{x: {a: 1}}, true],
false
],
[null, obj1, false],
[obj1, obj1, true],
[obj1, obj2, false],
[null, emptyModel1, false],
[emptyModel1, emptyModel1, true],
[emptyModel1, emptyModel2, true],
[emptyModel1, sameModel1, false],
[sameModel1, sameModel1, true],
[sameModel1, sameModel2, true],
[circularObj1, circularObj1, true],
[circularObj1, circularObj2, true],
[circularObj2, circularObj2, true],
[circularObj1, circularObj3, false],
[circularObj2, circularObj3, false],
// [circularObj2, circularObj4, false],
[circularArr1, circularArr1, true],
[circularArr1, circularArr2, true],
[circularArr2, circularArr2, true],
[circularArr1, circularArr3, false],
[circularArr2, circularArr3, false],
[circularModel1, circularModel1, true],
[circularModel1, circularModel2, true],
[circularModel2, circularModel2, true],
[circularModel1, circularModel3, false],
[circularModel2, circularModel3, false],
[-0, +0, true]
];
pairs.forEach((pair, i) => {
class TestModel extends Model<TestModel> {
structure() {
return {
value: Types.Or({
or: [
// for Infinity, NaN, regExp, ...
Types.Any,
SomeModel
]
})
};
}
}
const model1 = new TestModel({
value: pair[0]
});
const model2 = new TestModel({
value: pair[1]
});
assert.strictEqual(
model1.equal( model2 ),
pair[2],
i + ": " + invalidValuesAsString(pair)
);
assert.strictEqual(
model2.equal( model1 ),
pair[2],
i + ": " + invalidValuesAsString(pair)
);
});
});
it("model.clone()", () => {
class User extends Model<User> {
structure() {
return {
id: Types.Number,
name: Types.String
};
}
}
class MyModel extends Model<MyModel> {
structure() {
return {
user: Types.Or({
or: [Types.Number, User]
})
};
}
}
const model = new MyModel({
user: {
id: 1,
name: "Bob"
}
});
assert.ok(model.row.user instanceof User);
const clone = model.clone();
assert.deepStrictEqual(clone.toJSON(), {
user: {
id: 1,
name: "Bob"
}
});
model.set({
user: 2
});
assert.strictEqual(model.row.user, 2);
assert.deepStrictEqual(clone.toJSON(), {
user: {
id: 1,
name: "Bob"
}
});
});
});