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 { 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 { 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 { 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 { 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 { 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 { 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 { structure() { return { value: Types.Or({ or: [Types.String, Types.Number] }) }; } } class NumberFirst extends Model { 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 { structure() { return { id: Types.Number, name: Types.String }; } } class MyModel extends Model { 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 { 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 { 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 { 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 { structure() { return { name: Types.String, bornDate: Types.Date }; } } class MyModel extends Model { 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 { 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 { 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 { 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 { 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 { 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 { structure() { return { id: Types.Number, name: Types.String }; } } class MyModel extends Model { 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" } }); }); });