Synthesize

property with getter, setter, constant, default, type, methods, and operators

Fork me on GitHub

This test documentation is produced using the mocha testing framework.

The development of this project is ongoing. The desired features are not fully implemented yet.

Basic property

should be defined by synthesize() or synthesize({})
person = {};
expect(person.name = synthesize()).to.be.a('function');
expect(person.age = synthesize({})).to.be.a('function');
should get the default value: undefined
expect(person.name()).to.equal(undefined);
expect(person.age()).to.equal(undefined);
should get the identical value as been set
var johnName = { first: 'John', last: 'Smith' };

person.name('John').age(30);
expect(person.name('John')).to.equal(person);
expect(person.age(30)).to.equal(person);
person.name(johnName);
expect(person.name()).to.equal(johnName);

Instance variable

should be defined by synthesize(_var)
expect(person.sex = synthesize('_sex')).to.be.a('function');
expect(person.sex()).to.equal(undefined);
expect(person._sex).to.equal(undefined);
person.sex('male');

// The synthesized property and the 'instance variable'.
expect(person.sex()).to.equal('male');
expect(person._sex).to.equal('male');
should have varName _var starting with an underscore _
person.sex = synthesize('_sex');
expect(function () {
    person.sex = synthesize('sex');
}).to.throw(Error);
expect(function () {
    person.sex = synthesize('@sex');
}).to.throw(Error);
should not have varName to be '_[0-9]+'
expect(function () {
    synthesize('_1');
}).to.throw(Error);
expect(function () {
    synthesize('_25');
}).to.throw(Error);

Default value

should be be defined by synthesize({ default: value })
defaultParents = { father: 'papa', mother: 'mama' };
defaultCourses = ['JavaScript', 'Python', 'Ruby'];
student = {
    name: synthesize({ default: 'unknown' }),
    age: synthesize({ default: 15 }),
    isStudent: synthesize({ default: true }),
    parents: synthesize({ default: defaultParents }),
    courses: synthesize({ default: defaultCourses })
};
should not be a constructed object or inherit object
// OK but not recommended, use object literal instead.
student.birthday = synthesize({ default: new Object() });

// Not OK; default value today is constructed by Date.
// Use default with type instead.
expect(function () {
    var today = new Date('2014-8-22');
    student.birthday = synthesize({ default: today });
}).to.throw(Error);

// OK
student.birthday = synthesize({ default: Object.create(null) });
// Not OK
expect(function () {
    synthesize({ default: Object.create({}) });
}).to.throw(Error);

// Default value specialCourses inherit defaultCourse
expect(function () {
    var specialCourses = Object.create(defaultCourses);
    student.courses = synthesize({ default: specialCourses });
}).to.throw(Error);
should be gotten by default correctly
expect(student.name()).to.equal('unknown');
expect(student.age()).to.equal(15);
expect(student.isStudent()).to.equal(true);
should be overridden by the setter
student.name('Joe').age(5).isStudent(false).parents(null).courses(null);
expect(student.name()).to.equal('Joe');
expect(student.age()).to.equal(5);
expect(student.isStudent()).to.equal(false);
expect(student.parents()).to.equal(null);
expect(student.courses()).to.equal(null);
should be reset by setting undefined to the property
student.name(undefined).age(undefined).isStudent(undefined)
       .parents(undefined).courses(undefined);
expect(student.name()).to.equal('unknown');
expect(student.age()).to.equal(15);
expect(student.isStudent()).to.equal(true);
expect(student.parents()).to.eql(defaultParents);
expect(student.courses()).to.eql(defaultCourses);
expect(student.parents()).to.not.equal(defaultParents);
expect(student.courses()).to.not.equal(defaultCourses);
should be gotten as a cloned value for an object or an array
expect(student.parents()).to.eql(defaultParents);
expect(student.courses()).to.eql(defaultCourses);
expect(student.parents()).to.not.equal(defaultParents);
expect(student.courses()).to.not.equal(defaultCourses);

Constant property

General

should be defined by synthesize({ constant: c })
synthesize({ constant: 1 });
should not be defined together with any other option
expect(function () {
    synthesize({ constant: 1, type: 'number' });
}).to.throw(Error);
should have setter triggering typeError and return self
var spy = sinon.spy();
person = {
    mode: synthesize({ constant: 'super' }),
    type: synthesize({ constant: undefined }),
    immortal: synthesize({ constant: false })
};
synths.on('typeError', spy);
expect(person.mode('superb')).to.equal(person);
expect(spy.calledOnce).is.equal(true);
should have getter always returning the constant
expect(person.mode()).to.equal('super');
expect(person.type()).to.equal(undefined);

// Setting the constant will not change the value.
person.mode('superb').type('superman').immortal(true);
expect(person.mode()).to.equal('super');
expect(person.type()).to.equal(undefined);
expect(person.immortal()).to.equal(false);
should be the same function for the same constant
var a = synthesize({ constant: 5 }),
    b = synthesize({ constant: 5 });
expect(a).is.a('function');
expect(a).to.equal(b);

constant undefined

should be defined shortly by synthesize(undefined) or synthesize('undefined')
// It can thought of as a constant undefined or a type 'undefined'
var person = {
    mode: synthesize(undefined),
    type: synthesize('undefined'),
    age: synthesize({ constant: undefined })
};

// The above three are just the same property.
expect(person.mode).is.a('function');
expect(person.mode).is.equal(person.type);
expect(person.mode).is.equal(person.age);

constant null

should be defined shortly by synthesize(null) or synthesize('null')
// It can thought of as a constant null or a type 'null'
var person = {
    mode: synthesize(null),
    type: synthesize('null'),
    age: synthesize({ constant: null })
};

// The above three are just the same property.
expect(person.mode).is.a('function');
expect(person.mode).is.equal(person.type);
expect(person.mode).is.equal(person.age);

Custom property

should be defined by synthesize({ get: getter, set: setter })
john = {
    firstName: synthesize(),
    lastName: synthesize(),

    // Synthesize custom property
    fullName: synthesize({
        get: function () {
            return this.firstName() + ' ' + this.lastName();
        },
        set: function (name) {
            var names = name.split(' ');
            this.firstName(names[0]);
            this.lastName(names[1]);
        }
    })
};
should work correctly
john.firstName('John').lastName('Smith');

// Custom getter
expect(john.fullName()).to.equal('John Smith');

// Custom setter
expect(john.fullName('J S')).to.equal(john);
expect(john.firstName()).to.equal('J');
expect(john.lastName()).to.equal('S');

Read only property

should be defined by synthesize({ get: getter }) @param {Function} getter
Person = function () {};
Person.prototype.firstName = synthesize();
Person.prototype.lastName = synthesize();

// Synthesize read-only property
Person.prototype.fullName = synthesize({
    get: function () {
        return this.firstName() + ' ' + this.lastName();
    }
});
john = new Person();
mary = new Person();

expect(function () {
    synthesize({ get: 1 });
}).to.throw(TypeError);
should also be defined by synthesize({ get: _var }) @param {String} _var
expect(function () {
    Person.prototype.mentor = synthesize({ get: '_mentor' });
}).to.not.throw(Error).and.throw(TypeError);
should have the name _var starting with an underscore _
expect(function () {
    Person.father = synthesize({ get: 'pa' });
}).to.throw(Error);
should get the correct value from the custom getter function
john.firstName('John').lastName('Smith');
expect(john.fullName()).to.equal('John Smith');
should get the correct value from obj[_var]
expect(john.mentor()).to.equal(undefined);
john._mentor = mary;
expect(john.mentor()).to.equal(mary);
should have setter triggering a typeError and returning self this
var spy = sinon.spy();
synths.on('typeError', spy);
expect(john.fullName('Tom')).to.equal(john);
expect(john.fullName()).to.equal('John Smith');
john.mentor(john);
expect(john.mentor()).to.equal(mary);
expect(spy.calledTwice).is.equal(true);

Write only property

