diff --git a/bin/backbone-generator b/bin/backbone-generator index 2367eed..5922c5e 100755 --- a/bin/backbone-generator +++ b/bin/backbone-generator @@ -7,11 +7,28 @@ require 'thor/group' class BackboneGenerator < Thor include Thor::Actions + class_option :coffee, :type => :boolean, :desc => 'Generate CoffeeScript instead of JavaScript' def self.source_root File.expand_path('../../templates', __FILE__) end no_tasks do + def extension + options[:coffee] ? 'coffee' : 'js' + end + + def script_target + options[:coffee] ? 'app/coffeescripts' : 'public/javascripts' + end + + def view_target + 'app/views' + end + + def test_target + 'spec/javascripts' + end + def underscore_name singularize(Thor::Util.snake_case(@name.gsub("::", "/"))) end @@ -29,25 +46,25 @@ class BackboneGenerator < Thor end def generate_model - template('model.js.erb', "public/javascripts/models/#{underscore_name}.js") - template('model_spec.js.erb', "spec/javascripts/models/#{underscore_name}_spec.js") + template("model.#{extension}.erb", File.join(script_target, "models", "#{underscore_name}.#{extension}")) + template("model_spec.#{extension}.erb", File.join(test_target, "models", "#{underscore_name}_spec.#{extension}")) end def generate_view - template('view.js.erb', "public/javascripts/views/#{underscore_name}_view.js") - template('view.jst.erb', "app/views/#{underscore_name}s/view.jst") - template('view_spec.js.erb', "spec/javascripts/views/#{underscore_name}_view_spec.js") + template("view.#{extension}.erb", File.join(script_target, "views", "#{underscore_name}_view.#{extension}")) + template("view.jst.erb", File.join(view_target, "#{underscore_name}s/view.jst")) + template("view_spec.#{extension}.erb", File.join(test_target, "views", "#{underscore_name}_view_spec.#{extension}")) end def generate_collection - template('collection.js.erb', "public/javascripts/collections/#{plural_underscore_name}.js") - template('collection_spec.js.erb', "spec/javascripts/collections/#{plural_underscore_name}_spec.js") + template("collection.#{extension}.erb", File.join(script_target, "collections", "#{plural_underscore_name}.#{extension}")) + template("collection_spec.#{extension}.erb", File.join(test_target, "collections", "#{plural_underscore_name}_spec.#{extension}")) end def generate_collection_view - template('collection_view.js.erb', "public/javascripts/views/#{plural_underscore_name}_view.js") - template('collection_view.jst.erb', "app/views/#{plural_underscore_name}/list.jst") - template('collection_view_spec.js.erb', "spec/javascripts/views/#{plural_underscore_name}_view_spec.js") + template("collection_view.#{extension}.erb", File.join(script_target, 'views', "#{plural_underscore_name}_view.#{extension}")) + template("collection_view.jst.erb", File.join(view_target, "#{plural_underscore_name}/list.jst")) + template("collection_view_spec.#{extension}.erb", File.join(test_target, "views", "#{plural_underscore_name}_view_spec.#{extension}")) end end @@ -83,20 +100,20 @@ class BackboneGenerator < Thor desc 'spec-helper', "Generate a spec helper for Backbone things" def spec_helper - template('spec_helper.js.erb', 'spec/javascripts/helpers/backbone_spec_helper.js') + template("spec_helper.#{extension}.erb", File.join(test_target, "helpers", "backbone_spec_helper.#{extension}")) end - desc 'app-helper', "Generate an application helper for useful Backbone things" + desc "app-helper", "Generate an application helper for useful Backbone things" def app_helper - template('app_helper.js.erb', 'public/javascripts/applications/backbone_helper.js') + template("app_helper.#{extension}.erb", File.join(script_target, "applications", "backbone_helper.#{extension}")) end - desc 'app-scaffold', "Generate an application scaffold" + desc "app-scaffold", "Generate an application scaffold" def app_scaffold - template('app_view.js.erb', 'public/javascripts/application/app_view.js') - template('app_view.jst.erb', 'app/views/application/app_view.jst') - template('controller.js.erb', 'public/javascripts/application/controller.js') - template('app_view_spec.js.erb', 'spec/javascripts/application/app_view_spec.js') + template("app_view.#{extension}.erb", File.join(script_target, "application", "app_view.#{extension}")) + template("app_view.jst.erb", File.join(view_target, "application", "app_view.jst")) + template("controller.#{extension}.erb", File.join(script_target, "application", "controller.#{extension}")) + template("app_view_spec.#{extension}.erb", File.join(test_target, "application", "app_view_spec.#{extension}")) end private diff --git a/spec/bin/backbone-generator_spec.rb b/spec/bin/backbone-generator_spec.rb index b21a70a..e366ead 100644 --- a/spec/bin/backbone-generator_spec.rb +++ b/spec/bin/backbone-generator_spec.rb @@ -16,8 +16,116 @@ describe 'backbone-generator' do after { clean! } describe 'coffeescript' do - describe 'model' do + def should_generate_model + File.file?(model = 'app/coffeescripts/models/section/model.coffee').should be_true + File.file?(spec = 'spec/javascripts/models/section/model_spec.coffee').should be_true + File.read(model).should match(/SectionModel/) + File.read(spec).should match(/SectionModel/) + end + + def should_generate_view + File.file?(view = 'app/coffeescripts/views/section/model_view.coffee').should be_true + File.file?(spec = 'spec/javascripts/views/section/model_view_spec.coffee').should be_true + File.file?(template = 'app/views/section/models/view.jst').should be_true + + File.read(view).should match(/SectionModel/) + File.read(view).should match(%r{template: JST\['section/models/view'\]}) + File.read(spec).should match(/SectionModel/) + end + + def should_generate_collection + File.file?(collection = 'app/coffeescripts/collections/section/models.coffee').should be_true + File.file?(spec = 'spec/javascripts/collections/section/models_spec.coffee').should be_true + + File.read(collection).should match(/SectionModels/) + File.read(collection).should_not match(/SectionModelss/) + File.read(collection).should match(%r{section/model}) + File.read(spec).should match(/SectionModels/) + File.read(spec).should_not match(/SectionModelss/) + end + + def should_generate_collection_view + File.file?(view = 'app/coffeescripts/views/section/models_view.coffee').should be_true + File.file?(spec = 'spec/javascripts/views/section/models_view_spec.coffee').should be_true + File.file?(template = 'app/views/section/models/list.jst').should be_true + + File.read(view).should match(/SectionModelsView/) + File.read(view).should_not match(/SectionModelssView/) + File.read(view).should match(/SectionModelView/) + File.read(view).should match(%r{template: JST\['section/models/list'\]}) + File.read(spec).should match(/SectionModelsView/) + File.read(spec).should_not match(/SectionModelssView/) + end + + describe 'model' do + it "should generate the model files" do + run "model", "Section::Model", '--coffee' + + should_generate_model + end + end + + describe 'view' do + it "should generate the model files" do + run "view", "Section::Model", '--coffee' + + should_generate_view + end + end + + describe 'collection view' do + it "should generate the collection view files" do + run "collection-view", "Section::Model", '--coffee' + + should_generate_collection_view + end + end + + describe 'collection' do + it "should generate the collection files" do + run "collection", "Section::Model", '--coffee' + + should_generate_collection + end + end + + describe 'scaffold' do + it "should generate everything!" do + run "scaffold", "Section::Model", '--coffee' + + should_generate_model + should_generate_view + should_generate_collection + should_generate_collection_view + end + end + + describe 'spec helper' do + it "should generate a spec helper" do + run "spec-helper", '--coffee' + + File.file?(collection = 'spec/javascripts/helpers/backbone_spec_helper.coffee').should be_true + end + end + + describe 'app helper' do + it "should generate an app helper" do + run "app-helper", '--coffee' + + File.file?(collection = 'app/coffeescripts/applications/backbone_helper.coffee').should be_true + end + end + + describe 'application scaffold' do + it "should generate an application scaffold" do + run "app-scaffold", '--coffee' + + File.file?(app = 'app/coffeescripts/application/app_view.coffee').should be_true + File.file?(app_view = 'app/views/application/app_view.jst').should be_true + File.file?(controller = 'app/coffeescripts/application/controller.coffee').should be_true + File.file?(spec = 'spec/javascripts/application/app_view_spec.coffee').should be_true + end end end diff --git a/templates/app_helper.coffee.erb b/templates/app_helper.coffee.erb new file mode 100644 index 0000000..686bd70 --- /dev/null +++ b/templates/app_helper.coffee.erb @@ -0,0 +1,18 @@ +Backbone.Collection.prototype.ensureFetched = (callback) -> + if (@_alreadyEnsureFetched) + _refresher => + this.unbind('refresh', _refresher) + callback.apply(this) + @_alreadyEnsureFetched = true + @bind('refresh', _refresher) + @fetch() + else + callback.apply(this) + +Backbone.View.prototype.attributes = -> + attrs = {} + for field in @attributeFields + do (field) => + attrs[field] = this.$("input[name='#{field}']").val() + attrs + diff --git a/templates/app_view.coffee.erb b/templates/app_view.coffee.erb new file mode 100644 index 0000000..fa30b42 --- /dev/null +++ b/templates/app_view.coffee.erb @@ -0,0 +1,10 @@ +class window.AppView extends Backbone.View + el: '#application' + template: JST['application/app_view'] + initialize: -> + controller = new Controller({app: this}) + Backbone.history.start() + render: => + $(@el).html(@template()) + this + diff --git a/templates/app_view_spec.coffee.erb b/templates/app_view_spec.coffee.erb new file mode 100644 index 0000000..90a4591 --- /dev/null +++ b/templates/app_view_spec.coffee.erb @@ -0,0 +1,9 @@ +describe 'AppView', -> + appView = null + + beforeEach, -> + appView = new AppView() + + it 'should render', -> + expect($(appView.render().el)).toContain('.something') + diff --git a/templates/collection.coffee.erb b/templates/collection.coffee.erb new file mode 100644 index 0000000..8f87dff --- /dev/null +++ b/templates/collection.coffee.erb @@ -0,0 +1,4 @@ +class window.<%= plural_object_name %> extends Backbone.Collection + url: '/<%= plural_underscore_name %>' + model: <%= object_name %> + diff --git a/templates/collection_spec.coffee.erb b/templates/collection_spec.coffee.erb new file mode 100644 index 0000000..5931453 --- /dev/null +++ b/templates/collection_spec.coffee.erb @@ -0,0 +1,7 @@ +describe '<%= plural_object_name %>', -> + collection = null + + withServer() + + it 'should fetch records from the API', -> + diff --git a/templates/collection_view.coffee.erb b/templates/collection_view.coffee.erb new file mode 100644 index 0000000..df2b724 --- /dev/null +++ b/templates/collection_view.coffee.erb @@ -0,0 +1,23 @@ +class window.<%= plural_object_name %>View extends Backbone.View + events: { + 'click button.new': 'addNew' + } + template: JST['<%= plural_underscore_name %>/list'] + initialize: -> + @collection.bind('refresh', @addAll) + + @render + @collection.fetch + render: => + $(this.el).html(@template()) + this + addOne: (model) => + view = new <%= object_name %>View({model: model}) + this.$('.list').append(view.render().el) + addAll: => + @collection.each(@addOne) + addNew: => + facility = new <%= object_name %>() + @collection.add(facility) + @addOne(facility) + diff --git a/templates/collection_view_spec.coffee.erb b/templates/collection_view_spec.coffee.erb new file mode 100644 index 0000000..cc6c9b5 --- /dev/null +++ b/templates/collection_view_spec.coffee.erb @@ -0,0 +1,19 @@ +describe '<%= plural_object_name %>View', -> + view = collection = null + + beforeEach -> + collection = new <%= plural_object_name %>() + + it 'should render', -> + view = new <%= plural_object_name %>View({collection: collectioon}) + view.render + + expect($(view.el)).toContain('.list') + expect($(view.el)).toContail('button.new') + + it 'should add a new model when new is clicked', -> + view.$('button.new').trigger('click') + + expect(view.$('.list').toContain('.<%= underscore_name %>') + expect(collection.length).toEqual(1) + diff --git a/templates/controller.coffee.erb b/templates/controller.coffee.erb new file mode 100644 index 0000000..6b763eb --- /dev/null +++ b/templates/controller.coffee.erb @@ -0,0 +1,2 @@ +class window.Controller extends Backbone.Controller + routes: {} diff --git a/templates/model.coffee.erb b/templates/model.coffee.erb new file mode 100644 index 0000000..e6bf166 --- /dev/null +++ b/templates/model.coffee.erb @@ -0,0 +1 @@ +class window.<%= object_name %> extends Backbone.Model diff --git a/templates/model_spec.coffee.erb b/templates/model_spec.coffee.erb new file mode 100644 index 0000000..8db61b4 --- /dev/null +++ b/templates/model_spec.coffee.erb @@ -0,0 +1,8 @@ +describe '<%= object_name %>', -> + model = null + + it 'should have some tests', -> + model = new <%= object_name %>() + + expect(true).toEqual(false) + diff --git a/templates/spec_helper.coffee.erb b/templates/spec_helper.coffee.erb new file mode 100644 index 0000000..9e50a2b --- /dev/null +++ b/templates/spec_helper.coffee.erb @@ -0,0 +1,14 @@ +window.withServer -> + jasmine.getEnv().withServer() + +jasmine.Env.prototype.withServer -> + @currentSuite.beforeEach -> + @server = sinon.fakeServer.create() + + @currentSuite.afterEach -> + @server.restore() + +beforeEach -> + @validJSONResponse = (data) -> + [ 200, { 'Content-type': 'application/json' }, JSON.stringify(data) ] + diff --git a/templates/view.coffee.erb b/templates/view.coffee.erb new file mode 100644 index 0000000..5065c42 --- /dev/null +++ b/templates/view.coffee.erb @@ -0,0 +1,22 @@ +class window.<%= object_name %>View extends Backbone.View + events: { + 'click button.save': 'save', + 'click button.delete': 'destroy' + } + attributeFields: [] + template: JST['<%= underscore_name %>s/view'] + className: '<%= underscore_name %>' + initialize: -> + @model.bind('change', @render) + @model.bind('remove', @remove) + @model.view = this + render: => + $(@el).html(@template(@model.toJSON)) + this.$('button.save').text(@model.isNew() ? 'Create' : 'Update') + this + save: => + @model.save(@attributes) + destroy: => + if confirm("Are you sure?") + @model.destroy + diff --git a/templates/view_spec.coffee.erb b/templates/view_spec.coffee.erb new file mode 100644 index 0000000..16fb22b --- /dev/null +++ b/templates/view_spec.coffee.erb @@ -0,0 +1,42 @@ +describe '<%= object_name %>View', -> + view = model = null + + describe 'new record', -> + beforeEach -> + model = new <%= object_name %> + + it 'should render with a create button', -> + view = new <%= object_name %>View({model: model}) + view.render + + expect(view.$('button.save')).toHaveText('Create') + + describe 'existing record', -> + beforeEach -> + model = new <%= object_name %>({id: 1}) + setFixtures('
') + + view = new <%= object_name %>View({model: model}) + + it 'should render with an update button', -> + view.render + + expect(view.$('button.save')).toHaveText('Update') + + it 'should destroy the model', -> + spyOn(window, 'confirm').andReturn(true) + spyOn(model, 'destory') + + $('#container').append(view.el) + view.$('button.delete').trigger('click') + + expect(model.destroy).toHaveBeenCalled + expect(window.confirm).toHaveBeenCalled + + it 'should remove the view when the model is destroyed', -> + $('#container').append(view.render().el) + + expect($('button.save')).toExist + model.trigger('remove') + expect($('button.save')).not.toExist +