Wrapped up the model tests.

This commit is contained in:
Jeremy Ashkenas
2010-10-06 12:23:22 -04:00
parent 71969d367b
commit 16149c7c37
3 changed files with 202 additions and 129 deletions

View File

@@ -101,7 +101,7 @@
Backbone.Model = function(attributes) {
this._attributes = {};
attributes = attributes || {};
this.set(attributes, true);
this.set(attributes, {silent : true});
this.cid = _.uniqueId('c');
this._formerAttributes = this.attributes();
};
@@ -116,6 +116,26 @@
// Has the item been changed since the last `changed` event?
_changed : false,
// Return a copy of the model's `attributes` object.
attributes : function() {
return _.clone(this._attributes);
},
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url : function() {
var base = this.collection.url();
if (this.isNew()) return base;
return base + '/' + this.id;
},
// String representation of the model. Override this to provide a nice way
// to print models to the console.
toString : function() {
return 'Model ' + this.id;
},
// Create a new model with identical attributes to this one.
clone : function() {
return new (this.constructor)(this.attributes());
@@ -132,47 +152,9 @@
return !this.id;
},
// Call this method to fire manually fire a `changed` event for this model.
// Calling this will cause all objects observing the model to update.
changed : function() {
this.trigger('change', this);
this._formerAttributes = this.attributes();
this._changed = false;
},
// Determine if the model has changed since the last `changed` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged : function(attr) {
if (attr) return this._formerAttributes[attr] != this._attributes[attr];
return this._changed;
},
// Get the previous value of an attribute, recorded at the time the last
// `changed` event was fired.
formerValue : function(attr) {
if (!attr || !this._formerAttributes) return null;
return this._formerAttributes[attr];
},
// Get all of the attributes of the model at the time of the previous
// `changed` event.
formerAttributes : function() {
return this._formerAttributes;
},
// Return an object containing all the attributes that have changed, or false
// if there are no changed attributes. Useful for determining what parts of a
// view need to be updated and/or what attributes need to be persisted to
// the server.
changedAttributes : function(now) {
var old = this.formerAttributes(), now = now || this.attributes(), changed = false;
for (var attr in now) {
if (!_.isEqual(old[attr], now[attr])) {
changed = changed || {};
changed[attr] = now[attr];
}
}
return changed;
// Get the value of an attribute.
get : function(attr) {
return this._attributes[attr];
},
// Set a hash of model attributes on the object, firing `changed` unless you
@@ -201,40 +183,61 @@
return this;
},
// Get the value of an attribute.
get : function(attr) {
return this._attributes[attr];
},
// Remove an attribute from the model, firing `changed` unless you choose to
// silence it.
unset : function(attr, options) {
options || (options = {});
var value = this._attributes[attr];
delete this._attributes[attr];
if (!options.silent) this.changed();
if (!options.silent) {
this._changed = true;
this.trigger('change:' + attr);
this.changed();
}
return value;
},
// Return a copy of the model's attributes.
attributes : function() {
return _.clone(this._attributes);
// Call this method to fire manually fire a `changed` event for this model.
// Calling this will cause all objects observing the model to update.
changed : function() {
this.trigger('change', this);
this._formerAttributes = this.attributes();
this._changed = false;
},
// Bind all methods in the list to the model.
bindAll : function() {
_.bindAll.apply(_, [this].concat(arguments));
// Determine if the model has changed since the last `changed` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged : function(attr) {
if (attr) return this._formerAttributes[attr] != this._attributes[attr];
return this._changed;
},
toString : function() {
return 'Model ' + this.id;
// Return an object containing all the attributes that have changed, or false
// if there are no changed attributes. Useful for determining what parts of a
// view need to be updated and/or what attributes need to be persisted to
// the server.
changedAttributes : function(now) {
var old = this.formerAttributes(), now = now || this.attributes(), changed = false;
for (var attr in now) {
if (!_.isEqual(old[attr], now[attr])) {
changed = changed || {};
changed[attr] = now[attr];
}
}
return changed;
},
// The URL of the model's representation on the server.
url : function() {
var base = this.collection.url();
if (this.isNew()) return base;
return base + '/' + this.id;
// Get the previous value of an attribute, recorded at the time the last
// `changed` event was fired.
formerValue : function(attr) {
if (!attr || !this._formerAttributes) return null;
return this._formerAttributes[attr];
},
// Get all of the attributes of the model at the time of the previous
// `changed` event.
formerAttributes : function() {
return this._formerAttributes;
},
// Set a hash of model attributes, and sync the model to the server.

View File

@@ -2,41 +2,41 @@ $(document).ready(function() {
module("Backbone bindable");
test("bindable: bind and trigger", function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Bindable);
obj.bind('event', function() { obj.counter += 1; });
obj.trigger('event');
equals(obj.counter,1,'counter should be incremented.');
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
equals(obj.counter, 5, 'counter should be incremented five times.');
});
test("bindable: bind and trigger", function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Bindable);
obj.bind('event', function() { obj.counter += 1; });
obj.trigger('event');
equals(obj.counter,1,'counter should be incremented.');
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
equals(obj.counter, 5, 'counter should be incremented five times.');
});
test("bindable: bind, then unbind all functions", function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Bindable);
var callback = function() { obj.counter += 1; };
obj.bind('event', callback);
obj.trigger('event');
obj.unbind('event');
obj.trigger('event');
equals(obj.counter, 1, 'counter should have only been incremented once.');
});
test("bindable: bind, then unbind all functions", function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Bindable);
var callback = function() { obj.counter += 1; };
obj.bind('event', callback);
obj.trigger('event');
obj.unbind('event');
obj.trigger('event');
equals(obj.counter, 1, 'counter should have only been incremented once.');
});
test("bindable: bind two callbacks, unbind only one", function() {
var obj = { counterA: 0, counterB: 0 };
_.extend(obj,Backbone.Bindable);
var callback = function() { obj.counterA += 1; };
obj.bind('event', callback);
obj.bind('event', function() { obj.counterB += 1; });
obj.trigger('event');
obj.unbind('event', callback);
obj.trigger('event');
equals(obj.counterA, 1, 'counterA should have only been incremented once.');
equals(obj.counterB, 2, 'counterB should have been incremented twice.');
});
test("bindable: bind two callbacks, unbind only one", function() {
var obj = { counterA: 0, counterB: 0 };
_.extend(obj,Backbone.Bindable);
var callback = function() { obj.counterA += 1; };
obj.bind('event', callback);
obj.bind('event', function() { obj.counterB += 1; });
obj.trigger('event');
obj.unbind('event', callback);
obj.trigger('event');
equals(obj.counterA, 1, 'counterA should have only been incremented once.');
equals(obj.counterB, 2, 'counterB should have been incremented twice.');
});
});