should be defined by synthesize({ set: setter }) @param {Function} setter
Person = function () {};
_.extend(Person.prototype, {
    firstName: synthesize(),
    lastName: synthesize(),

    // Synthesize write-only property
    fullName: synthesize({
        set: function (value) {
            var names = ('' + value).split(' ');
            this.firstName(names[0]);
            this.lastName(names[1]);
        }
    })
});
john = new Person();
expect(function () {
    synthesize({ set: 1 });
}).to.throw(TypeError);
should also be defined by synthesize({ set: _var }) @param {String} _var
Person.prototype.weight = synthesize({ set: '_www' });
should have the name _var starting with underscore _
expect(function () {
    Person.father = synthesize({ set: 'pa' });
}).to.throw(Error);
should have the custom getter function working correctly
john.fullName('John Smith');
expect(john.firstName()).to.equal('John');
expect(john.lastName()).to.equal('Smith');
should set the correct value to obj[_var]
expect(john.weight()).to.equal(undefined);
john.weight(90);
expect(john.weight()).to.equal(undefined);
expect(john._www).to.equal(90);
should have getter triggering a typeError and returning undefined
var spy = sinon.spy();
synths.on('typeError', spy);
expect(john.fullName()).to.equal(undefined);
expect(john.weight()).to.equal(undefined);
expect(spy.calledTwice).is.equal(true);

synthesize(options)

options

should be nothing, null, undefined or {Object} options
synthesize();
synthesize(null);
synthesize(undefined);
synthesize({
    // valid options
});
should also be a valid {String} typeName or a valid {String} _varName
synthesize('string');       // a valid type name
synthesize('_aName');       // a valid instance variable name, starting with '_'
should also be a {Function} constructor
synthesize(function () {});
synthesize(Date);
synthesize(RegExp);
should be forbidden for anything else
expect(function () { synthesize(null, null); }).to.throw(Error);
expect(function () { synthesize([]); }).to.throw(Error);
expect(function () { synthesize(new Date()); }).to.throw(Error);
expect(function () { synthesize(true); }).to.throw(Error);
expect(function () { synthesize(false); }).to.throw(Error);

returned value

should be a synthesized property, which is a {Function} method for object
expect(synthesize()).to.be.a('function');
expect(synthesize(null)).to.be.a('function');
expect(synthesize(undefined)).to.be.a('function');
expect(synthesize({})).to.be.a('function');

Synthesized property

Setter

is invoked with a parameter obj.prop(val), and should return the object itself this
person = {
    name: synthesize(),
    age: synthesize(),
    type: synthesize(null),
    mode: synthesize(undefined)
};
expect(person.name('John')).to.equal(person);
should therefore be always chainable
expect(person.name('Tom').age(25).type('what').mode('ever')).to.equal(person);

Getter

is invoked without any parameter, obj.prop(), and should return the correct value
var person = {
    name: synthesize(),
    age: synthesize(),
    type: synthesize(null),
    mode: synthesize(undefined)
};
person.name('Tom').age(25);
expect(person.name()).to.equal('Tom');
expect(person.age()).to.equal(25);
person.name(undefined).age(null);
expect(person.name()).to.equal(undefined);
expect(person.age()).to.equal(null);

Type checking

Error reporting

should be by default doing nothing
person = { type: synthesize(null) };
synths.trigger('typeError');
person.type(1);                         // write to a constant
should work by overriding synths.trigger
// Throw an error
synths.trigger = function (name, message) {
    throw new Error(name + ': ' + message);
};
expect(function () { person.type(1); }).to.throw(Error);

// Mixin Backbone.Events
_.extend(synths, Events);

var spy = sinon.spy();
synths.on('typeError', spy);
synths.trigger('typeError');
person.type(1);                         // write to a constant
expect(spy.calledTwice).is.equal(true);

Property's property

should be invoked by obj.prop('subProp', [value])
// x and y are both synthesized properties of the from and the to property
var Vector = function (x, y) { this.x(x); this.y(y); };
Vector.prototype.x = synthesize();
Vector.prototype.y = synthesize();

var line = {
    from: synthesize(),
    to: synthesize()
};
line.from(new Vector(1, 2)).to(new Vector(3, 4));

