Skip to content

Instantly share code, notes, and snippets.

@Filirom1
Created December 22, 2011 00:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Filirom1/1508370 to your computer and use it in GitHub Desktop.
Save Filirom1/1508370 to your computer and use it in GitHub Desktop.
Test Backbone Model and Collections with NodeJs and Vows. Isomorphic JS

How to test Backbone Model and Collections webapp using nodeJs and vows.

I used the Todos exemple of Backbone + RequireJS : https://github.com/addyosmani/todomvc/tree/master/todo-example/backbone+require

I added on each requireJs module an amdefine definition : https://github.com/jrburke/amdefine

In fact it's just one line to add before define([...], function(){...});

if (typeof define !== 'function') { var define = (require('amdefine'))(module); }

On Backbone Model and Collection I mock the sync method : model.sync = function(){}

And because the Collection use the localStorage, I had to mock it, via a global definition :

global.localStorage = {
  getItem: function(){},
  setItem: function(){}
};
if (typeof define !== 'function') { var define = (require('amdefine'))(module); }
define(['underscore', 'backbone'], function(_, Backbone){
// A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that.
// Generate four random hex digits.
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
// Generate a pseudo-GUID by concatenating random hexadecimal.
function guid() {
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
};
// Our Store is represented by a single JS object in *localStorage*. Create it
// with a meaningful name, like the name you'd give a table.
var Store = function(name) {
this.name = name;
var store = localStorage.getItem(this.name);
this.data = (store && JSON.parse(store)) || {};
};
_.extend(Store.prototype, {
// Save the current state of the **Store** to *localStorage*.
save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
},
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own.
create: function(model) {
if (!model.id) model.id = model.attributes.id = guid();
this.data[model.id] = model;
this.save();
return model;
},
// Update a model by replacing its copy in `this.data`.
update: function(model) {
this.data[model.id] = model;
this.save();
return model;
},
// Retrieve a model from `this.data` by id.
find: function(model) {
return this.data[model.id];
},
// Return the array of all models currently in storage.
findAll: function() {
return _.values(this.data);
},
// Delete a model from `this.data`, returning it.
destroy: function(model) {
delete this.data[model.id];
this.save();
return model;
}
});
// Override `Backbone.sync` to use delegate to the model or collection's
// *localStorage* property, which should be an instance of `Store`.
Backbone.sync = function(method, model, options) {
var resp;
var store = model.localStorage || model.collection.localStorage;
switch (method) {
case "read": resp = model.id ? store.find(model) : store.findAll(); break;
case "create": resp = store.create(model); break;
case "update": resp = store.update(model); break;
case "delete": resp = store.destroy(model); break;
}
if (resp) {
options.success(resp);
} else {
options.error("Record not found");
}
};
return Store;
});
global.localStorage = {
getItem: function(){},
setItem: function(){}
};
var vows = require('vows'),
assert = require('assert'),
Todo = require('../models/todo');
vows.describe('Test Backbone todo and Collections').addBatch({
'Given a todo': {
topic: function(){
var todo = new Todo();
todo.sync = function(){};
return todo;
},
'The default values must be set.': function(todo){
assert.equal(todo.get('content'), 'empty todo...');
assert.isFalse(todo.get('done'));
},
'When I toggle the todo': {
topic: function(todo){
todo.toggle();
return todo;
},
'Then the todo is done': function(todo){
assert.isTrue(todo.get('done'));
}
}
},
'Given a collection': {
topic: function(){
return require('../collections/todos');
},
'When I add a todo': {
topic: function(todos){
return todos.add(new Todo({content: 'vows tests'}));
},
teardown: function(todos){
todos.remove(todos.last());
},
'Then remaining todos must be 0': function(todos){
assert.equal(todos.done().length, 0);
},
'And when I toggle the todo': {
topic: function(todos){
todos.last().toggle();
return todos;
},
'Then remaining todos must 1': function(todos){
assert.equal(todos.done().length, 1);
}
}
},
'Given several ordered todos': {
topic: function(todos){
return todos.add([
{content: 'one', order: 1},
{content: 'three', order: 3},
{content: 'two', order: 2}
]);
},
teardown: function(todos){
todos.each(function(todo, index){
todos.remove(todo);
});
},
'When I list the todos': {
topic: function(todos){
return todos.map(function(todo){ return todo.get('content'); });
},
'Then the order is respected': function( contents){
assert.equal(contents[0], 'one');
assert.equal(contents[1], 'two');
assert.equal(contents[2], 'three');
}
}
}
}
}).export(module);
if (typeof define !== 'function') { var define = (require('amdefine'))(module); }
define(['underscore', 'backbone'], function(_, Backbone) {
var TodoModel = Backbone.Model.extend({
// Default attributes for the todo.
defaults: {
content: "empty todo...",
done: false
},
// Ensure that each todo created has `content`.
initialize: function() {
if (!this.get("content")) {
this.set({"content": this.defaults.content});
}
},
// Toggle the `done` state of this todo item.
toggle: function() {
this.save({done: !this.get("done")});
},
// Remove this Todo from *localStorage* and delete its view.
clear: function() {
this.destroy();
this.view.remove();
}
});
return TodoModel;
});
if (typeof define !== 'function') { var define = (require('amdefine'))(module); }
define([
'underscore',
'backbone',
'libs/backbone/localstorage',
'models/todo'
], function(_, Backbone, Store, Todo){
var TodosCollection = Backbone.Collection.extend({
// Reference to this collection's model.
model: Todo,
// Save all of the todo items under the `"todos"` namespace.
localStorage: new Store("todos"),
// Filter down the list of all todo items that are finished.
done: function() {
return this.filter(function(todo){ return todo.get('done'); });
},
// Filter down the list to only todo items that are still not finished.
remaining: function() {
return this.without.apply(this, this.done());
},
// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
},
// Todos are sorted by their original insertion order.
comparator: function(todo) {
return todo.get('order');
}
});
return new TodosCollection;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment