From 6b35e993bd71b225f396576d6c748bf630d4214a Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 10 Jun 2015 09:40:28 +0800 Subject: [PATCH] WIP: More work on panel content, styling and data submission logic --- .../index_panel_controller.js.coffee | 22 +++ .../index_producer_panel_controller.js.coffee | 21 ++- .../index_shop_panel_controller.js.coffee | 4 +- .../services/enterprise_resource.js.coffee | 4 +- .../services/enterprises.js.coffee | 27 ++- .../directives/panel_row.js.coffee | 4 +- .../panels/enterprise_producer.html.haml | 21 ++- .../admin/panels/enterprise_shop.html.haml | 160 ++++++++++-------- .../admin/enterprise_index_panels.css.scss | 7 + .../stylesheets/admin/panel_rows.css.scss | 3 +- .../admin/enterprises_controller.rb | 18 ++ .../api/admin/basic_enterprise_serializer.rb | 2 +- app/views/admin/enterprises/index.html.haml | 4 +- .../enterprises_controller_spec.js.coffee | 4 +- .../index_panel_controller_spec.js.coffee | 46 +++++ .../services/enterprises_spec.js.coffee | 76 ++++++++- 16 files changed, 322 insertions(+), 101 deletions(-) create mode 100644 app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee create mode 100644 spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee diff --git a/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee new file mode 100644 index 0000000000..dbf6ee47b2 --- /dev/null +++ b/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee @@ -0,0 +1,22 @@ +angular.module("admin.enterprises").controller 'indexPanelCtrl', ($scope, Enterprises) -> + $scope.enterprise = $scope.object + $scope.saving = false + + $scope.saved = -> + Enterprises.saved($scope.enterprise) + + $scope.save = -> + unless $scope.saved() + $scope.saving = true + Enterprises.save($scope.enterprise).then (data) -> + $scope.saving = false + , (response) -> + $scope.saving = false + if response.status == 422 && response.data.errors? + message = 'Please resolve the following errors:\n' + for attr, msg of response.data.errors + message += "#{attr} #{msg}\n" + alert(message) + + $scope.resetAttribute = (attribute) -> + Enterprises.resetAttribute($scope.enterprise, attribute) diff --git a/app/assets/javascripts/admin/enterprises/controllers/index_producer_panel_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/index_producer_panel_controller.js.coffee index 342d4fb0e6..75fd9f1ec5 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/index_producer_panel_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/index_producer_panel_controller.js.coffee @@ -1,9 +1,14 @@ -angular.module("admin.enterprises").controller 'indexProducerPanelCtrl', ($scope) -> - $scope.enterprise = angular.copy($scope.object()) - $scope.persisted = angular.copy($scope.object()) - $scope.attributes = ['is_primary_producer'] +angular.module("admin.enterprises").controller 'indexProducerPanelCtrl', ($scope, $controller) -> + angular.extend this, $controller('indexPanelCtrl', {$scope: $scope}) - $scope.saved = -> - for attribute in $scope.attributes - return false if $scope.enterprise[attribute] != $scope.persisted[attribute] - true + $scope.changeToProducer = -> + $scope.resetAttribute('sells') + $scope.resetAttribute('producer_profile_only') + $scope.enterprise.is_primary_producer = true + + $scope.changeToNonProducer = -> + if $scope.enterprise.sells == 'own' + $scope.enterprise.sells = 'any' + if $scope.enterprise.producer_profile_only = true + $scope.enterprise.producer_profile_only = false + $scope.enterprise.is_primary_producer = false diff --git a/app/assets/javascripts/admin/enterprises/controllers/index_shop_panel_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/index_shop_panel_controller.js.coffee index 0cba49e1ab..ad67ed0ea8 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/index_shop_panel_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/index_shop_panel_controller.js.coffee @@ -1,2 +1,2 @@ -angular.module("admin.enterprises").controller 'indexShopPanelCtrl', ($scope) -> - $scope.enterprise = angular.copy($scope.object()) +angular.module("admin.enterprises").controller 'indexShopPanelCtrl', ($scope, $controller) -> + angular.extend this, $controller('indexPanelCtrl', {$scope: $scope}) diff --git a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee index 489beeaf2f..357302d2b8 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee @@ -1,6 +1,8 @@ angular.module("admin.enterprises").factory 'EnterpriseResource', ($resource) -> - $resource('/admin/enterprises.json', {}, { + $resource('/admin/enterprises/:id.json', {}, { 'index': method: 'GET' isArray: true + 'update': + method: 'PUT' }) diff --git a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee index 045dffcf89..3411c4a6c4 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee @@ -1,16 +1,39 @@ -angular.module("admin.enterprises").factory 'Enterprises', (EnterpriseResource) -> +angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResource) -> new class Enterprises enterprises: [] enterprises_by_id: {} + pristine_by_id: {} loaded: false index: (params={}, callback=null) -> EnterpriseResource.index params, (data) => for enterprise in data @enterprises.push enterprise - @enterprises_by_id[enterprise.id] = enterprise + @pristine_by_id[enterprise.id] = angular.copy(enterprise) @loaded = true (callback || angular.noop)(@enterprises) @enterprises + + save: (enterprise) -> + deferred = $q.defer() + enterprise.$update({id: enterprise.permalink}) + .then( (data) => + @pristine_by_id[enterprise.id] = angular.copy(enterprise) + deferred.resolve(data) + ).catch (response) -> + deferred.reject(response) + deferred.promise + + saved: (enterprise) -> + @diff(enterprise).length == 0 + + diff: (enterprise) -> + changed = [] + for attr, value of enterprise when not angular.equals(value, @pristine_by_id[enterprise.id][attr]) + changed.push attr unless attr is "$$hashKey" + changed + + resetAttribute: (enterprise, attribute) -> + enterprise[attribute] = @pristine_by_id[enterprise.id][attribute] diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee index 40a5062a27..eb5a4171f4 100644 --- a/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee @@ -2,7 +2,7 @@ angular.module("admin.indexUtils").directive "panelRow", (Panels, Columns) -> restrict: "C" templateUrl: "admin/panel.html" scope: - object: "&" + object: "=" panels: "=" link: (scope, element, attrs) -> scope.template = "" @@ -34,4 +34,4 @@ angular.module("admin.indexUtils").directive "panelRow", (Panels, Columns) -> element.hide 0, -> scope.setSelected null - Panels.register(scope.object().id, scope) + Panels.register(scope.object.id, scope) diff --git a/app/assets/javascripts/templates/admin/panels/enterprise_producer.html.haml b/app/assets/javascripts/templates/admin/panels/enterprise_producer.html.haml index eec1375e82..b0f7473e06 100644 --- a/app/assets/javascripts/templates/admin/panels/enterprise_producer.html.haml +++ b/app/assets/javascripts/templates/admin/panels/enterprise_producer.html.haml @@ -10,32 +10,35 @@ %h3 Producer %p Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it. - %p Being a producer does not limit an enterprise, producers can still opt to aggregate food from other enterprises and sell it through a shop on the Open Food Network. + %p Producers can also perform other functions, such as aggregating food from other enterprises and selling it through a shop on the Open Food Network. .info{ ng: { show: "enterprise.is_primary_producer==false" } } %h3 Non-Producer %p Non-producers do not produce any food themselves, meaning that they cannot create their own products for sale through the Open Food Network. - %p Instead, non-producers specialise in filling the other vital roles between producer and end eater, whether it be aggregating, grading, packing, selling or delivering food. + %p Instead, non-producers specialise in linking producers to the end eater, whether it be by aggregating, grading, packing, selling or delivering food. .omega.eight.columns - %a.button.selector{ ng: { click: 'enterprise.is_primary_producer=true', class: "{selected: enterprise.is_primary_producer==true}" } } + %a.button.selector{ ng: { click: 'changeToProducer()', class: "{selected: enterprise.is_primary_producer==true}" } } .top %h3 PRODUCER %p Primary producers of food .bottom eg. GROWERS, BAKERS, BREWERS, MAKERS - %a.button.selector{ ng: { click: 'enterprise.is_primary_producer=false', class: "{selected: enterprise.is_primary_producer==false}" } } + %a.button.selector{ ng: { click: 'changeToNonProducer()', class: "{selected: enterprise.is_primary_producer==false}" } } .top %h3 Non-Producer %p All other food enterprises .bottom eg. Grocery stores, Food co-ops, Buying groups - %a.button.update.fullwidth{ ng: { class: "{disabled: saved()}" } } - %span{ ng: {show: "saved()" } } + %a.button.update.fullwidth{ ng: { class: "{disabled: saved() && !saving, saving: saving}", click: "save()" } } + %span{ ng: {hide: "saved() || saving" } } + SAVE + %i.icon-save + %span{ ng: {show: "saved() && !saving" } } SAVED %i.icon-ok-sign - %span{ ng: {hide: "saved()" } } - SAVE - %i.icon-upload-alt + %span{ ng: {show: "saving" } } + SAVING + %i.icon-refresh diff --git a/app/assets/javascripts/templates/admin/panels/enterprise_shop.html.haml b/app/assets/javascripts/templates/admin/panels/enterprise_shop.html.haml index 0a9f84c6d9..e32be04dbd 100644 --- a/app/assets/javascripts/templates/admin/panels/enterprise_shop.html.haml +++ b/app/assets/javascripts/templates/admin/panels/enterprise_shop.html.haml @@ -4,65 +4,96 @@ %input{ hidden: "true", name: "sells", ng: { required: true, pattern: "/^(none|own|any)$/", value: "enterprise.sells"} } %input{ hidden: "true", name: "producer_profile_only", ng: { required: "enterprise.is_primary_producer=='none'", disabled: "enterprise.is_primary_producer!='none'", value: "enterprise.producer_profile_only"} } - -# %table - -# %col{ width: '25%' } - -# %col{ width: '25%' } - -# %col{ width: '50%' } - -# %thead - -# %th PRODUCER? - -# %th SHOP? - -# %th COST AND DESCRIPTION - -# %tr - -# %td.selector{ rowspan: 3, ng: { click: 'enterprise.is_primary_producer=true', class: "{selected: enterprise.is_primary_producer==true}" } } - -# %h5 - -# PRODUCER - -# %td.selector{ rowspan: 2, ng: { click: "enterprise.sells='none'", class: "{selected: enterprise.sells=='none'}" } } - -# %h5 - -# NO SHOP - -# %td.description{ rowspan: 6 } - -# %p - -# %strong MONTHLY COST: - -# {{ "FREE" }} - -# - -# %p - -# %strong DESCRIPTION: - -# %br - -# %strong {{ enterprise.name }} - -# is a - -# %strong {{ enterprise.is_primary_producer ? "producer" : "non-producer" }} - -# with a shop that sells - -# =succeed "." do - -# %strong {{ enterprise.sells }} - -# %a.update.fullwidth.button - -# UPDATE NOW - -# %tr - -# %tr - -# %td.selector{ rowspan: 2, ng: { click: "enterprise.sells='own'", class: "{selected: enterprise.sells=='own'}" } } - -# %h5 - -# PRODUCER SHOP - -# %tr - -# %td.selector{ rowspan: 3, ng: { click: 'enterprise.is_primary_producer=false', class: "{selected: enterprise.is_primary_producer==false}" } } - -# %h5 - -# NON-PRODUCER - -# %tr - -# %td.selector{ rowspan: 2, ng: { click: "enterprise.sells='any'", class: "{selected: enterprise.sells=='any'}" } } - -# %h5 - -# FULL SHOP - -# %tr - .row .alpha.eight.columns - %a.button.selector{ ng: { click: "enterprise.sells='none' && enterprise.producer_profile_only=true", class: "{selected: enterprise.sells=='none' && enterprise.producer_profile_only==true}" } } + -# Non-Producer Info + .info{ ng: { show: "!enterprise.is_primary_producer && enterprise.sells=='none'" } } + %h3 Hub Profile + + %p + %strong COST: ALWAYS FREE + + %p A Hub Profile gives you the ability to list your enterprise on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings. + + %p Profile enterprises cannot create products, and so are unable to trade with other enterprises through the Open Food Network. + + .info{ ng: { show: "!enterprise.is_primary_producer && enterprise.sells=='any'" } } + %h3 Hub Shop + + %p + %strong COST: %2 OF SALES, CAPPED AT $50 PER MONTH + + %p A Full Shop enables an enterprise to aggregate produce and to sell it through a shop on the Open Food Network. + + %p Hubs can take many forms, whether they be a food co-op, a buying group, a veggie-box program, or a local grocery store. + + %p The Open Food Network aims to support as many hub models as possible, so no matter your situation, we want to provide the tools you need to run your organisation or local food business. + + + -# Producer Info + .info{ ng: { show: "enterprise.is_primary_producer && enterprise.sells=='none' && enterprise.producer_profile_only==true" } } + %h3 Producer Profile + + %p + %strong COST: ALWAYS FREE + + %p A Producer Profile gives you the ability to list your enterprise on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings. + + %p Profile enterprises cannot create products, and so are unable to trade with other enterprises through the Open Food Network. + + .info{ ng: { show: "enterprise.is_primary_producer && enterprise.sells=='none' && enterprise.producer_profile_only==false" } } + %h3 No Shop + + %p + %strong COST: ALWAYS FREE + + %p If you prefer to focus on producing food, and want to leave the work of selling it to someone else, you won't require a shop on the Open Food Network. + + %p Producers without a shop can still market their produce through the Open Food Network by connecting and trading with existing shops. + + .info{ ng: { show: "enterprise.is_primary_producer && enterprise.sells=='own' && enterprise.producer_profile_only==false" } } + %h3 Producer Shop + + %p + %strong COST: %2 OF SALES, CAPPED AT $50 PER MONTH + + %p A Producer Shop allows producers to offer their products for sale to customers through their very own Open Food Network shop. + + %p Producer Shops may sell produce that has been grown by the producer in question, but do not allow for aggregation of produce from elsewhere. + + .info{ ng: { show: "enterprise.is_primary_producer && enterprise.sells=='any' && enterprise.producer_profile_only==false" } } + %h3 Producer Hub + + %p + %strong COST: %2 OF SALES, CAPPED AT $50 PER MONTH + + %p Producer Hubs can take many forms, whether they be a CSA, a veggie-box program, or a food co-op with a rooftop garden. + + %p The Open Food Network aims to support as many hub models as possible, so no matter your situation, we want to provide the tools you need to run your organisation or local food business. + + .omega.eight.columns + %a.button.selector{ ng: { if: "!enterprise.is_primary_producer", click: "enterprise.sells='none'; enterprise.producer_profile_only=false;", class: "{selected: enterprise.sells=='none'}" } } + .top + %h3 Profile Only + %p Sell through other shops + .bottom ALWAYS FREE + %a.button.selector{ ng: { if: "!enterprise.is_primary_producer", click: "enterprise.sells='any'; enterprise.producer_profile_only=false;", class: "{selected: enterprise.sells=='any'}" } } + .top + %h3 Hub Shop + %p Sell through other shops + .bottom ALWAYS FREE + + %a.button.selector{ ng: { if: "enterprise.is_primary_producer", click: "enterprise.sells='none'; enterprise.producer_profile_only=true;", class: "{selected: enterprise.sells=='none' && enterprise.producer_profile_only==true}" } } .top %h3 Profile Only %p Connect through OFN .bottom ALWAYS FREE - %a.button.selector{ ng: { click: "enterprise.sells='none' && enterprise.producer_profile_only=false", class: "{selected: enterprise.sells=='none' && enterprise.producer_profile_only==false}" } } + %a.button.selector{ ng: { if: "enterprise.is_primary_producer", click: "enterprise.sells='none'; enterprise.producer_profile_only=false;", class: "{selected: enterprise.sells=='none' && enterprise.producer_profile_only==false}" } } .top %h3 No Shop %p Sell through other shops .bottom ALWAYS FREE - %a.button.selector{ ng: { click: "enterprise.sells='own' && enterprise.producer_profile_only=false", class: "{selected: enterprise.sells=='own'}" } } + %a.button.selector{ ng: { if: "enterprise.is_primary_producer", click: "enterprise.sells='own';enterprise.producer_profile_only=false;", class: "{selected: enterprise.sells=='own'}" } } .top %h3 Producer Shop %p Sell your own produce @@ -70,31 +101,22 @@ \%2 OF SALES %br CAPPED AT $50 PER MONTH - %a.button.selector{ ng: { click: "enterprise.sells='any' && enterprise.producer_profile_only=false", class: "{selected: enterprise.sells=='any'}" } } + %a.button.selector{ ng: { if: "enterprise.is_primary_producer", click: "enterprise.sells='any';enterprise.producer_profile_only=false;", class: "{selected: enterprise.sells=='any'}" } } .top - %h3 Producer Hub + %h3 Hub Shop %p Aggregate and sell produce .bottom \%2 OF SALES %br CAPPED AT $50 PER MONTH - .omega.eight.columns - %a.button.update.fullwidth{ ng: { class: "{disabled: saved()}" } } - %span{ ng: {show: "saved()" } } + + %a.button.update.fullwidth{ ng: { class: "{disabled: saved() && !saving, saving: saving}", click: "save()" } } + %span{ ng: {hide: "saved() || saving" } } + SAVE + %i.icon-save + %span{ ng: {show: "saved() && !saving" } } SAVED %i.icon-ok-sign - %span{ ng: {hide: "saved()" } } - SAVE - %i.icon-upload-alt - - .info{ ng: { show: "enterprise.is_primary_producer==true && enterprise.sells='any'" } } - %h3 Producer Profile - %p Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it. - - %p Being a producer does not limit an enterprise at all, producers can aggreagate food from other enterprises and sell it through shops on the Open Food Network. - - - .info{ ng: { show: "enterprise.is_primary_producer==false" } } - %h3 Non-Producer - %p - This enterprise does not produce any food itself, which means that it is probably involved in aggregating, selling and/or delivering food to the end eater. + %span{ ng: {show: "saving" } } + SAVING + %i.icon-refresh diff --git a/app/assets/stylesheets/admin/enterprise_index_panels.css.scss b/app/assets/stylesheets/admin/enterprise_index_panels.css.scss index 5037149652..21079462f5 100644 --- a/app/assets/stylesheets/admin/enterprise_index_panels.css.scss +++ b/app/assets/stylesheets/admin/enterprise_index_panels.css.scss @@ -37,6 +37,13 @@ &.disabled { background-color: #C1C1C1; } + &.saving { + background-color: #FF9848; + i.icon-refresh { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + } span{ i{ font-size: 1.5rem; diff --git a/app/assets/stylesheets/admin/panel_rows.css.scss b/app/assets/stylesheets/admin/panel_rows.css.scss index 1fdfe83293..e877a12c35 100644 --- a/app/assets/stylesheets/admin/panel_rows.css.scss +++ b/app/assets/stylesheets/admin/panel_rows.css.scss @@ -8,9 +8,10 @@ tr.panel-row { } td { + border-color: #000000; padding: 0; .panel { - border: 3px solid black; + border: 2px solid #000000; .row{ margin: 0px -4px; diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 8ef3021f54..276a0ad333 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -32,6 +32,24 @@ module Admin end end + def update + invoke_callbacks(:update, :before) + if @object.update_attributes(params[object_name]) + invoke_callbacks(:update, :after) + flash[:success] = flash_message_for(@object, :successfully_updated) + respond_with(@object) do |format| + format.html { redirect_to location_after_save } + format.js { render :layout => false } + format.json { render json: @object, serializer: Api::Admin::BasicEnterpriseSerializer } + end + else + invoke_callbacks(:update, :fails) + respond_with(@object) do |format| + format.json { render json: { errors: @object.errors.messages }, status: :unprocessable_entity } + end + end + end + def set_sells enterprise = Enterprise.find_by_permalink(params[:id]) || Enterprise.find(params[:id]) attributes = { sells: params[:sells] } diff --git a/app/serializers/api/admin/basic_enterprise_serializer.rb b/app/serializers/api/admin/basic_enterprise_serializer.rb index f060dc4166..854b9b4019 100644 --- a/app/serializers/api/admin/basic_enterprise_serializer.rb +++ b/app/serializers/api/admin/basic_enterprise_serializer.rb @@ -1,4 +1,4 @@ class Api::Admin::BasicEnterpriseSerializer < ActiveModel::Serializer attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category, :payment_method_ids, :shipping_method_ids - attributes :producer_profile_only + attributes :producer_profile_only, :permalink end diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 3bd1ba31a7..0035acd2e3 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -61,12 +61,12 @@ %td.producer{ ng: { show: 'columns.producer.visible' } } %panel-toggle{ name: "producer", object: "enterprise" } %a.button.fullwidth - %span{ bo: { bind: "enterprise.is_primary_producer" } } + %span{ ng: { bind: "enterprise.is_primary_producer" } } %i.icon-arrow-down %td.shop{ ng: { show: 'columns.shop.visible' } } %panel-toggle{ name: "shop", object: "enterprise" } %a.button.fullwidth - %span{ bo: { bind: "enterprise.sells" } } + %span{ ng: { bind: "enterprise.sells" } } %i.icon-arrow-down %td.status{ ng: { show: 'columns.status.visible' } } %panel-toggle{ name: "status", object: "enterprise" } diff --git a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee index 424adecc1a..6e8bdd8040 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee @@ -4,14 +4,12 @@ describe "EnterprisesCtrl", -> Enterprises = null beforeEach -> - shops = "list of shops" - module('admin.enterprises') inject ($controller, $rootScope, _Enterprises_) -> scope = $rootScope Enterprises = _Enterprises_ spyOn(Enterprises, "index").andReturn "list of enterprises" - ctrl = $controller 'enterprisesCtrl', {$scope: scope, Enterprises: Enterprises, shops: shops} + ctrl = $controller 'enterprisesCtrl', {$scope: scope, Enterprises: Enterprises} describe "setting the shop on scope", -> it "calls Enterprises#index with the correct params", -> diff --git a/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee new file mode 100644 index 0000000000..2792f33465 --- /dev/null +++ b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee @@ -0,0 +1,46 @@ +describe "indexPanelCtrl", -> + ctrl = null + scope = null + Enterprises = null + + beforeEach -> + module('admin.enterprises') + inject ($controller, $rootScope, _Enterprises_) -> + scope = $rootScope.$new() + $rootScope.object = { some: "object" } + Enterprises = _Enterprises_ + ctrl = $controller 'indexPanelCtrl', {$scope: scope, Enterprises: Enterprises} + + describe "initialisation", -> + it "pulls object from the parent scope and points the 'enterprise' on the current scope to it", inject ($rootScope) -> + expect(scope.enterprise).toBe $rootScope.object + + describe "saving changes on an enterprise", -> + describe "when changes have been made", -> + deferred = null + + beforeEach inject ($q) -> + spyOn(scope, "saved").andReturn false + deferred = $q.defer() + spyOn(Enterprises, "save").andReturn(deferred.promise) + scope.save() + + it "sets scope.saving to true", -> + expect(scope.saving).toBe true + + describe "when the save is successful", -> + beforeEach inject ($rootScope) -> + deferred.resolve() + $rootScope.$digest() + + + it "sets scope.saving to false", -> + expect(scope.saving).toBe false + + describe "when the save is unsuccessful", -> + beforeEach inject ($rootScope) -> + deferred.reject({ status: 404 }) + $rootScope.$digest() + + it "sets scope.saving to false", -> + expect(scope.saving).toBe false diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee index d695ab27b7..ba184045d0 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee @@ -8,12 +8,13 @@ describe "Enterprises service", -> Enterprises = _Enterprises_ EnterpriseResource = _EnterpriseResource_ $httpBackend = _$httpBackend_ - $httpBackend.expectGET('/admin/enterprises.json').respond 200, [{ id: 5, name: 'Enterprise 1'}] + describe "#index", -> result = null beforeEach -> + $httpBackend.expectGET('/admin/enterprises.json').respond 200, [{ id: 5, name: 'Enterprise 1'}] expect(Enterprises.loaded).toBe false result = Enterprises.index() $httpBackend.flush() @@ -29,3 +30,76 @@ describe "Enterprises service", -> it "sets @loaded to true", -> expect(Enterprises.loaded).toBe true + + + describe "#save", -> + result = null + + describe "success", -> + enterprise = null + resolved = false + + beforeEach -> + enterprise = new EnterpriseResource( { id: 15, permalink: 'enterprise1', name: 'Enterprise 1' } ) + $httpBackend.expectPUT('/admin/enterprises/enterprise1.json').respond 200, { id: 15, name: 'Enterprise 1'} + Enterprises.save(enterprise).then( -> resolved = true) + $httpBackend.flush() + + it "updates the pristine copy of the enterprise", -> + # Resource results have extra properties ($then, $promise) that cause them to not + # be exactly equal to the response object provided to the expectPUT clause above. + expect(Enterprises.pristine_by_id[15]).toEqual enterprise + + it "resolves the promise", -> + expect(resolved).toBe(true); + + + describe "failure", -> + enterprise = null + rejected = false + + beforeEach -> + enterprise = new EnterpriseResource( { id: 15, permalink: 'permalink', name: 'Enterprise 1' } ) + $httpBackend.expectPUT('/admin/enterprises/permalink.json').respond 422, { error: 'obj' } + Enterprises.save(enterprise).catch( -> rejected = true) + $httpBackend.flush() + + it "does not update the pristine copy of the enterprise", -> + expect(Enterprises.pristine_by_id[15]).toBeUndefined() + + it "rejects the promise", -> + expect(rejected).toBe(true); + + describe "#saved", -> + describe "when attributes of the object have been altered", -> + beforeEach -> + spyOn(Enterprises, "diff").andReturn ["attr1", "attr2"] + + it "returns false", -> + expect(Enterprises.saved({})).toBe false + + describe "when attributes of the object have not been altered", -> + beforeEach -> + spyOn(Enterprises, "diff").andReturn [] + + it "returns false", -> + expect(Enterprises.saved({})).toBe true + + + describe "diff", -> + beforeEach -> + Enterprises.pristine_by_id = { 23: { id: 23, name: "ent1", is_primary_producer: true } } + + it "returns a list of properties that have been altered", -> + expect(Enterprises.diff({ id: 23, name: "enterprise123", is_primary_producer: true })).toEqual ["name"] + + + describe "resetAttribute", -> + enterprise = { id: 23, name: "ent1", is_primary_producer: true } + + beforeEach -> + Enterprises.pristine_by_id = { 23: { id: 23, name: "enterprise1", is_primary_producer: true } } + + it "resets the specified value according to the pristine record", -> + Enterprises.resetAttribute(enterprise, "name") + expect(enterprise.name).toEqual "enterprise1"