// Getter with key
expect(line.from('x')).to.equal(1);
expect(line.from('y')).to.equal(2);
expect(line.to('x')).to.equal(3);
expect(line.to('y')).to.equal(4);

// Setter with key, value; setters are chainable.
line.from('x', 5).from('y', 6).to('x', 7).to('y', 8);

expect(line.from('x')).to.equal(5);
expect(line.from('y')).to.equal(6);
expect(line.to('x')).to.equal(7);
expect(line.to('y')).to.equal(8);

Array property's index and value

should be invoked by obj.prop(index, [value])
var family = {
    members: synthesize()
};
family.members(['John', 'Mary', 'Tom', 'Sue']);

// The original way
expect(family.members()[0]).to.equal('John');
family.members()[0] = 'Joey';
expect(family.members()[0]).to.equal('Joey');

// Getter with index
expect(family.members(1)).to.equal('Mary');

// Setter with index, value; setters are chainable.
family.members(1, 'Amy').members(2, 'Tommy');

expect(family.members(1)).to.equal('Amy');
expect(family.members(2)).to.equal('Tommy');
expect(family.members()).to.eql(['Joey', 'Amy', 'Tommy', 'Sue']);

// Negative index
expect(family.members(-1)).to.equal('Sue');
expect(family.members(-2)).to.equal('Tommy');

// Floating point index
expect(family.members(-2.8)).to.equal('Tommy');
expect(family.members('-2.3')).to.equal('Tommy');
expect(family.members(2.8)).to.equal('Tommy');
expect(family.members('2.3')).to.equal('Tommy');

Object property's key and value

should be invoked by obj.prop('key', [value])
var john = {
    parents: synthesize()
};
john.parents({
    father: 'Tom',
    mother: 'Sue',
});
expect(john.parents('father')).to.equal('Tom');
expect(john.parents('mother')).to.equal('Sue');

Method of property

should be invoked by obj.prop('#method')
john = {
    weight: synthesize(),
    birthday: synthesize(),
    courses: synthesize()
};
john.weight(50).birthday(new Date('2010-6-6'))
    .courses(['JavaScript']);

expect(john.weight()).to.equal(50);

// Method of Number
expect(john.weight('#toString')).to.equal('50');
// Alias name of the the method
expect(john.weight('#to string')).to.equal('50');
expect(john.weight('#to-string')).to.equal('50');
expect(john.weight('#to_string')).to.equal('50');

// Methods of Date
expect(john.birthday('#getFullYear')).to.equal(2010);
expect(john.birthday('#get full year')).to.equal(2010);
expect(john.birthday('#getMonth')).to.equal(5);
expect(john.birthday('#getDate')).to.equal(6);
john.birthday('#setDate', 7);
expect(john.birthday('#getDate')).to.equal(7);

// Methods of Array
john.courses('#push', 'Ruby');
expect(john.courses()).to.eql(['JavaScript', 'Ruby']);
should be assignment with an = suffix
// Method with an assignment operator
john.courses('#concat=', ['Python', 'Java']);
expect(john.courses()).to.eql(['JavaScript', 'Ruby', 'Python', 'Java']);

// Methods of lodash
john.courses('#filter=', function (course) {
    return course.charAt(0) === 'J';
}).courses('#reject=', function (course) {
    return course.length === 4;
});
expect(john.courses()).to.eql(['JavaScript']);

// Escape the '#'
expect(john.birthday('##').courses('##text')).to.equal(john);
expect(john.birthday()).to.equal('#');
expect(john.courses()).to.equal('#text');

Operator of property

should have binary operator shortcut
var john = {
    name: synthesize(),
    age: synthesize()
};
john.name('Jo').age(10);

john.name('+=', 'hn').age('+=', 5);
expect(john.name()).to.equal('John');

john.name('r+=', '. ', 'Mr').name('+=', ' ', 'Smi', 'th');
expect(john.name()).to.equal('Mr. John Smith');

