Home Reference Source Test

spec/objects/equal.spec.ts

import 'mocha';

import { given } from 'mocha-testdata';

import { TinyType, TinyTypeOf } from '../../src';
import { equal } from '../../src/objects';
import { expect } from '../expect';

/** @test {equal} */
describe('equal', () => {
    describe('when used with primitives', () => {

        given<any>(undefined, null, false, 'string', 42).
        it('is reflexive', (primitive: any) => {
            expect(equal(primitive, primitive)).to.be.true;                  // tslint:disable-line:no-unused-expression
        });

        given<{ v1: any, v2: any }>(
            { v1: false,    v2: false             },
            { v1: false,    v2: Boolean(false)    },
            { v1: 'string', v2: 'string'          },
            { v1: 'string', v2: String('string')  },
            { v1: 42,       v2: 42                },
            { v1: 42,       v2: Number(42)        },
            { v1: 42,       v2: 42              },
            { v1: 42,       v2: Number(42)      },
        ).
        it('is symmetric', ({ v1, v2 }) => {
            expect(equal(v1, v2)).to.be.true;                                // tslint:disable-line:no-unused-expression
            expect(equal(v1, v2)).to.equal(equal(v2, v1));
        });

        given<{ v1: any, v2: any, v3: any }>(
            { v1: false,    v2: false,    v3: false     },
            { v1: 'string', v2: 'string', v3: 'string'  },
            { v1: 42,       v2: 42,       v3: 42        },
        ).
        it('is transitive', ({ v1, v2, v3 }) => {
            expect(equal(v1, v2)).to.be.true;                               // tslint:disable-line:no-unused-expression
            expect(equal(v2, v3)).to.be.true;                               // tslint:disable-line:no-unused-expression
            expect(equal(v3, v1)).to.be.true;                               // tslint:disable-line:no-unused-expression
        });

        given<{ v1: any, v2: any }>(
            { v1: false,    v2: true       },
            { v1: 'apple',  v2: 'orange'   },
            { v1: 42,       v2: 24         },
            { v1: false,    v2: 'elephant' },
            { v1: null,     v2: undefined  },
        ).
        it('returns false when subjects are not equal', ({ v1, v2 }) => {
            expect(equal(v1, v2)).to.be.false;                               // tslint:disable-line:no-unused-expression
        });
    });

    /** @test {TinyType#equals} */
    describe('when used with TinyTypes', () => {

        class Name extends TinyTypeOf<string>() {}
        class Age  extends TinyTypeOf<number>() {}

        class Person extends TinyType {
            constructor(public readonly name: Name, public readonly role: Age) {
                super();
            }
        }

        // Please note that there's not much point in wrapping an array into a tiny type, as it doesn't provide
        // methods you'd expect from a list, such as map, reduce, forEach, etc.
        // What People is here to demonstrate is that `equals` works equally well (pun intended)
        // with tiny types with a member field of type Array.
        class People extends TinyTypeOf<Person[]>() {}

        const
            Alice   = new Name('Alice'),
            Bob     = new Name('Bob'),
            MsAlice = new Person(Alice, new Age(28)),
            MrBob   = new Person(Alice, new Age(38)),
            Team    = new People([MsAlice, MrBob]),
            Team2   = new People([MsAlice]);

        given<TinyType>(Alice, MsAlice, Team).
        it('is reflexive', (value: TinyType) => {
            expect(equal(value, value)).to.be.true;                          // tslint:disable-line:no-unused-expression
        });

        given<{ v1: TinyType, v2: TinyType }>(
            { v1: new Name('Alice'),    v2: new Name('Alice')   },
            { v1: new Age(28),          v2: new Age(28)         },
            { v1: Team,                 v2: Team                },
            { v1: new Person(Alice, new Age(28)), v2: new Person(Alice, new Age(28))  },
        ).
        it('is symmetric', ({ v1, v2 }) => {
            expect(equal(v1, v2)).to.be.true;                                // tslint:disable-line:no-unused-expression
            expect(equal(v1, v2)).to.equal(equal(v2, v1));
        });

        given<{ v1: TinyType, v2: TinyType, v3: TinyType }>(
            { v1: new Name('Alice'),    v2: new Name('Alice'),  v3: new Name('Alice') },
            { v1: new Age(28),          v2: new Age(28),        v3: new Age(28)       },
            { v1: new Person(Alice, new Age(28)), v2: new Person(Alice, new Age(28)), v3: new Person(Alice, new Age(28)) },
        ).
        it('is transitive', ({ v1, v2, v3 }) => {
            expect(equal(v1, v2)).to.be.true;                                // tslint:disable-line:no-unused-expression
            expect(equal(v2, v3)).to.be.true;                                // tslint:disable-line:no-unused-expression
            expect(equal(v3, v1)).to.be.true;                                // tslint:disable-line:no-unused-expression
        });

        given<{ v1: any, v2: any }>(
            { v1: Alice,    v2: null    },
            { v1: Alice,    v2: Bob     },
            { v1: Bob,      v2: 'cat'   },
            { v1: Alice,    v2: MsAlice },
            { v1: MsAlice,  v2: MrBob   },
            { v1: MrBob,    v2: Team    },
            { v1: Team,     v2: Team2   },
        ).
        it('returns false when subjects are not equal', ({ v1, v2 }) => {
            expect(equal(v1, v2)).to.be.false;                               // tslint:disable-line:no-unused-expression
        });

        it('compares public and private member fields', () => {
            class PrivatePerson extends TinyType {
                constructor(private readonly name: Name, private readonly age: Age) {
                    super();
                }
            }

            const
                PrivateAlice = new PrivatePerson(new Name('Alice'), new Age(28)),
                PrivateRyan  = new PrivatePerson(new Name('Ryan'), new Age(28));

            expect(PrivateAlice.equals(PrivateAlice)).to.be.true;            // tslint:disable-line:no-unused-expression
            expect(PrivateAlice.equals(PrivateRyan)).to.be.false;            // tslint:disable-line:no-unused-expression
        });
    });

    /** @test {TinyType#equals} */
    describe('when used with TinyTypes with optional fields', () => {

        class FirstName extends TinyTypeOf<string>() {}
        class LastName extends TinyTypeOf<string>() {}

        class Magician extends TinyType {
            constructor(
                public readonly firstName: FirstName,
                public readonly lastName?: LastName,
            ) {
                super();
            }
        }

        it('returns true when the non-optional fields are equal', () => {
            const
                t1 = new Magician(new FirstName('Teller')),
                t2 = new Magician(new FirstName('Teller'));

            expect(equal(t1, t2)).to.equal(true);
        });
    });

    /** @test {TinyType#equals} */
    describe('when used with Dates', () => {
        const
            dateInstance1 = new Date('2018-05-01T12:00:00.000Z'),
            dateInstance2 = new Date('2018-05-01T12:00:00.000Z'),
            dateInstance3 = new Date('2018-05-01T12:00:00.000Z'),
            differentDate = new Date('2042-05-01T12:15:30.000Z');

        it('is reflexive', () => {
            expect(equal(dateInstance1, dateInstance1)).to.be.true;          // tslint:disable-line:no-unused-expression
        });

        it('is symmetric', () => {
            expect(equal(dateInstance1, dateInstance2)).to.be.true;          // tslint:disable-line:no-unused-expression
            expect(equal(dateInstance2, dateInstance1)).to.equal(equal(dateInstance1, dateInstance2));
        });

        it('is transitive', () => {
            expect(equal(dateInstance1, dateInstance2)).to.be.true;          // tslint:disable-line:no-unused-expression
            expect(equal(dateInstance2, dateInstance3)).to.be.true;          // tslint:disable-line:no-unused-expression
            expect(equal(dateInstance3, dateInstance1)).to.be.true;          // tslint:disable-line:no-unused-expression
        });

        it('returns false when subjects are not equal', () => {
            expect(equal(dateInstance1, differentDate)).to.be.false;         // tslint:disable-line:no-unused-expression
        });
    });

    /** @test {TinyType#equals} */
    describe('when used with Arrays', () => {
        class Name extends TinyTypeOf<string>() {}

        it('returns false when arrays are of different length', () => {
            expect(equal(
                [ new Name('Alice'), new Name('Bob') ],
                [ new Name('Alice'), new Name('Bob'), new Name('Cyril') ],
            )).to.equal(false);
        });

        it('returns false when arrays contain different items', () => {
            expect(equal(
                [ new Name('Alice'), new Name('Bob'), new Name('Cyril')],
                [ new Name('Alice'), new Name('Bob'), new Name('Cynthia') ],
            )).to.equal(false);
        });

        it('returns true when both arrays contain equal items', () => {
            expect(equal(
                [ new Name('Alice'), new Name('Bob'), new Name('Cynthia') ],
                [ new Name('Alice'), new Name('Bob'), new Name('Cynthia') ],
            )).to.equal(true);
        });
    });
});