diff --git a/client/spa/js/account/account.controller.js b/client/spa/js/account/account.controller.js new file mode 100644 index 0000000..3855b1a --- /dev/null +++ b/client/spa/js/account/account.controller.js @@ -0,0 +1,69 @@ +/** + * Created by russia on 2/2/15. + */ +'use strict'; + +var Backbone = require('../vendor/index').Backbone; +var $ = require('../vendor/index').$; +var Model = require('./account.model'); +var View = require('./account.view'); + +module.exports = Backbone.Controller.extend({ + + routes: { + 'accounts/:id': 'showAccount' + }, + + initialize: function(){ + this.options.container = this.options.container || 'body'; + this.model = new Model(); + this.view = new View({model: this.model}); + }, + + showAccount: function(accountId, cb){ + this.fetchModel(accountId, function(err){ + var view; + + if (err){ + view = this.renderError(); + } else { + view = this.renderView(); + } + + if (cb){ + cb(err, view); + } + + }.bind(this)); + }, + + fetchModel: function(accountId, cb){ + this.model.set({id: accountId}); + + this.model.fetch({ + success: function(model, response, options){ + //console.log(model); + cb(null, model); + }, + error: function(model, response, options){ + //console.error(response); + cb(response, model); + } + }); + }, + + renderToContainer: function(view){ + return $(this.options.container).html(view); + }, + + renderView: function(){ + this.renderToContainer(this.view.render().$el); + return this.view; + }, + + renderError: function(){ + return this.renderToContainer( + '

There was a problem rendering this account

'); + } + +}); diff --git a/client/spa/js/account/account.html b/client/spa/js/account/account.html new file mode 100644 index 0000000..fd12b40 --- /dev/null +++ b/client/spa/js/account/account.html @@ -0,0 +1,3 @@ +

<%- cust_name %>

+

<%- account_type %>

+ diff --git a/client/spa/js/account/account.model.js b/client/spa/js/account/account.model.js new file mode 100644 index 0000000..d794a62 --- /dev/null +++ b/client/spa/js/account/account.model.js @@ -0,0 +1,30 @@ +/** + * Created by russia on 2/2/15. + */ +'use strict'; + +var Backbone = require('../vendor/index').Backbone; + +module.exports = Backbone.Model.extend({ + + defaults: { + account_type: 'enterprise' + }, + + urlRoot: '/api/account', + + initialize: function(){ + this.on('change', function(){ + this.trigger('foo', 'bar'); + }); + }, + + validate: function(attrs) { + if (!attrs.cust_name) { + return 'name cannot be empty'; + } + if (!attrs.account_type) { + return 'name cannot be empty'; + } + } +}); diff --git a/client/spa/js/account/account.view.js b/client/spa/js/account/account.view.js new file mode 100644 index 0000000..b9088fe --- /dev/null +++ b/client/spa/js/account/account.view.js @@ -0,0 +1,39 @@ +/** + * Created by russia on 2/2/15. + */ +'use strict'; + +var Backbone = require('../vendor/index').Backbone; +var _ = require('../vendor/index')._; +var $ = require('../vendor/index').$; + +var fs = require('fs'); //will be replaced by brfs in the browser + +// readFileSync will be evaluated statically so errors can't be caught +var template = fs.readFileSync(__dirname + '/account.html', 'utf8'); + +module.exports = Backbone.View.extend({ + + className: 'account', + + template: _.template(template), + + events: { + 'click .delete': 'destroy' + }, + + initialize: function(){ + this.listenTo(this.model, 'destroy', this.remove); + }, + + render: function(){ + var context = this.model.toJSON(); + this.$el.html(this.template(context)); + return this; + }, + + destroy: function(){ + this.model.destroy(); + } + +}); diff --git a/client/spa/js/account/spec/account.controller.spec.js b/client/spa/js/account/spec/account.controller.spec.js new file mode 100644 index 0000000..7844a57 --- /dev/null +++ b/client/spa/js/account/spec/account.controller.spec.js @@ -0,0 +1,94 @@ +/** + * Created by russia on 2/2/15. + */ +/* + global jasmine, describe, it, expect, beforeEach, afterEach, xdescribe, xit, + spyOn + */ + +// Get the code you want to test +var Controller = require('../account.controller'); +var $ = require('jquery'); +var matchers = require('jasmine-jquery-matchers'); + +// Test suite +console.log('test account.controller'); +describe('Account controller', function(){ + + var controller; + + beforeEach(function(){ + controller = new Controller(); + }); + + it('can be created', function(){ + expect(controller).toBeDefined(); + }); + + describe('when it is created', function(){ + + it('has the expected routes', function(){ + expect(controller.routes).toEqual(jasmine.objectContaining({ + 'accounts/:id': 'showAccount' + })); + }); + + it('without a container option, uses body as the container', function(){ + expect(controller.options.container).toEqual('body'); + }); + + it('with a container option, uses specified container', function(){ + var ctrl = new Controller({container: '.newcontainer'}); + expect(ctrl.options.container).toEqual('.newcontainer'); + }); + }); + + describe('when calling showAccount', function(){ + + beforeEach(function(){ + jasmine.addMatchers(matchers); + }); + + var success = function(callbacks){ + controller.model.set({'cust_name': 'valid account', 'account_type':'enterprise'}); + callbacks.success(controller.model); + }; + var err = function(callbacks){ + callbacks.error('error', controller.model); + }; + + it('with a valid account id, fetches the model', function(){ + spyOn(controller.model, 'fetch').and.callFake(success); + var cb = function(err, view){ + expect(err).toBeNull(); + expect(controller.model.get('cust_name')).toEqual('valid account'); + }; + controller.showAccount(1, cb); + }); + + it('with a valid account id, renders the view', function(){ + spyOn(controller.model, 'fetch').and.callFake(success); + spyOn(controller.view, 'render').and.callFake(function(){ + controller.view.$el = 'fake render'; + return controller.view; + }); + var cb = function(err, view){ + expect($('body')).toHaveText('fake render'); + expect(view.cid).toEqual(controller.view.cid); + }; + controller.showAccount(1, cb); + }); + + it('with an invalid account id, renders an error message', function(){ + spyOn(controller.model, 'fetch').and.callFake(err); + var cb = function(err, view){ + expect(err).toBeTruthy(); + expect($('body')).toHaveText( + 'There was a problem rendering this account'); + }; + controller.showAccount(1, cb); + }); + + }); + +}); diff --git a/client/spa/js/account/spec/account.model.spec.js b/client/spa/js/account/spec/account.model.spec.js new file mode 100644 index 0000000..98589cd --- /dev/null +++ b/client/spa/js/account/spec/account.model.spec.js @@ -0,0 +1,90 @@ +/** + * Created by russia on 2/2/15. + */ + +/* + global jasmine, describe, it, expect, beforeEach, afterEach, xdescribe, xit, + spyOn + */ +'use strict'; + +// Get the code you want to test +var Model = require('../account.model'); + +// Test suite +console.log('test account.model'); +describe('Account model ', function(){ + + var model; + + describe('when creating a new model ', function(){ + + beforeEach(function(){ + model = new Model(); + }); + + it('sets the correct default values', function(){ + expect(model.get('account_type')).toEqual('enterprise'); + }); + + xit('initializes with custom logic', function(){ + + }); + }); + + describe('when updating the model ', function(){ + + var errorSpy; + + beforeEach(function(){ + errorSpy = jasmine.createSpy('Invalid'); + model = new Model({ + id: 1 + }); + model.on('invalid', errorSpy); + model.save(); + }); + + it('does not save when name is empty ', function(){ + expect(errorSpy).toHaveBeenCalled(); + expect(errorSpy).toHaveBeenCalledWith( + model, + 'name cannot be empty', + { validate: true, validationError: 'name cannot be empty'} + ); + }); + + // Use if you have transformation logic on set + xit('sets the values correctly ', function(){ + + }); + + // Use if you have transformation logic on get + xit('retrieves the correct values ', function(){ + + }); + }); + + describe('when changing the state of the model ', function(){ + + var eventSpy; + + beforeEach(function(){ + eventSpy = jasmine.createSpy('Change Event'); + model = new Model({ + id: 1, + name: 'test' + }); + model.on('foo', eventSpy); + model.set({name: 'changed'}); + }); + + it('triggers the custom event foo', function(){ + expect(eventSpy).toHaveBeenCalled(); + expect(eventSpy).toHaveBeenCalledWith( + 'bar' + ); + }); + }); + +}); diff --git a/client/spa/js/account/spec/account.view.spec.js b/client/spa/js/account/spec/account.view.spec.js new file mode 100644 index 0000000..e17caec --- /dev/null +++ b/client/spa/js/account/spec/account.view.spec.js @@ -0,0 +1,97 @@ +/** + * Created by russia on 2/2/15. + */ + +/* + global jasmine, describe, it, expect, beforeEach, afterEach, xdescribe, xit, + spyOn + */ +'use strict'; + +// Get the code you want to test +var View = require('../account.view.js'); +var matchers = require('jasmine-jquery-matchers'); +var Backbone = require('../../vendor/index').Backbone; + +// Test suite +console.log('test account.view'); +describe('Account view ', function(){ + + var model; + var view; + + beforeEach(function(){ + // Add some convenience tests for working with the DOM + jasmine.addMatchers(matchers); + + var Model = Backbone.Model.extend({}); + // Needs to have the fields required by the template + model = new Model({ + cust_name: 'Account <3', + account_type: 'enterprise' + }); + + view = new View({ + model: model + }); + }); + + describe('when the view is instantiated ', function(){ + + it('creates the correct element', function(){ + // Element has to be uppercase + expect(view.el.nodeName).toEqual('DIV'); + }); + + it('sets the correct class', function(){ + expect(view.$el).toHaveClass('account'); + }); + }); + + describe('when the view is rendered ', function(){ + + it('returns the view object ', function(){ + expect(view.render()).toEqual(view); + }); + + it('produces the correct HTML ', function(){ + view.render(); + expect(view.$('h1').html()).toEqual('Account <3'); + }); + + }); + + xdescribe('when the user clicks on the Save button ', function(){ + + xit('updates the model', function(){ + }); + }); + + xdescribe('when the user clicks on ... ', function(){ + + xit('triggers the ... event', function(){ + }); + }); + + describe('when the user clicks on the Delete button ', function(){ + + beforeEach(function(){ + // Must call through otherwise the actual view function won't be called + spyOn(view, 'destroy').and.callThrough(); + // Must delegateEvents for the spy on a DOM event to work + view.delegateEvents(); + + spyOn(model, 'destroy'); + }); + + it('deletes the model', function(){ + // Must render for the event to be fired + view.render(); + view.$('.delete').trigger('click'); + + expect(view.destroy).toHaveBeenCalled(); + expect(model.destroy).toHaveBeenCalled(); + }); + }); + +}); diff --git a/client/spa/js/main.js b/client/spa/js/main.js index d450a0b..8b4fe2f 100644 --- a/client/spa/js/main.js +++ b/client/spa/js/main.js @@ -3,9 +3,9 @@ window.Backbone = require('./vendor').Backbone; // Include your code -//var Contact = require('./contact/contact.controller'); +var Account = require('./account/account.controller'); // Initialize it -//window.contact = new Contact({router:true, container: 'body'}); +window.account = new Account({router:true, container: 'body'}); // Additional modules go here