john.age('-=', 5, 5).age('**=', 2);
expect(john.age()).to.equal(25);
should be overloaded by custom operator
// x and y are both synthesized properties of the from and the to property
var Vector = function (x, y) { this.x(x); this.y(y); };

// Static method
Vector['-'] = function (v1, v2) {
    return new Vector(v1.x() - v2.x(), v1.y() - v2.y());
};
_.extend(Vector.prototype, {
    x: synthesize(),
    y: synthesize(),

    // Dynamic method
    '+': function (v) {
        return new Vector(this.x() + v.x(), this.y() + v.y());
    },
});

var v1 = new Vector(5, 10),
    v2 = new Vector(15, 20),
    v3;

// Dynamic method
v3 = v1['+'](v2);
expect(v3.x()).to.equal(20);
expect(v3.y()).to.equal(30);

// Static method
v3 = Vector['-'](v1, v2);
expect(v3.x()).to.equal(-10);
expect(v3.y()).to.equal(-10);

var line = {
    from: synthesize(),
    to: synthesize(Vector)  // type needed for the static method
};
line.from(new Vector(1, 2)).to('#new', 3, 4);

// Property's operator is overloaded.
// Dynamic method
v3 = line.from('+', v1);
expect(v3.x()).to.equal(6);
expect(v3.y()).to.equal(12);

// Static method
v3 = line.to('-', v1);
expect(v3.x()).to.equal(-2);
expect(v3.y()).to.equal(-6);

// Assignment operator
line.from('+=', v1, v2).to('-=', v1, v2);
expect(line.from('x')).to.equal(21);
expect(line.from('y')).to.equal(32);
expect(line.to('x')).to.equal(-17);
expect(line.to('y')).to.equal(-26);

Nested property, method and operator

should be invoked by such as obj.prop(subprop, key, index, ...)
var john = {
    relatives: synthesize()
};

// Normal setter
john.relatives({
    sisters: [
        {
            name: 'Mary',
            age: synthesize()
        },
        {
            name: 'Jane',
            birthday: synthesize()
        }
    ],
    children: synthesize(),
});

// Object (key, value) setter
john.relatives('children', {
    girl: 'Amy',
    boy: 'Tommy'
});

// Nested getters
expect(john.relatives('sisters', 0, 'name')).to.equal('Mary');
expect(john.relatives('sisters', 1, 'name')).to.equal('Jane');
expect(john.relatives('children', 'girl')).to.equal('Amy');
expect(john.relatives('children', 'boy')).to.equal('Tommy');

// Nested setters
john.relatives('sisters', 0, 'name', 'M').relatives('sisters', 1, 'name', 'J')
    .relatives('children', 'girl', 'A').relatives('children', 'boy', 'T')
    .relatives('sisters', 0, 'age', 15)
    .relatives('sisters', 1, 'birthday', new Date('2013-8-25'));

// Nested getters
expect(john.relatives('sisters', 0, 'name')).to.equal('M');
expect(john.relatives('sisters', 1, 'name')).to.equal('J');
expect(john.relatives('sisters', 0, 'age')).to.equal(15);
expect(john.relatives('children', 'girl')).to.equal('A');
expect(john.relatives('children', 'boy')).to.equal('T');

// Nested getters and methods
expect(john.relatives('sisters', 0, 'age', '#toString')).to.equal('15');
expect(john.relatives('sisters', 1, 'birthday', '#getDate')).to.equal(25);
expect(john.relatives('sisters', 0, 'age', '#to string')).to.equal('15');
expect(john.relatives('sisters', 1, 'birthday', '#get date')).to.equal(25);

john.courses = synthesize();
john.courses([{
    name: 'JS',
    book: synthesize(),
    credit: synthesize()
}]);
john.courses(0, 'book', 'JavaScript');
expect(john.courses(0, 'book')).to.equal('JavaScript');
john.courses(0, 'book', 'r+=', 'Eloquent ');
expect(john.courses(0, 'book')).to.equal('Eloquent JavaScript');
expect(john.courses(0, 'name')).to.equal('JS');
john.courses(0, 'credit', 3);
expect(john.courses(0, 'credit')).to.equal(3);
expect(john.courses(0, 'credit', '#to string')).to.equal('3');
john.courses(0, 'credit', '+=', 2);
expect(john.courses(0, 'credit')).to.equal(5);

