UNPKG

moko

Version:

Generator based models

652 lines (562 loc) 19.9 kB
/* jshint esnext: true, noyield: true*/ var expect = require('expect.js'), co = require('co'); var Moko = require('../'), User = new Moko('User'); User.attr('_id').attr('name'); describe('Moko Base Methods', function() { describe("Constructor", function() { var user; beforeEach(co(function *() { user = yield new User(); })); it('initializes attrs', function() { expect(user._attrs).to.be.an(Object); }); it('initializes dirty', function() { expect(user._dirty).to.be.an(Object); }); it('supports arrays', co(function *() { var users = yield new User([{name: 'Bobby'}, {name: 'Sam'}]); expect(users).to.be.a(Array); users.forEach(function() { expect(users[0]).to.be.a(User); expect(users[1]).to.be.a(User); }); })); it('supports setting attrs', co(function*() { var user = yield new User({name: 'Bob', age: 47 }); expect(user._attrs).to.have.property('name', 'Bob'); expect(user._attrs).to.not.have.property('age'); })); }); describe('Model.use', function() { it('passes the Moko to the plugin', function(done) { expect(User.use).to.be.a('function'); User.use(plugin); function plugin(Moko) { expect(Moko).to.be(User); done(); } }); it('allows for generator-based plugins', function(done) { co(function*() { yield User.use(function*() { done(); }); })(); }) }); describe('Model.attr', function() { before(function() { User.attr('name'); }); it('allows attrs to be defined multiple twice', co(function*() { var Alt = Moko('User'); Alt.attr('name', { test: 'a', another: 'b' }) .attr('name', { test: 'c' }); expect(Alt.attrs.name.another).to.be('b'); expect(Alt.attrs.name.test).to.be('c'); })); it('creates a getter', co(function*() { var user = yield new User({name: 'Bob'}); expect(user.name).to.be('Bob'); })); it('creates a setter', co(function*() { var user = yield new User(); user.name = 'Bob'; expect(user._attrs.name).to.be('Bob'); })); it('returns the Model', function() { var Person = new Moko('Person'); expect(Person.attr('name')).to.be(Person); }); it('updates dirty', co(function *() { var user = yield new User(); user.name = 'Bob'; expect(user._dirty).to.have.property('name', 'Bob'); })); it('sets primary', function() { var Person = new Moko('Person').attr('id'), Another = new Moko('Person').attr('_id'), Again = new Moko('Person').attr('random', { primary: true }); expect(Person._primary).to.be('id'); expect(Another._primary).to.be('_id'); expect(Again._primary).to.be('random'); }); it('emits the attr event', function(done) { User.on('attr', function(attr, opts) { expect(attr).to.be('test'); expect(opts).to.eql({a: 1}); done(); }); User.attr('test', {a: 1}); }); it('adds the attr to Model.attrs', function() { expect(User.attrs.name).to.eql({}); }); describe('events', function() { it('emits "change" on model', function(done) { co(function*() { var user = yield new User({name: 'Bob'}); User.on('change', function(instance, prop, val, old) { expect(instance).to.be(user); expect(prop).to.be('name'); expect(val).to.be('Marko'); expect(old).to.be('Bob'); User.removeAllListeners(); done(); }); user.name = 'Marko'; })(); }); it('emits "change" on instance', function(done) { co(function*() { var user = yield new User({name: 'Bob'}); user.on('change', function(prop, val, old) { expect(prop).to.be('name'); expect(val).to.be('Marko'); expect(old).to.be('Bob'); user.removeAllListeners(); done(); }); user.name = 'Marko'; })(); }); it('emits "change <attr>" on model', function(done) { co(function*() { var user = yield new User({name: 'Bob'}); User.on('change name', function(instance, val, old) { expect(instance).to.be(user); expect(val).to.be('Marko'); expect(old).to.be('Bob'); User.removeAllListeners(); done(); }); user.name = 'Marko'; })(); }); it('emits "change <attr>" on instance', function(done) { co(function*() { var user = yield new User({name: 'Bob'}); user.on('change name', function(val, old) { expect(val).to.be('Marko'); expect(old).to.be('Bob'); user.removeAllListeners(); done(); }); user.name = 'Marko'; })(); }); }); }); describe('Model.validate', function() { it('adds the validator to Model._validators', function() { var validator = function *() { }; User.validate(validator); expect(User._validators).to.have.length(1); expect(User._validators[0]).to.be(validator); User._validators = []; }); it('returns the Model', function() { var validator = function*() { }; expect(User.validate(validator)).to.be(User); }); }); describe('instance methods', function() { describe('#set', function() { it('sets multiple attributes', co(function*() { var user = yield new User(); user.set({name: 'bob'}); expect(user.name).to.be('bob'); })); it('ignores attrs not in the schema', co(function*() { var user = yield new User(); user.set({name: 'bob', age: 20}); expect(user.name).to.be('bob'); expect(user.age).to.be(undefined); })); }); describe('#error', function() { it('adds the error', co(function *() { var user = yield new User(); var result = user.error('name', 'cant be blank'); expect(result).to.be(user); expect(user._errors).to.have.property('name'); expect(user._errors.name).to.have.length(1); })); it('prevents duplicate errors from appearing twice', co(function *() { var user = yield new User(); user.error('name', 'cant be blank'); user.error('name', 'cant be blank'); expect(user._errors.name).to.have.length(1); })); }); describe('#errors', function() { it('returns all errors if no key specified', co(function *() { var user = yield new User(); user.error('name', 'cant be blank'); expect(user.errors()).to.have.key('name'); })); it('returns the errors array for the key', co(function *() { var user = yield new User(); user.error('name', 'cant be blank'); expect(user.errors('name')).to.eql(['cant be blank']); })); it('returns the a copy of the errors object', co(function *() { var user = yield new User(); user.error('name', 'cant be blank'); expect(user.errors('name')).to.not.be(user._errors['name']); })); it('returns an empty array if there are no errors', co(function *() { var user = yield new User(); expect(user.errors('name')).to.have.length(0); })); }); describe('#isValid', function() { it('emits the validators', co(function *() { var User = Moko('User'), user = yield new User(); var count = 0; User.validate(function *(u) { count++; expect(u).to.be(user); u.error('name', 'cant be blank'); }); yield user.isValid(); expect(count).to.be(1); })); it('returns true if no errors', co(function*() { var user = yield new User(); expect(yield user.isValid()).to.be(true); })); it('returns false if errors', co(function*() { var User = new Moko(); User.validate(function *() { user.error('name', 'dumb'); }); var user = yield new User(); expect(yield user.isValid()).to.be(false); })); it('supports lookup on a specific field', co(function *() { var User = new Moko('User'); User.validate(function *() { user.error('name', 'dumb'); }); var user = yield new User(); expect(yield user.isValid('brobob')).to.be(true); expect(yield user.isValid('name')).to.be(false); })); }); describe('#toJSON', function() { it('returns a JSON object', co(function*() { var user = yield new User({name: 'Tobi'}); var json = user.toJSON(); })); it('should clone, not reference attrs', co(function*() { var user = yield new User({name: 'Tobi'}); var json = user.toJSON(); json.name = 'Bob'; expect(user.name).to.be('Tobi'); })); it('works with null values', co(function*() { var user = yield new User({name: null}); var json = user.toJSON(); expect(json.name).to.be(null); })); it('is recursive', co(function*() { var name = { f: 'Bob', l: 'Harris' }; name.toJSON = function() { return { first: this.f, last: this.l }; }; var user = yield new User({ name: name }); expect(user.toJSON().name).to.have.property('first', name.f); expect(user.toJSON().name).to.have.property('last', name.l); })); }); describe('#primary', function() { it('throws an error if primary is not determined', co(function *() { var User = new Moko('User'); var user = yield new User(); expect(user.primary).to.throwError('primary key is not defined'); })); it('sets the primary', co(function *() { var user = yield new User(); user.primary(1); expect(user._attrs._id).to.be(1); })); it('returns the primary', co(function *() { var user = yield new User({_id: 1}); expect(user.primary()).to.be(1); })); }); describe('#isNew', function() { it('returns true if primary() is set', co(function *() { var user = yield new User(); expect(user.isNew()).to.be(true); })); it('returns false if primary() is not set', co(function *() { var user = yield new User({_id: 1}); expect(user.isNew()).to.be(false); })); }); describe('#remove', function() { var ctx; var Person = new Moko('Person').attr('_id').attr('name'); Person.remove = function*() { ctx = this; }; beforeEach(function() { ctx = undefined; }); it('throws an error if the sync layer has no remove', co(function*() { var user = yield new User(); try { yield user.remove(); throw new Error(); } catch(e) { expect(e.message).to.be('No sync layer provided'); } })); it('throws an error if the model is not saved', co(function*() { var user = yield new Person(); try { yield user.remove(); throw new Error(); } catch(e) { expect(e.message).to.be('not saved'); } })); it('calls the remove method', co(function*() { var person = yield new Person({_id: '1'}); yield person.remove(); expect(ctx).to.be(person); })); it('sets removed to true', co(function*() { var person = yield new Person({_id: '1'}); yield person.remove(); expect(person.removed).to.be(true); })); it('runs the removing middleware', co(function*() { var modelCalled, instanceCalled; var person = yield new Person({_id: '1'}); Person.once('removing', function*() { modelCalled = true; }); person.once('removing', function*() { instanceCalled = true; }); yield person.remove(); expect(modelCalled).to.be(true); expect(instanceCalled).to.be(true); })); it('emits the remove events', co(function*() { var modelCalled, instanceCalled; var person = yield new Person({_id: '1'}); Person.once('remove', function() { modelCalled = true; }); person.once('remove', function() { instanceCalled = true; }); yield person.remove(); expect(modelCalled).to.be(true); expect(instanceCalled).to.be(true); })); }); describe('#save', function() { var User; beforeEach(function () { User = new Moko('User').attr('_id'); }); it('throws an error if no sync layer', co(function*() { var Person = Moko('Person'); var user = yield new Person(); try { yield user.save(); expect(false).to.be(true); // shouldnt get here } catch(e) { expect(e.message).to.be("No sync layer provided"); } })); it('throws an error if the model is invalid', co(function*() { var Person = new Moko('Person'); Person.save = function *() {}; Person.update = function *() {}; var user = yield new Person(); Person.validate(function *(p) { p.error('name', 'bad name'); }); try { yield user.save(); expect(false).to.be(true); // shouldnt get here } catch(e) { expect(e.message).to.be("validation failed"); expect(e.errors).to.be.an(Object); } })); it('calls Model.save when valid and new', co(function *() { var called = false; User.save = function *() { called = true; }; User.update = function *() { }; var user = yield new User(); yield user.save(); expect(called).to.be(true); })); it('calls Model.update when valid and old', co(function *() { var called = false; var User = new Moko('User'); User.save = function *() {}; User.update = function *() { called = true; }; User.attr('_id'); var user = yield new User({_id: 1}); yield user.save(); expect(called).to.be(true); })); it('clears _dirty on success', co(function *() { var User = new Moko('User'); User.save = User.update = function *() {}; User.attr('_id').attr('name'); var user = yield new User({_id: 1}); user.name = 'Bob'; expect(user._dirty).to.have.property('name'); yield user.save(); expect(user._dirty).to.eql({}); })); it('allows skipping of validations', co(function *() { var called = false; var User = new Moko('User').attr('_id'); User.save = function *() { called = true; }; User.update = function *() {}; User.validate(function *(u) { u.error('name', 'invalid'); }); var user = yield new User(); yield user.save(true); })); describe('events', function() { var User, user, called, setCalled, args; beforeEach(co(function*() { args = undefined; User = new Moko('User').attr('_id'); user = yield new User(); called = false; setCalled = function() { called = true; args = Array.prototype.slice.call(arguments); }; checkCalled = function *() { args = Array.prototype.slice.call(arguments); called = true; }; User.save = function *() { }; User.update = function *() { }; })); it('emits the "saving" event on the model', co(function *() { User.on('saving', checkCalled); var dirty = user._dirty; yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(user); expect(args[1]).to.eql(dirty); })); it('emits the "saving" event on the instance', co(function *() { user.on('saving', checkCalled); var dirty = user._dirty; yield user.save(); expect(called).to.be(true); expect(args[0]).to.eql(dirty); })); it('emits the "save" event on the model', co(function*() { user.model.on('save', setCalled); yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(user); })); it('emits the "save" event on the instance', co(function*() { user.on('save', setCalled); yield user.save(); expect(called).to.be(true); })); it('emits the "updating" event on the model', co(function *() { User.on('updating', checkCalled); user = yield new User({_id: 1}); var dirty = user._dirty; yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(user); expect(args[1]).to.be(dirty); })); it('emits the "updating" event on the instance', co(function *() { user = yield new User({_id: 1}); var dirty = user._dirty; user.on('updating', checkCalled); yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(dirty); })); it('emits the "update" event on the model', co(function*() { user._attrs._id = 123; user.model.on('update', setCalled); yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(user); })); it('emits the "update" event on the instance', co(function*() { user._attrs._id = 123; user.on('update', setCalled); yield user.save(); expect(called).to.be(true); })); it('emits the "creating" event on the model', co(function *() { User.on('creating', checkCalled); var dirty = user._attrs; yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(user); expect(args[1]).to.be(dirty); })); it('emits the "creating" event on the instance', co(function *() { user.on('creating', checkCalled); var dirty = user._attrs; yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(dirty); })); it('emits the "create" event on the model', co(function*() { user.model.on('create', setCalled); yield user.save(); expect(called).to.be(true); expect(args[0]).to.be(user); })); it('emits the "create" event on the instance', co(function*() { user.on('create', setCalled); yield user.save(); expect(called).to.be(true); })); }); }); }); }); describe('Moko', function() { describe('misc events', function() { beforeEach(function() { User = new Moko('User').attr('name'); }); it('emits "initializing" event on model', co(function *() { var called; function *listener(instance, attrs) { expect(attrs).to.eql({name: 'Bob'}); attrs.name = 'Steve'; called = true; } User.on('initializing', listener); var user = yield new User({name: 'Bob'}); expect(called).to.be(true); expect(user._attrs.name).to.be('Steve'); })); it('emits "initialize" event on model', co(function *() { var called; function listener(instance) { expect(instance).to.be.an(User); called = true; } User.on('initialize', listener); var user = yield new User({name: 'Bob'}); expect(called).to.be(true); })); }); });