mirror of
https://github.com/jashkenas/backbone.git
synced 2026-01-23 22:08:04 -05:00
Wrapped up the model tests.
This commit is contained in:
125
backbone.js
125
backbone.js
@@ -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.
|
||||
|
||||
@@ -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.');
|
||||
});
|
||||
|
||||
});
|
||||
138
test/model.js
138
test/model.js
@@ -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));
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user