john.wife = synthesize();
var mary = { husband: synthesize() };
john.wife(mary).wife('husband', john);
expect(mary.husband()).to.equal(john);
expect(mary.husband('wife', 'husband', 'courses', 0, 'name')).to.equal('JS');
expect(mary.husband('wife', 'husband', 'courses', 0, 'credit')).to.equal(5);
mary.husband('wife', 'husband', 'wife', 'husband', 'courses', 0, 'credit', '+=', 1);
expect(mary.husband('courses', 0, 'credit')).to.equal(6);

synthesize('toJSON')

Basic mode

should be defined by synthesize('toJSON')
Person = function (n) { this.fullName(n); };
_.extend(Person.prototype, {
    firstName: synthesize(),                        // basic property is readable
    lastName: synthesize(),                         // readable
    fullName: synthesize({                          // write only
        set: function (value) {
            var names = ('' + value).split(' ');
            this.firstName(names[0]);
            this.lastName(names[1]);
        }
    }),
    weight: synthesize({ set: '_www' }),            // write only
    type: synthesize({ constant: 1 }),              // constant is readable
    isHappy: synthesize({                           // read only
        get: function () {
            return this._www < 85 && this._www > 75;
        }
    }),
    wife: synthesize({ get: '_wife' }),             // Read only

    // Synthesize a toJSON method, which is not a synthesized property.
    toJSON: synthesize('toJSON')
});

john = new Person('John Smith');
john.weight(80);
john._wife = 'Mary';    // write to a read-only internally
expect(Person.prototype.toJSON).to.be.a('function');
should convert all readable synthesized properties to typical properties
var result = {
    firstName: 'John',
    lastName: 'Smith',
    type: 1,
    isHappy: true,
    wife: 'Mary'
};
expect(john.toJSON()).to.eql(result);

Key inclusive mode

should be defined by synthesize('toJSON', key1, key2, ...)
Person.prototype.toJSON = synthesize('toJSON', 'firstName', 'wife');
expect(Person.prototype.toJSON).to.be.a('function');
should get only included properties
var result = {
    firstName: 'John',
    wife: 'Mary'
};
expect(john.toJSON()).to.eql(result);
should trigger an error if that key is not a synthesized property
var spy = sinon.spy();
synths.on('error', spy);
john.toJSON();
expect(spy.called).to.equal(false);

Person.prototype.toJSON = synthesize('toJSON', 'firstName', 'wifi', 'haha');
expect(john.toJSON()).to.eql({ firstName: 'John' });
expect(spy.calledTwice).to.equal(true);

Key map mode

should be defined by synthesize('toJSON', { key1: prop1, key2: prop2, ... })
Person.prototype.toJSON = synthesize('toJSON', {
    name: 'firstName',
    love: 'wife'
});
expect(Person.prototype.toJSON).to.be.a('function');
should get the mapped properties
var result = {
    name: 'John',
    love: 'Mary'
};
expect(john.toJSON()).to.eql(result);
should trigger an error if that property is not a synthesized property
var spy = sinon.spy();
synths.on('error', spy);
john.toJSON();
expect(spy.called).to.equal(false);

Person.prototype.toJSON = synthesize('toJSON', {
    name: 'firstName',
    love: 'wifi',           // wifi is not in the prototype chain
    firstName: 'name'       // name is not in the prototype chain
});
expect(john.toJSON()).to.eql({ name: 'John' });
expect(spy.calledTwice).to.equal(true);

Key exclusive mode

should be defined by synthesize('toJSON', '-', key1, key2, ...)
Person.prototype.toJSON = synthesize('toJSON', '-', 'firstName', 'wife');
expect(Person.prototype.toJSON).to.be.a('function');
should get all readable properties other than excluded
var result = {
    lastName: 'Smith',
    type: 1,
    isHappy: true
};
expect(john.toJSON()).to.eql(result);

