From 06f10398da77a3c19ab2f5c9a53ca442b1b7713f Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 6 Mar 2015 11:22:52 +1100 Subject: [PATCH] Adding product property filter to shop page --- .../darkswarm/filters/properties_of.js.coffee | 7 ++++ .../darkswarm/services/products.js.coffee | 15 ++++---- .../darkswarm/services/properties.js.coffee | 9 +++++ app/helpers/injection_helper.rb | 4 +++ app/serializers/api/id_name_serializer.rb | 3 ++ app/serializers/api/product_serializer.rb | 2 +- app/views/layouts/darkswarm.html.haml | 3 +- app/views/shop/products/_filters.html.haml | 14 ++------ .../consumer/shopping/shopping_spec.rb | 33 +++++++++-------- .../products_controller_spec.js.coffee | 14 +++++--- ...spec.js.coffee => products_spec.js.coffee} | 35 +++++++++++++++---- .../services/properties_spec.js.coffee | 16 +++++++++ 12 files changed, 105 insertions(+), 50 deletions(-) create mode 100644 app/assets/javascripts/darkswarm/filters/properties_of.js.coffee create mode 100644 app/assets/javascripts/darkswarm/services/properties.js.coffee create mode 100644 app/serializers/api/id_name_serializer.rb rename spec/javascripts/unit/darkswarm/services/{product_spec.js.coffee => products_spec.js.coffee} (74%) create mode 100644 spec/javascripts/unit/darkswarm/services/properties_spec.js.coffee diff --git a/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee b/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee new file mode 100644 index 0000000000..4ab8c94bfd --- /dev/null +++ b/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee @@ -0,0 +1,7 @@ +Darkswarm.filter 'propertiesOf', -> + (objects)-> + properties = {} + for object in objects + for property in object.properties + properties[property.id] = property + properties diff --git a/app/assets/javascripts/darkswarm/services/products.js.coffee b/app/assets/javascripts/darkswarm/services/products.js.coffee index 9da3187f82..db8bbbb7ee 100644 --- a/app/assets/javascripts/darkswarm/services/products.js.coffee +++ b/app/assets/javascripts/darkswarm/services/products.js.coffee @@ -1,27 +1,28 @@ -Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Cart, Variants) -> +Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Properties, Cart, Variants) -> new class Products constructor: -> @update() - + # TODO: don't need to scope this into object # Already on object as far as controller scope is concerned products: null loading: true update: => - @loading = true + @loading = true @products = $resource("/shop/products").query (products)=> @extend() && @dereference() - @registerVariants() + @registerVariants() @registerVariantsWithCart() @loading = false @ - + dereference: -> for product in @products product.supplier = Enterprises.enterprises_by_id[product.supplier.id] Dereferencer.dereference product.taxons, Taxons.taxons_by_id - + Dereferencer.dereference product.properties, Properties.properties_by_id + # May return different objects! If the variant has already been registered # by another service, we fetch those registerVariants: -> @@ -45,7 +46,7 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car prices = (v.price for v in product.variants) product.price = Math.min.apply(null, prices) product.hasVariants = product.variants?.length > 0 - + product.primaryImage = product.images[0]?.small_url if product.images product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png" product.largeImage = product.images[0]?.large_url if product.images diff --git a/app/assets/javascripts/darkswarm/services/properties.js.coffee b/app/assets/javascripts/darkswarm/services/properties.js.coffee new file mode 100644 index 0000000000..37314947b4 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/properties.js.coffee @@ -0,0 +1,9 @@ +Darkswarm.factory "Properties", (properties)-> + new class Properties + # Populate ProductProperties.properties from json in page. + properties: properties + properties_by_id: {} + constructor: -> + # Map properties to id/object pairs for lookup. + for property in @properties + @properties_by_id[property.id] = property diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index a168d0284d..37794cef9d 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -21,6 +21,10 @@ module InjectionHelper inject_json_ams "taxons", Spree::Taxon.all, Api::TaxonSerializer end + def inject_properties + inject_json_ams "properties", Spree::Property.all, Api::IdNameSerializer + end + def inject_currency_config inject_json_ams "currencyConfig", {}, Api::CurrencyConfigSerializer end diff --git a/app/serializers/api/id_name_serializer.rb b/app/serializers/api/id_name_serializer.rb new file mode 100644 index 0000000000..1db5f2439e --- /dev/null +++ b/app/serializers/api/id_name_serializer.rb @@ -0,0 +1,3 @@ +class Api::IdNameSerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/api/product_serializer.rb b/app/serializers/api/product_serializer.rb index 0026290b6d..78f7593e4f 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -35,7 +35,7 @@ class Api::CachedProductSerializer < ActiveModel::Serializer has_many :variants, serializer: Api::VariantSerializer has_many :taxons, serializer: Api::IdSerializer - has_many :properties, serializer: Api::PropertySerializer + has_many :properties, serializer: Api::IdSerializer has_many :images, serializer: Api::ImageSerializer has_one :supplier, serializer: Api::IdSerializer diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 0c6ec608d4..e1976ab167 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -28,6 +28,7 @@ = inject_json "user", "current_user" = inject_json "railsFlash", "flash" = inject_taxons + = inject_properties = inject_current_order = inject_currency_config @@ -37,6 +38,6 @@ %section{ role: "main" } = yield - + #footer %loading diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index 3cb72a4853..9a347ddee0 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,15 +1,5 @@ .filter-box.filter-box-shopfront.animate-hide.text-right %single-line-selectors{ objects: "Products.products | products:query | taxonsOf", "active-selectors" => "activeTaxons"} --# .filter-box.property-box-shopfront.animate-hide.text-right --# %ul --# %li --# %a Organic Certified --# %li --# %a Free Range --# %li --# %a Biodynamic --# %li --# %a --# + 2 more --# %span.caret +.filter-box.property-box-shopfront.animate-hide.text-right + %single-line-selectors{ objects: "Products.products | products:query | propertiesOf", "active-selectors" => "activeProperties"} diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index a47b73ba6c..d540280656 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -15,7 +15,7 @@ feature "As a consumer I want to shop with a distributor", js: true do let(:product) { create(:simple_product, supplier: supplier) } let(:order) { create(:order, distributor: distributor) } - before do + before do set_order order end @@ -28,23 +28,23 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path page.should have_text distributor.name find("#tab_about a").click - first("distributor img")['src'].should == distributor.logo.url(:thumb) + first("distributor img")['src'].should == distributor.logo.url(:thumb) end it "shows the producers for a distributor" do - exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) exchange.variants << product.master visit shop_path find("#tab_producers a").click - page.should have_content supplier.name + page.should have_content supplier.name end describe "selecting an order cycle" do let(:exchange1) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } it "selects an order cycle if only one is open" do - exchange1.update_attribute :pickup_time, "turtles" + exchange1.update_attribute :pickup_time, "turtles" visit shop_path page.should have_selector "option[selected]", text: 'turtles' end @@ -52,8 +52,8 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "with multiple order cycles" do let(:exchange2) { Exchange.find(oc2.exchanges.to_enterprises(distributor).outgoing.first.id) } before do - exchange1.update_attribute :pickup_time, "frogs" - exchange2.update_attribute :pickup_time, "turtles" + exchange1.update_attribute :pickup_time, "frogs" + exchange2.update_attribute :pickup_time, "turtles" end it "shows a select with all order cycles, but doesn't show the products by default" do @@ -62,20 +62,20 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should have_selector "option", text: 'turtles' page.should_not have_selector("input.button.right", visible: true) end - + it "shows products after selecting an order cycle" do product.master.update_attribute(:display_name, "kitten") product.master.update_attribute(:display_as, "rabbit") exchange1.variants << product.master ## add product to exchange visit shop_path - page.should_not have_content product.name + page.should_not have_content product.name Spree::Order.last.order_cycle.should == nil select "frogs", :from => "order_cycle_id" page.should have_selector "products" - page.should have_content "Next order closing in 2 days" + page.should have_content "Next order closing in 2 days" Spree::Order.last.order_cycle.should == oc1 - page.should have_content product.name + page.should have_content product.name page.should have_content product.master.display_name page.should have_content product.master.display_as @@ -88,10 +88,10 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "after selecting an order cycle with products visible" do let(:variant1) { create(:variant, product: product, price: 20) } let(:variant2) { create(:variant, product: product, price: 30) } - let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } + let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } before do - exchange.update_attribute :pickup_time, "frogs" + exchange.update_attribute :pickup_time, "frogs" exchange.variants << product.master exchange.variants << variant1 exchange.variants << variant2 @@ -134,7 +134,7 @@ feature "As a consumer I want to shop with a distributor", js: true do set_order_cycle(order, oc1) visit shop_path end - + it "should save group buy data to ze cart" do fill_in "variants[#{product.master.id}]", with: 5 fill_in "variant_attributes[#{product.master.id}][max_quantity]", with: 9 @@ -145,7 +145,7 @@ feature "As a consumer I want to shop with a distributor", js: true do li.max_quantity.should == 9 li.quantity.should == 5 end - + # TODO move to controller test pending "adding a product with a max quantity less than quantity results in max_quantity==quantity" do fill_in "variants[#{product.master.id}]", with: 5 @@ -165,7 +165,7 @@ feature "As a consumer I want to shop with a distributor", js: true do set_order_cycle(order, oc1) visit shop_path end - + it "should save group buy data to the cart" do fill_in "variants[#{variant.id}]", with: 6 fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 7 @@ -213,4 +213,3 @@ feature "As a consumer I want to shop with a distributor", js: true do end end end - diff --git a/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee index 73ecd611ef..67aced98c6 100644 --- a/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee @@ -4,19 +4,23 @@ describe 'ProductsCtrl', -> event = null Products = null Cart = {} + Taxons = null beforeEach -> module('Darkswarm') - Products = + Products = all: -> update: -> products: ["testy mctest"] + loading: false OrderCycle = order_cycle: {} - - inject ($controller) -> - scope = {} - ctrl = $controller 'ProductsCtrl', {$scope: scope, Products: Products, OrderCycle: OrderCycle, Cart: Cart} + Taxons: + taxons: [] + + inject ($rootScope, $controller) -> + scope = $rootScope + ctrl = $controller 'ProductsCtrl', {$scope: scope, Products: Products, OrderCycle: OrderCycle, Cart: Cart, Taxons: Taxons} it 'fetches products from Products', -> expect(scope.Products.products).toEqual ['testy mctest'] diff --git a/spec/javascripts/unit/darkswarm/services/product_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee similarity index 74% rename from spec/javascripts/unit/darkswarm/services/product_spec.js.coffee rename to spec/javascripts/unit/darkswarm/services/products_spec.js.coffee index 27e3f5aff6..2cb23522e2 100644 --- a/spec/javascripts/unit/darkswarm/services/product_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee @@ -4,13 +4,15 @@ describe 'Products service', -> Enterprises = null Variants = null Cart = null - CurrentHubMock = {} + CurrentHubMock = {} currentOrder = null product = null productWithImage = null + properties = null + taxons = null beforeEach -> - product = + product = test: "cats" supplier: id: 9 @@ -27,16 +29,23 @@ describe 'Products service', -> ] currentOrder = line_items: [] + properties = + { id: 1, name: "some property" } + taxons = + { id: 2, name: "some taxon" } module 'Darkswarm' module ($provide)-> - $provide.value "CurrentHub", CurrentHubMock - $provide.value "currentOrder", currentOrder + $provide.value "CurrentHub", CurrentHubMock + $provide.value "currentOrder", currentOrder + $provide.value "taxons", taxons + $provide.value "properties", properties null inject ($injector, _$httpBackend_)-> Products = $injector.get("Products") Enterprises = $injector.get("Enterprises") + Properties = $injector.get("Properties") Variants = $injector.get("Variants") Cart = $injector.get("Cart") $httpBackend = _$httpBackend_ @@ -44,20 +53,32 @@ describe 'Products service', -> it "Fetches products from the backend on init", -> $httpBackend.expectGET("/shop/products").respond([product]) $httpBackend.flush() - expect(Products.products[0].test).toEqual "cats" + expect(Products.products[0].test).toEqual "cats" it "dereferences suppliers", -> - Enterprises.enterprises_by_id = + Enterprises.enterprises_by_id = {id: 9, name: "test"} $httpBackend.expectGET("/shop/products").respond([{supplier : {id: 9}, master: {}}]) $httpBackend.flush() expect(Products.products[0].supplier).toBe Enterprises.enterprises_by_id["9"] + it "dereferences taxons", -> + product.taxons = [2] + $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.flush() + expect(Products.products[0].taxons[1]).toBe taxons[0] + + it "dereferences properties", -> + product.properties = [1] + $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.flush() + expect(Products.products[0].properties[1]).toBe properties[0] + it "registers variants with Variants service", -> product.variants = [{id: 1}] $httpBackend.expectGET("/shop/products").respond([product]) $httpBackend.flush() - expect(Products.products[0].variants[0]).toBe Variants.variants[1] + expect(Products.products[0].variants[0]).toBe Variants.variants[1] it "registers variants with the Cart", -> product.variants = [{id: 8}] diff --git a/spec/javascripts/unit/darkswarm/services/properties_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/properties_spec.js.coffee new file mode 100644 index 0000000000..d40d4f8901 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/properties_spec.js.coffee @@ -0,0 +1,16 @@ +describe "Properties service", -> + Properties = null + properties = [ + {id: 1, name: "Property1"} + {id: 2, name: "Property2"} + ] + + beforeEach -> + module('Darkswarm') + angular.module('Darkswarm').value 'properties', properties + + inject ($injector)-> + Properties = $injector.get("Properties") + + it "caches properties in an id-referenced hash", -> + expect(Properties.properties_by_id[1]).toBe properties[0]