View File

@@ -2,51 +2,121 @@ $(document).ready(function() {
module("Backbone model");
// Variable to catch the last request.
var lastRequest = null;
// Stub out Backbone.request...
Backbone.request = function() {
lastRequest = _.toArray(arguments);
};
var attrs = {
id : '1-the-tempest',
title : "The Tempest",
author : "Bill Shakespeare",
length : 123
};
var doc = new Backbone.Model(attrs);
var klass = Backbone.Collection.extend({
url : function() { return '/collection'; }
});
var collection = new klass();
collection.add(doc);
test("model: attributes", function() {
ok(doc.attributes() !== attrs, "Attributes are different objects.");
ok(_.isEqual(doc.attributes(), attrs), "but with identical contents.");
});
test("model: url", function() {
equals(doc.url(), '/collection/1-the-tempest');
});
test("model: toString", function() {
equals(doc.toString(), 'Model 1-the-tempest');
});
test("model: clone", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
b = a.clone();
equals(a.get('foo'), 1);
equals(a.get('bar'), 2);
equals(a.get('baz'), 3);
equals(b.get('foo'), a.get('foo'), "Foo should be the same on the clone.");
equals(b.get('bar'), a.get('bar'), "Bar should be the same on the clone.");
equals(b.get('baz'), a.get('baz'), "Baz should be the same on the clone.");
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
b = a.clone();
equals(a.get('foo'), 1);
equals(a.get('bar'), 2);
equals(a.get('baz'), 3);
equals(b.get('foo'), a.get('foo'), "Foo should be the same on the clone.");
equals(b.get('bar'), a.get('bar'), "Bar should be the same on the clone.");
equals(b.get('baz'), a.get('baz'), "Baz should be the same on the clone.");
a.set({foo : 100});
equals(a.get('foo'), 100);
equals(b.get('foo'), 1, "Changing a parent attribute does not change the clone.");
});
test("model: isEqual", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
b = new Backbone.Model(attrs);
ok(a.isEqual(b), "a should equal b");
c = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'qux': 4});
ok(!a.isEqual(c), "a should not equal c");
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
b = new Backbone.Model(attrs);
ok(a.isEqual(b), "a should equal b");
c = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'qux': 4});
ok(!a.isEqual(c), "a should not equal c");
});
test("model: isNew", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
ok(a.isNew(), "it should be new");
attrs = { 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 };
ok(a.isNew(), "any defined ID is legal, negative or positive");
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
ok(a.isNew(), "it should be new");
attrs = { 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 };
ok(a.isNew(), "any defined ID is legal, negative or positive");
});
test("model: set", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
var changeCount = 0;
a.bind("change", function() { changeCount += 1; });
a.set({'foo': 2});
ok(a.get('foo')== 2, "Foo should have changed.");
ok(changeCount == 1, "Change count should have incremented.");
a.set({'foo': 2}); // set with value that is not new shouldn't fire change event
ok(a.get('foo')== 2, "Foo should NOT have changed, still 2");
ok(changeCount == 1, "Change count should NOT have incremented.");
test("model: get", function() {
equals(doc.get('title'), 'The Tempest');
equals(doc.get('author'), 'Bill Shakespeare');
});
a.unset('foo');
ok(a.get('foo')== null, "Foo should have changed");
ok(changeCount == 2, "Change count should have incremented for unset.");
test("model: set and unset", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
var changeCount = 0;
a.bind("change:foo", function() { changeCount += 1; });
a.set({'foo': 2});
ok(a.get('foo')== 2, "Foo should have changed.");
ok(changeCount == 1, "Change count should have incremented.");
a.set({'foo': 2}); // set with value that is not new shouldn't fire change event
ok(a.get('foo')== 2, "Foo should NOT have changed, still 2");
ok(changeCount == 1, "Change count should NOT have incremented.");
a.unset('foo');
ok(a.get('foo')== null, "Foo should have changed");
ok(changeCount == 2, "Change count should have incremented for unset.");
});
test("model: changed, hasChanged, changedAttributes, formerValue, formerAttributes", function() {
var model = new Backbone.Model({name : "Tim", age : 10});
model.bind('change', function() {
ok(model.hasChanged('name'), 'name changed');
ok(!model.hasChanged('age'), 'age did not');
ok(_.isEqual(model.changedAttributes(), {name : 'Rob'}), 'changedAttributes returns the changed attrs');
equals(model.formerValue('name'), 'Tim');
ok(_.isEqual(model.formerAttributes(), {name : "Tim", age : 10}), 'formerAttributes is correct');
});
model.set({name : 'Rob'}, {silent : true});
model.changed();
equals(model.get('name'), 'Rob');
});
test("model: save", function() {
doc.save({title : "Henry V"});
equals(lastRequest[0], 'PUT');
ok(_.isEqual(lastRequest[1], doc));
});
test("model: destroy", function() {
doc.destroy();
equals(lastRequest[0], 'DELETE');
ok(_.isEqual(lastRequest[1], doc));
});
});