Constructor type

should be defined by synthesize(Constructor)
Vector = function (x, y) {
    this.x(x);
    this.y(y);
};
Vector.prototype.x = synthesize();
Vector.prototype.y = synthesize();

line = {
    from: synthesize(Vector),
    to: synthesize(Vector)
};
birth = { date: synthesize(Date) };
should have setter working as normal
var v1 = new Vector(1, 2),
    v2 = new Vector(3, 4);
line.from(v1).to(v2);
expect(line.from()).to.equal(v1);
expect(line.to()).to.equal(v2);
should have new method to invoke the constructor
line.from('#new', 5, 6).to('#new', 7, 8);
birth.date('#new', 2013, 7, 17);
should have getter getting a correct type and value
// Type is referred to as an instance of the given constructor.
expect(line.from()).to.be.an.instanceof(Vector);
expect(line.to()).to.be.an.instanceof(Vector);
expect(birth.date()).to.be.an.instanceof(Date);
expect(birth.date()).to.be.an.instanceof(Object);

expect(line.from().x()).to.equal(5);
expect(line.from().y()).to.equal(6);
expect(line.to().x()).to.equal(7);
expect(line.to().y()).to.equal(8);
expect(birth.date().getFullYear()).to.equal(2013);
expect(birth.date().getMonth()).to.equal(7);
expect(birth.date().getDate()).to.equal(17);
expect(birth.date().getDay()).to.equal(6);
birth.date('#new', '2010-6-6');
expect(birth.date()).to.be.an.instanceof(Date);
expect(birth.date().getFullYear()).to.equal(2010);
expect(birth.date().getMonth()).to.equal(5);
expect(birth.date().getDate()).to.equal(6);

Name type

should be defined by a valid type name: synthesize('typeName')
person = {
    name: synthesize('string'),
    age: synthesize('integer'),
    weight: synthesize('number'),
    isStudent: synthesize('boolean'),
    parents: synthesize('object'),
    options: synthesize('plainObject'),
    courses: synthesize('array')
};
expect(function () {
    synthesize('unknownType');
}).to.throw(Error);
should have primitive types
person.name(5).age('20.9').weight('30.5').isStudent('abc');
expect(person.name()).to.equal('5');
expect(person.age()).to.equal(20);
expect(person.age('#to string')).to.equal('20');
expect(person.weight()).to.equal(30.5);
expect(person.isStudent()).to.equal(true);
person.isStudent(0);
expect(person.isStudent()).to.equal(false);
expect(person.isStudent('#to string')).to.equal('false');
should have plain object type
synthesize('plainObject');
synthesize('plain object');
should have array type
person.courses(['JS', 'PHP', 'JSP']);
expect(person.courses()).to.eql(['JS', 'PHP', 'JSP']);

// Array index and value
expect(person.courses(0)).to.equal('JS');
person.courses(0, 'JavaScript');
expect(person.courses()).to.eql(['JavaScript', 'PHP', 'JSP']);

// Lodash method
expect(person.courses('#filter', function (c) {
    return c.charAt(0) === 'J';
})).to.eql(['JavaScript', 'JSP']);

// Constructor
person.courses('#new', 'JS', 'C++', 'C#');
expect(person.courses()).to.eql(['JS', 'C++', 'C#']);
person.courses('#new', 123);
expect(person.courses()).to.eql([123]);

// Construct from object to array
person.courses({ a: 'JS', b: 'COFFEE' });
expect(person.courses()).to.eql(['JS', 'COFFEE']);

// Construct from string to array
person.courses('#new', 'abcde');
expect(person.courses()).to.eql(['a', 'b', 'c', 'd', 'e']);

// Array method
person.courses('#slice=', 1, -1);
expect(person.courses()).to.eql(['b', 'c', 'd']);
should have date type
var person = { birthday: synthesize('date') };
person.birthday('#new', '2011-1-2');
expect(person.birthday('date')).to.equal(2);
expect(person.birthday('month')).to.equal(1);
person.birthday('month', 3);
expect(person.birthday('month')).to.equal(3);