diff --git a/Gemfile b/Gemfile index 9c977995fe..3271c30756 100644 --- a/Gemfile +++ b/Gemfile @@ -85,4 +85,10 @@ end group :development do gem 'pry-debugger' gem 'debugger-linecache' + gem 'guard' + gem 'guard-livereload' + gem 'rack-livereload' + gem 'guard-rails' + gem 'guard-zeus' + gem 'guard-rspec' end diff --git a/Gemfile.lock b/Gemfile.lock index 11c366f984..78b60d1977 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -182,6 +182,8 @@ GEM rack-test (>= 0.5.4) selenium-webdriver (~> 2.0) xpath (~> 1.0.0) + celluloid (0.15.2) + timers (~> 1.1.0) childprocess (0.3.9) ffi (~> 1.0, >= 1.0.11) chunky_png (1.2.8) @@ -230,6 +232,9 @@ GEM devise-encryptable (0.1.2) devise (>= 2.1.0) diff-lcs (1.2.4) + em-websocket (0.5.0) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.5.3) erubis (2.7.0) eventmachine (1.0.3) excon (0.25.3) @@ -260,6 +265,24 @@ GEM fssm (0.2.10) geocoder (1.1.8) gmaps4rails (1.5.6) + guard (2.2.4) + formatador (>= 0.2.4) + listen (~> 2.1) + lumberjack (~> 1.0) + pry (>= 0.9.12) + thor (>= 0.18.1) + guard-livereload (2.0.1) + em-websocket (~> 0.5) + guard (~> 2.0) + multi_json (~> 1.8) + guard-rails (0.4.7) + guard (>= 0.2.2) + guard-rspec (4.0.4) + guard (>= 2.1.1) + rspec (~> 2.14) + guard-zeus (0.0.1) + guard + zeus haml (4.0.4) tilt highline (1.6.18) @@ -287,6 +310,11 @@ GEM letter_opener (1.0.0) launchy (>= 2.0.4) libv8 (3.16.14.3) + listen (2.2.0) + celluloid (>= 0.15.2) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + lumberjack (1.0.4) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) @@ -332,6 +360,8 @@ GEM rack (1.4.5) rack-cache (1.2) rack (>= 0.4) + rack-livereload (0.3.15) + rack rack-ssl (1.3.3) rack rack-test (0.6.2) @@ -357,6 +387,9 @@ GEM actionpack (~> 3.0) activerecord (~> 3.0) polyamorous (~> 0.5.0) + rb-fsevent (0.9.3) + rb-inotify (0.9.2) + ffi (>= 0.5.0) rdoc (3.12.2) json (~> 1.4) ref (1.0.5) @@ -420,6 +453,7 @@ GEM thor (0.18.1) tilt (1.4.1) timecop (0.6.2.2) + timers (1.1.0) treetop (1.4.15) polyglot polyglot (>= 0.3.1) @@ -453,6 +487,8 @@ GEM websocket (1.0.7) xpath (1.0.0) nokogiri (~> 1.3) + zeus (0.13.3) + method_source (>= 0.6.7) PLATFORMS ruby @@ -476,6 +512,11 @@ DEPENDENCIES faker geocoder gmaps4rails + guard + guard-livereload + guard-rails + guard-rspec + guard-zeus haml jquery-rails json_spec @@ -488,6 +529,7 @@ DEPENDENCIES poltergeist pry-debugger rabl + rack-livereload rack-ssl rails (= 3.2.14) representative_view diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000000..5205fdcf62 --- /dev/null +++ b/Guardfile @@ -0,0 +1,50 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard 'livereload' do + watch(%r{app/views/.+\.(erb|haml|slim)$}) + watch(%r{app/helpers/.+\.rb}) + watch(%r{public/.+\.(css|js|html)}) + #watch(%r{config/locales/.+\.yml}) + # Rails Assets Pipeline + watch(%r{(app|vendor)(/assets/\w+/(.+\.(css|js|html|png|jpg))).*}) { |m| "/assets/#{m[3]}" } +end + + +#guard 'rails' do + #watch('Gemfile.lock') + #watch(%r{^(config|lib)/.*}) +#end + + +#guard 'zeus' do + ## uses the .rspec file + ## --colour --fail-fast --format documentation --tag ~slow + #watch(%r{^spec/.+_spec\.rb$}) + #watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + #watch(%r{^app/(.+)\.haml$}) { |m| "spec/#{m[1]}.haml_spec.rb" } + #watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + #watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/requests/#{m[1]}_spec.rb"] } +#end + +#guard :rspec do + #watch(%r{^spec/.+_spec\.rb$}) + #watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + #watch('spec/spec_helper.rb') { "spec" } + + ## Rails example + #watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + #watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } + #watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } + #watch(%r{^spec/support/(.+)\.rb$}) { "spec" } + #watch('config/routes.rb') { "spec/routing" } + #watch('app/controllers/application_controller.rb') { "spec/controllers" } + + ## Capybara features specs + #watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" } + + ## Turnip features and steps + #watch(%r{^spec/acceptance/(.+)\.feature$}) + #watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } +#end + diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 0000000000..aca7aac1dc Binary files /dev/null and b/app/assets/images/logo.png differ diff --git a/app/assets/images/matte.png b/app/assets/images/matte.png new file mode 100644 index 0000000000..845642cab2 Binary files /dev/null and b/app/assets/images/matte.png differ diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index da7f5af956..0e8b4e946f 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -65,6 +65,7 @@ productsApp.directive "ofnToggleVariants", -> element.addClass "icon-chevron-down" + productsApp.directive "ofnToggleColumn", -> link: (scope, element, attrs) -> element.addClass "unselected" unless scope.column.visible @@ -77,24 +78,6 @@ productsApp.directive "ofnToggleColumn", -> scope.column.visible = true element.removeClass "unselected" - -productsApp.directive "ofnToggleColumnList", [ - "$compile" - ($compile) -> - return link: (scope, element, attrs) -> - dialogDiv = element.next() - element.on "click", -> - pos = element.position() - height = element.outerHeight() - dialogDiv.css( - position: "absolute" - top: (pos.top + height) + "px" - left: pos.left + "px" - ).toggle() - -] - - productsApp.directive "datetimepicker", [ "$parse" ($parse) -> @@ -126,7 +109,7 @@ productsApp.controller "AdminBulkProductsCtrl", [ unit: {name: "Unit", visible: true} price: {name: "Price", visible: true} on_hand: {name: "On Hand", visible: true} - available_on: {name: "Available On", visible: true} + available_on: {name: "Available On", visible: false} $scope.variant_unit_options = [ ["Weight (g)", "weight_1"], @@ -138,6 +121,39 @@ productsApp.controller "AdminBulkProductsCtrl", [ ["Items", "items"] ] + $scope.filterableColumns = [ + { name: "Supplier", db_column: "supplier_name" }, + { name: "Name", db_column: "name" } + ] + + $scope.filterTypes = [ + { name: "Equals", predicate: "eq" }, + { name: "Contains", predicate: "cont" } + ] + + $scope.optionTabs = + filters: { title: "Filter Products", visible: false } + column_toggle: { title: "Toggle Columns", visible: false } + + $scope.visibleTab = { title: "Lala" } + $scope.perPage = 25 + $scope.currentPage = 1 + $scope.products = [] + $scope.filteredProducts = [] + $scope.currentFilters = [] + $scope.totalCount = -> $scope.filteredProducts.length + $scope.totalPages = -> Math.ceil($scope.totalCount()/$scope.perPage) + $scope.firstVisibleProduct = -> ($scope.currentPage-1)*$scope.perPage+1 + $scope.lastVisibleProduct = -> Math.min($scope.totalCount(),$scope.currentPage*$scope.perPage) + $scope.setPage = (page) -> $scope.currentPage = page + $scope.minPage = -> Math.max(1,Math.min($scope.totalPages()-4,$scope.currentPage-2)) + $scope.maxPage = -> Math.min($scope.totalPages(),Math.max(5,$scope.currentPage+2)) + + $scope.$watch -> + $scope.totalPages() + , (newVal, oldVal) -> + $scope.currentPage = Math.max $scope.totalPages(), 1 if newVal != oldVal && $scope.totalPages() < $scope.currentPage + $scope.initialise = (spree_api_key) -> authorise_api_reponse = "" dataFetcher("/api/users/authorise_api?token=" + spree_api_key).then (data) -> @@ -148,14 +164,23 @@ productsApp.controller "AdminBulkProductsCtrl", [ dataFetcher("/api/enterprises/managed?template=bulk_index&q[is_primary_producer_eq]=true").then (data) -> $scope.suppliers = data # Need to have suppliers before we get products so we can match suppliers to product.supplier - dataFetcher("/api/products/managed?template=bulk_index;page=1;per_page=500").then (data) -> - $scope.resetProducts data + $scope.fetchProducts() else if authorise_api_reponse.hasOwnProperty("error") $scope.api_error_msg = authorise_api_reponse("error") else api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access." + $scope.fetchProducts = -> # WARNING: returns a promise + $scope.loading = true + queryString = $scope.currentFilters.reduce (qs,f) -> + return qs + "q[#{f.property.db_column}_#{f.predicate.predicate}]=#{f.value};" + , "" + return dataFetcher("/api/products/managed?template=bulk_index;page=1;per_page=500;#{queryString}").then (data) -> + $scope.resetProducts data + $scope.loading = false + + $scope.resetProducts = (data) -> $scope.products = data $scope.dirtyProducts = {} @@ -214,6 +239,23 @@ productsApp.controller "AdminBulkProductsCtrl", [ onHand = "error" onHand + $scope.shiftTab = (tab) -> + $scope.visibleTab.visible = false unless $scope.visibleTab == tab + tab.visible = !tab.visible + $scope.visibleTab = tab + + $scope.addFilter = (filter) -> + if $scope.filterableColumns.indexOf(filter.property) >= 0 + if $scope.filterTypes.indexOf(filter.predicate) >= 0 + $scope.currentFilters.push filter + $scope.fetchProducts() + + $scope.removeFilter = (filter) -> + index = $scope.currentFilters.indexOf(filter) + if index != -1 + $scope.currentFilters.splice index, 1 + $scope.fetchProducts() + $scope.editWarn = (product, variant) -> if ($scope.dirtyProductCount() > 0 and confirm("Unsaved changes will be lost. Continue anyway?")) or ($scope.dirtyProductCount() == 0) @@ -265,11 +307,19 @@ productsApp.controller "AdminBulkProductsCtrl", [ $http( method: "POST" url: "/admin/products/bulk_update" - data: productsToSubmit + data: + products: productsToSubmit + filters: $scope.currentFilters ).success((data) -> + # TODO: remove this check altogether, need to write controller tests if we want to test this behaviour properly + # Note: Rob implemented subset(), which is a simpler alternative to productsWithoutDerivedAttributes(). However, it + # conflicted with some changes I made before merging my work, so for now I've reverted to the old way of + # doing things. TODO: Review together and decide on strategy here. -- Rohan, 14-1-2014 + #if subset($scope.productsWithoutDerivedAttributes(), data) + if angular.toJson($scope.productsWithoutDerivedAttributes($scope.products)) == angular.toJson($scope.productsWithoutDerivedAttributes(data)) $scope.resetProducts data - $scope.displaySuccess() + $timeout -> $scope.displaySuccess() else $scope.displayFailure "Product lists do not match." ).error (data, status) -> @@ -285,8 +335,10 @@ productsApp.controller "AdminBulkProductsCtrl", [ $scope.packProduct product productsToSubmit = filterSubmitProducts($scope.dirtyProducts) - $scope.updateProducts productsToSubmit - + if productsToSubmit.length > 0 + $scope.updateProducts productsToSubmit # Don't submit an empty list + else + $scope.setMessage $scope.updateStatusMessage, "No changes to update.", color: "grey", 3000 $scope.packProduct = (product) -> if product.variant_unit_with_scale @@ -394,6 +446,10 @@ productsApp.factory "dataFetcher", [ deferred.promise ] +productsApp.filter "rangeArray", -> + return (input,start,end) -> + input.push(i) for i in [start..end] + input filterSubmitProducts = (productsToFilter) -> filteredProducts = [] @@ -477,3 +533,11 @@ toObjectWithIDKeys = (array) -> object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array object + +subset = (bigArray,smallArray) -> + if smallArray instanceof Array && bigArray instanceof Array && smallArray.length > 0 + for item in smallArray + return false if angular.toJson(bigArray).indexOf(angular.toJson(item)) == -1 + return true + else + return false diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee new file mode 100644 index 0000000000..96486a4a69 --- /dev/null +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -0,0 +1,14 @@ +#= require jquery +#= require jquery_ujs +#= require jquery-ui +#= require spin +#= require ../shared/angular +#= require ../shared/angular-resource +#= require foundation +#= require ./shop +#= require_tree . + +$ -> + $(document).foundation() + + diff --git a/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee new file mode 100644 index 0000000000..d16d31603c --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee @@ -0,0 +1,4 @@ +Shop.controller "OrderCycleCtrl", ($scope, $rootScope, OrderCycle) -> + $scope.order_cycle = OrderCycle.order_cycle + $scope.changeOrderCycle = -> + OrderCycle.push_order_cycle() diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee new file mode 100644 index 0000000000..8f21390a7f --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee @@ -0,0 +1,13 @@ +angular.module("Shop").controller "ProductsCtrl", ($scope, $rootScope, Product) -> + $scope.data = Product.data + Product.update() + + + #$scope.order_cycle = OrderCycle.order_cycle + #$scope.updateProducts = -> + #$scope.products = Product.all() + #$scope.$watch "order_cycle.order_cycle_id", $scope.updateProducts + #$scope.updateProducts() + + + diff --git a/app/assets/javascripts/darkswarm/overrides.js.coffee b/app/assets/javascripts/darkswarm/overrides.js.coffee new file mode 100644 index 0000000000..6db1441f08 --- /dev/null +++ b/app/assets/javascripts/darkswarm/overrides.js.coffee @@ -0,0 +1,20 @@ +Foundation.libs.section.toggle_active = (e)-> + $this = $(this) + self = Foundation.libs.section + region = $this.parent() + content = $this.siblings(self.settings.content_selector) + section = region.parent() + settings = $.extend({}, self.settings, self.data_options(section)) + prev_active_region = section.children(self.settings.region_selector).filter("." + self.settings.active_class) + + #for anchors inside [data-section-title] + e.preventDefault() if not settings.deep_linking and content.length > 0 + e.stopPropagation() #do not catch same click again on parent + unless region.hasClass(self.settings.active_class) + prev_active_region.removeClass self.settings.active_class + region.addClass self.settings.active_class + #force resize for better performance (do not wait timer) + self.resize region.find(self.settings.section_selector).not("[" + self.settings.resized_data_attr + "]"), true + else if not settings.one_up# and (self.small(section) or self.is_vertical_nav(section) or self.is_horizontal_nav(section) or self.is_accordion(section)) + region.removeClass self.settings.active_class + settings.callback section diff --git a/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee b/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee new file mode 100644 index 0000000000..042ea960e4 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee @@ -0,0 +1,7 @@ +Shop.factory 'OrderCycle', ($resource, Product, orderCycleData) -> + class OrderCycle + @order_cycle = orderCycleData || {orders_close_at: ""} + @push_order_cycle: -> + new $resource("/shop/order_cycle").save {order_cycle_id: @order_cycle.order_cycle_id}, (order_data)-> + OrderCycle.order_cycle.orders_close_at = order_data.orders_close_at + Product.update() diff --git a/app/assets/javascripts/darkswarm/services/product.js.coffee b/app/assets/javascripts/darkswarm/services/product.js.coffee new file mode 100644 index 0000000000..f8f139ad32 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/product.js.coffee @@ -0,0 +1,11 @@ +Shop.factory 'Product', ($resource) -> + new class Product + data: { + products: null + } + update: -> + @data.products = $resource("/shop/products").query => + #console.log @products + @data + all: -> + @data.products || @update() diff --git a/app/assets/javascripts/darkswarm/shop.js.coffee b/app/assets/javascripts/darkswarm/shop.js.coffee new file mode 100644 index 0000000000..85c94211e6 --- /dev/null +++ b/app/assets/javascripts/darkswarm/shop.js.coffee @@ -0,0 +1,14 @@ +window.Shop = angular.module("Shop", ["ngResource", "filters"]).config ($httpProvider) -> + $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') + +#angular.module('Shop', ['filters']) + +angular.module("filters", []).filter "truncate", -> + (text, length, end) -> + text = text || "" + length = 10 if isNaN(length) + end = "..." if end is `undefined` + if text.length <= length or text.length - end.length <= length + text + else + String(text).substring(0, length - end.length) + end diff --git a/app/assets/javascripts/distributors.js.coffee b/app/assets/javascripts/distributors.js.coffee new file mode 100644 index 0000000000..761567942f --- /dev/null +++ b/app/assets/javascripts/distributors.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/stylesheets/admin/products.css.scss b/app/assets/stylesheets/admin/products.css.scss index 923b6b6867..026e4cf83b 100644 --- a/app/assets/stylesheets/admin/products.css.scss +++ b/app/assets/stylesheets/admin/products.css.scss @@ -2,6 +2,59 @@ display: block; } +div.pagination { + div.pagenav { + margin: 0px; + span.first, span.prev, span.next, span.last { + padding: 5px 0px; + display:inline-block; + } + } +} + +div.pagination_info { + text-align: right; +} + + + +div.applied_filter { + margin-bottom: 5px; + border: solid 2px #5498da; + padding: 5px 0px; + border-radius: 5px; + div.four.columns { + padding-left: 10px; + } +} + +div.option_tabs { + div.applied_filters, div.filters, div.column_toggle { + margin-bottom: 10px; + } +} + +div.option_tab_titles { + h6 { + border-radius: 3px; + border: 1px solid #cee1f4; + padding: 3px; + text-align: center; + color: darken(#cee1f4, 3); + cursor: pointer; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + } + h6.selected { + border: 1px solid #5498da; + color: #5498da; + } + margin-bottom: 10px; +} + tbody.odd { tr.product { td { background-color: white; } } tr.variant.odd { td { background-color: lighten(#eff5fc, 3); } } @@ -27,11 +80,18 @@ li.column-list-item { border-radius: 3px; padding: 2px 20px; margin: 2px 1px; - border: 2px solid darken(#5498da, 3); + border: 2px solid #5498da; background-color: #5498da; color: white; font-size: 100%; cursor: default; + text-align: center; + cursor: pointer; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; } li.column-list-item.unselected { @@ -42,10 +102,7 @@ li.column-list-item.unselected { } ul.column-list { - padding: 5px 8px; - border-radius: 3px; - border: solid 1px darkgray; - list-style:none + list-style: none; } table#listing_products.bulk { diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 60db6ad7e5..db803ee40a 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -4,4 +4,4 @@ * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_self *= require_tree . -*/ \ No newline at end of file +*/ diff --git a/app/assets/stylesheets/darkswarm/all.scss b/app/assets/stylesheets/darkswarm/all.scss new file mode 100644 index 0000000000..b5d1542d11 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/all.scss @@ -0,0 +1,10 @@ +/* + * This is a manifest file that'll automatically include all the stylesheets available in this directory + * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at + * the top of the compiled file, but it's generally better to create a new file per style scope. + + *= require_self + + *= require foundation + *= require_tree . +*/ diff --git a/app/assets/stylesheets/darkswarm/footer.sass b/app/assets/stylesheets/darkswarm/footer.sass new file mode 100644 index 0000000000..fea9689201 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/footer.sass @@ -0,0 +1,12 @@ +@import variables + +#footer + padding: 74px 0px + background: $fawn + margin-top: 85px + + #copyright + clear: both + img + display: block + margin: 0px auto 8px diff --git a/app/assets/stylesheets/darkswarm/header.css.sass b/app/assets/stylesheets/darkswarm/header.css.sass new file mode 100644 index 0000000000..a33fa1b30d --- /dev/null +++ b/app/assets/stylesheets/darkswarm/header.css.sass @@ -0,0 +1,3 @@ +/*body { background: #ff0000; }*/ +nav.top-bar + margin-bottom: 0px diff --git a/app/assets/stylesheets/darkswarm/mixins.sass b/app/assets/stylesheets/darkswarm/mixins.sass new file mode 100644 index 0000000000..70554bac29 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/mixins.sass @@ -0,0 +1,8 @@ +@import typography + +@mixin big-input + border: 1px solid #999 + font-size: 18px + @extend .avenir + padding: 18px + margin-bottom: 1.25em diff --git a/app/assets/stylesheets/darkswarm/overrides.css.sass b/app/assets/stylesheets/darkswarm/overrides.css.sass new file mode 100644 index 0000000000..fcec6b455d --- /dev/null +++ b/app/assets/stylesheets/darkswarm/overrides.css.sass @@ -0,0 +1,2 @@ +.row + max-width: 74em diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass new file mode 100644 index 0000000000..0d6137fb3a --- /dev/null +++ b/app/assets/stylesheets/darkswarm/shop.css.sass @@ -0,0 +1,104 @@ +@import mixins +@import variables + +product + display: block + +shop + #search + font-size: 2em + @include big-input + color: #666 + display: block + navigation + display: block + background: $fawn + distributor.details + box-sizing: border-box + display: block + height: 150px + padding: 40px 0px 0px + select + width: 200px + position: relative + img + display: block + height: 100px + width: 100px + margin-right: 12px + location + font-family: "AvenirBla_IE", "AvenirBla" + padding-right: 16px + ordercycle + display: block + position: absolute + right: 0px + top: 40px + form.custom + width: 400px + text-align: right + & > strong + line-height: 2.5 + font-size: 1.29em + padding-right: 14px + .custom.dropdown + width: 280px + display: inline-block + background: transparent + border-width: 2px + border-color: #666666 + font-size: 1.28em + margin-bottom: 0 + closing + font-size: 0.875em + display: block + float: right + padding-top: 14px + + tabs + background: url("/assets/matte.png") top left repeat + display: block + .section-container.auto + margin-bottom: 0 + & > section + transition:height 0s linear 0.5s + & > .content + background: none + border: none + & > .title, &.active > .title + text-transform: uppercase + line-height: 50px + border: none + &, &:hover + background: none + a + padding: 0px 2.2em + + products + display: block + padding-top: 36px + table + width: 100% + border-collapse: collapse + border: none + th + line-height: 50px + .notes + max-width: 300px + td, th + background: #fff + border: 1px solid #cccccc + border-left: 0px + border-right: 0px + td + padding: 20px 0px + input[type=number] + width: 60px + margin: 0px + + + + display: block + float: right + padding-top: 14px + diff --git a/app/assets/stylesheets/darkswarm/typography.css.sass b/app/assets/stylesheets/darkswarm/typography.css.sass new file mode 100644 index 0000000000..0bf0b39e44 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/typography.css.sass @@ -0,0 +1,36 @@ +@font-face + font-family: 'AvenirBla_IE' + src: url("/AveniBla.eot") format("opentype") + +@font-face + font-family: 'AvenirBla' + src: url("/AvenirLTStd-Black.otf") format("opentype") + +@font-face + font-family: 'AvenirMed_IE' + src: url("/AveniMed.eot") format("opentype") + +@font-face + font-family: 'AvenirMed' + src: url("/AvenirLTStd-Medium.otf") format("opentype") + +//body + //font-family: "AvenirBla_IE", "AvenirBla" + +h1, h2, h3, h4, h5, h6, .avenir + color: #333333 + font-family: "AvenirBla_IE", "AvenirBla" + padding: 0px + +strong.avenir + font-weight: normal // Avenir is basically bold anyway + +td + font-family: "helvetica" + +// These selectors match the default Foundation selectors +// For clean overriden magic +table tr th, table tr td + color: #333333 +table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td + color: #333333 diff --git a/app/assets/stylesheets/darkswarm/variables.css.sass b/app/assets/stylesheets/darkswarm/variables.css.sass new file mode 100644 index 0000000000..a13eb9d014 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/variables.css.sass @@ -0,0 +1 @@ +$fawn: #f6efe5 diff --git a/app/assets/stylesheets/distributors.css.scss b/app/assets/stylesheets/distributors.css.scss new file mode 100644 index 0000000000..de8cd669a0 --- /dev/null +++ b/app/assets/stylesheets/distributors.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the distributors controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/darkswarm_controller.rb b/app/controllers/darkswarm_controller.rb new file mode 100644 index 0000000000..b1a7422e02 --- /dev/null +++ b/app/controllers/darkswarm_controller.rb @@ -0,0 +1,5 @@ +class DarkswarmController < ApplicationController + def index + + end +end diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index dd4b30a206..d4b6ade04b 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -2,7 +2,6 @@ class EnterprisesController < BaseController helper Spree::ProductsHelper include OrderCyclesHelper - def index @enterprises = Enterprise.all end @@ -66,7 +65,6 @@ class EnterprisesController < BaseController order_cycle_options = OrderCycle.active.with_distributor(distributor) order.order_cycle = order_cycle_options.first if order_cycle_options.count == 1 - order.save! redirect_to main_app.enterprise_path(distributor) diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb new file mode 100644 index 0000000000..dde9af99f4 --- /dev/null +++ b/app/controllers/shop_controller.rb @@ -0,0 +1,48 @@ +class ShopController < BaseController + layout "darkswarm" + + before_filter :set_distributor + before_filter :set_order_cycles + + def show + # All suppliers of all our products + @producers = Exchange.where(receiver_id: @distributor.id).map{ |ex| ex.variants.map {|v| v.product.supplier }}.flatten.uniq + end + + def products + unless @products = current_order_cycle.andand.products_distributed_by(@distributor) + render json: "", status: 404 + end + end + + def order_cycle + if request.post? + if oc = OrderCycle.with_distributor(@distributor).active.find_by_id(params[:order_cycle_id]) + current_order(true).set_order_cycle! oc + render partial: "shop/order_cycle" + else + render status: 404, json: "" + end + else + render partial: "shop/order_cycle" + end + end + + private + + def set_distributor + + unless @distributor = current_distributor + redirect_to root_path + end + end + + def set_order_cycles + @order_cycles = OrderCycle.with_distributor(@distributor).active + + # And default to the only order cycle if there's only the one + if @order_cycles.count == 1 + current_order(true).set_order_cycle! @order_cycles.first + end + end +end diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index e5c61ada05..a6c2967b87 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -11,11 +11,16 @@ Spree::Admin::ProductsController.class_eval do end def bulk_update - collection_hash = Hash[params[:_json].each_with_index.map { |p,i| [i,p] }] + collection_hash = Hash[params[:products].each_with_index.map { |p,i| [i,p] }] product_set = Spree::ProductSet.new({:collection_attributes => collection_hash}) + params[:filters] ||= {} + bulk_index_query = params[:filters].reduce("") do |string, filter| + "#{string}q[#{filter[:property][:db_column]}_#{filter[:predicate][:predicate]}]=#{filter[:value]};" + end + if product_set.save - redirect_to "/api/products/managed?template=bulk_index&page=1&per_page=500" + redirect_to "/api/products/managed?template=bulk_index;page=1;per_page=500;#{bulk_index_query}" else render :nothing => true, :status => 418 end diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index 308aa55046..d080b6bb8e 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -5,14 +5,13 @@ Spree::OrdersController.class_eval do before_filter :update_distribution, :only => :update before_filter :filter_order_params, :only => :update - # Patch Orders#populate to provide distributor_id and order_cycle_id to OrderPopulator + # Patch Orders#populate to populate multi_cart (if enabled) def populate if OpenFoodNetwork::FeatureToggle.enabled? :multi_cart populate_cart params.slice(:products, :variants, :quantity, :distributor_id, :order_cycle_id) end - populator = Spree::OrderPopulator.new(current_order(true), current_currency) - if populator.populate(params.slice(:products, :variants, :quantity, :distributor_id, :order_cycle_id)) + if populator.populate(params.slice(:products, :variants, :quantity)) fire_event('spree.cart.add') fire_event('spree.order.contents_changed') respond_with(@order) do |format| diff --git a/app/controllers/suburbs_controller.rb b/app/controllers/suburbs_controller.rb index 21c4040d22..4fe73f29d8 100644 --- a/app/controllers/suburbs_controller.rb +++ b/app/controllers/suburbs_controller.rb @@ -2,4 +2,4 @@ class SuburbsController < ActionController::Base def index @suburbs = Suburb.matching(params[:term]).order(:name).limit(8) end -end \ No newline at end of file +end diff --git a/app/helpers/order_cycles_helper.rb b/app/helpers/order_cycles_helper.rb index d20c667da7..92cd968e0b 100644 --- a/app/helpers/order_cycles_helper.rb +++ b/app/helpers/order_cycles_helper.rb @@ -54,8 +54,8 @@ module OrderCyclesHelper OpenFoodNetwork::FeatureToggle.enabled? :order_cycles end - def pickup_time - current_order_cycle.exchanges.to_enterprises(current_distributor).outgoing.first.pickup_time + def pickup_time(order_cycle = current_order_cycle) + order_cycle.exchanges.to_enterprises(current_distributor).outgoing.first.pickup_time end end diff --git a/app/helpers/shop_helper.rb b/app/helpers/shop_helper.rb new file mode 100644 index 0000000000..3066bfbe05 --- /dev/null +++ b/app/helpers/shop_helper.rb @@ -0,0 +1,10 @@ +module ShopHelper + def order_cycles_name_and_pickup_times(order_cycles) + order_cycles.map do |oc| + [ + pickup_time(oc), + oc.id + ] + end + end +end diff --git a/app/models/cart.rb b/app/models/cart.rb index 845a80187d..fee1a25a44 100644 --- a/app/models/cart.rb +++ b/app/models/cart.rb @@ -8,7 +8,7 @@ class Cart < ActiveRecord::Base order = create_or_find_order_for_distributor distributor, order_cycle, currency @populator = Spree::OrderPopulator.new(order, currency) - @populator.populate({ :variants => { variant_id => quantity }, :distributor_id => distributor.id, :order_cycle_id => order_cycle }) + @populator.populate({ :variants => { variant_id => quantity } }) end def create_or_find_order_for_distributor distributor, order_cycle, currency @@ -17,6 +17,7 @@ class Cart < ActiveRecord::Base order_for_distributor = Spree::Order.create(:currency => currency, :distributor => distributor) order_for_distributor.distributor = distributor order_for_distributor.order_cycle = order_cycle + order_for_distributor.save! orders << order_for_distributor end diff --git a/app/models/spree/order_populator_decorator.rb b/app/models/spree/order_populator_decorator.rb index 002b0a0f74..aad019e06f 100644 --- a/app/models/spree/order_populator_decorator.rb +++ b/app/models/spree/order_populator_decorator.rb @@ -1,26 +1,15 @@ Spree::OrderPopulator.class_eval do def populate_with_distribution_validation(from_hash) - @distributor, @order_cycle = load_distributor_and_order_cycle(from_hash) + @distributor, @order_cycle = distributor_and_order_cycle + # Refactor: We may not need this validation - we can't change distribution here, so + # this validation probably can't fail if !distribution_can_supply_products_in_cart(@distributor, @order_cycle) errors.add(:base, "That distributor or order cycle can't supply all the products in your cart. Please choose another.") end - # Set order distributor and order cycle - @orig_distributor, @orig_order_cycle = orig_distributor_and_order_cycle - cart_distribution_set = false - if valid? - set_cart_distributor_and_order_cycle @distributor, @order_cycle - cart_distribution_set = true - end - populate_without_distribution_validation(from_hash) if valid? - # Undo distribution setting if validation failed when adding a product - if !valid? && cart_distribution_set - set_cart_distributor_and_order_cycle @orig_distributor, @orig_order_cycle - end - valid? end alias_method_chain :populate, :distribution_validation @@ -32,7 +21,7 @@ Spree::OrderPopulator.class_eval do variant = Spree::Variant.find(variant_id) if quantity > 0 if check_stock_levels(variant, quantity) && - check_distribution_provided_for(variant) && + check_order_cycle_provided_for(variant) && check_variant_available_under_distribution(variant) @order.add_variant(variant, quantity, currency) @@ -43,44 +32,18 @@ Spree::OrderPopulator.class_eval do private - def orig_distributor_and_order_cycle + def distributor_and_order_cycle [@order.distributor, @order.order_cycle] end - - def load_distributor_and_order_cycle(from_hash) - distributor = from_hash[:distributor_id].present? ? - Enterprise.is_distributor.find(from_hash[:distributor_id]) : nil - order_cycle = from_hash[:order_cycle_id].present? ? - OrderCycle.find(from_hash[:order_cycle_id]) : nil - - [distributor, order_cycle] - end - - def set_cart_distributor_and_order_cycle(distributor, order_cycle) - # Using @order.reload or not performing any reload causes totals fields (ie. item_total) - # to be set to zero - @order = Spree::Order.find @order.id - - @order.set_distribution! distributor, order_cycle - end - def distribution_can_supply_products_in_cart(distributor, order_cycle) DistributionChangeValidator.new(@order).can_change_to_distribution?(distributor, order_cycle) end - def check_distribution_provided_for(variant) - distribution_provided = distribution_provided_for variant - - unless distribution_provided - if order_cycle_required_for variant - errors.add(:base, "Please choose a distributor and order cycle for this order.") - else - errors.add(:base, "Please choose a distributor for this order.") - end - end - - distribution_provided + def check_order_cycle_provided_for(variant) + order_cycle_provided = (!order_cycle_required_for(variant) || @order_cycle.present?) + errors.add(:base, "Please choose an order cycle for this order.") unless order_cycle_provided + order_cycle_provided end def check_variant_available_under_distribution(variant) @@ -92,10 +55,6 @@ Spree::OrderPopulator.class_eval do end end - def distribution_provided_for(variant) - @distributor.present? && (!order_cycle_required_for(variant) || @order_cycle.present?) - end - def order_cycle_required_for(variant) variant.product.product_distributions.empty? end diff --git a/app/views/darkswarm/index.html.haml b/app/views/darkswarm/index.html.haml new file mode 100644 index 0000000000..5938185c06 --- /dev/null +++ b/app/views/darkswarm/index.html.haml @@ -0,0 +1 @@ +TESTING diff --git a/app/views/home/_login.html.haml b/app/views/home/_login.html.haml index e5b9aa5c20..31fafd05d9 100644 --- a/app/views/home/_login.html.haml +++ b/app/views/home/_login.html.haml @@ -15,4 +15,4 @@ %label = f.check_box :remember_me = f.label :remember_me, t(:remember_me) - %p= f.submit t(:login), :class => 'button primary', :tabindex => 3, :id => "login_spree_user_remember_me" \ No newline at end of file + %p= f.submit t(:login), :class => 'button primary', :tabindex => 3, :id => "login_spree_user_remember_me" diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml new file mode 100644 index 0000000000..823d2d7350 --- /dev/null +++ b/app/views/layouts/darkswarm.html.haml @@ -0,0 +1,26 @@ +!!! +%html + %head + %meta{charset: 'utf-8'}/ + %meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/ + + %title= content_for?(:title) ? yield(:title) : 'Welcome to Open Food Network' + = favicon_link_tag "favicon.png" + + = stylesheet_link_tag "darkswarm/all" + = javascript_include_tag "darkswarm/all" + = render "layouts/bugherd_script" + = csrf_meta_tags + + %body.off-canvas + = render partial: "shared/menu" + + %section{ role: "main" } + = yield + + %section#sidebar{ role: "complementary" } + = render partial: "shared/login_panel" + = yield :sidebar + + = yield :scripts + diff --git a/app/views/products/_list.html.haml b/app/views/products/_list.html.haml new file mode 100644 index 0000000000..3770387175 --- /dev/null +++ b/app/views/products/_list.html.haml @@ -0,0 +1,13 @@ +%table#product-list + %thead + %th Item + %th Description + %th Variant + %th Quantity + %th Available? + %th Price + + - list.each do |product| + %tr + %td= product.name + %td= product.description diff --git a/app/views/shared/_copyright.html.haml b/app/views/shared/_copyright.html.haml new file mode 100644 index 0000000000..271ba90c41 --- /dev/null +++ b/app/views/shared/_copyright.html.haml @@ -0,0 +1,3 @@ +#copyright.text-center + %img.copyright{src: "/assets/logo.png", alt: "Open Food Network"} + © Copyright 2013 Open Food Foundation diff --git a/app/views/shared/_login.html.haml b/app/views/shared/_login.html.haml new file mode 100644 index 0000000000..67249aeb26 --- /dev/null +++ b/app/views/shared/_login.html.haml @@ -0,0 +1,12 @@ +- if spree_current_user.nil? + %li#login-link= link_to "Login", "#sidebar", id: "sidebarLoginButton", class: "sidebar-button" + %li#login-name.hide + %li.divider + %li#sign-up-link= link_to "Sign Up", "#sidebar", id: "sidebarSignUpButton", class: "sidebar-button" + %li#sign-out-link.hide= link_to "Sign Out", "/logout" +- else + %li#login-link.hide= link_to "Login", "#sidebar", id: "sidebarLoginButton", class: "sidebar-button" + %li#login-name= link_to "#{spree_current_user.email}", "#" + %li.divider + %li#sign-up-link.hide= link_to "Sign Up", "#" + %li#sign-out-link= link_to "Sign Out", "/logout" diff --git a/app/views/shared/_login_panel.html.haml b/app/views/shared/_login_panel.html.haml new file mode 100644 index 0000000000..8266eaf0b5 --- /dev/null +++ b/app/views/shared/_login_panel.html.haml @@ -0,0 +1,5 @@ +.login-panel + #login-content.hide + = render "home/login" + #sign-up-content.hide + = render "home/signup" diff --git a/app/views/shared/_menu.html.haml b/app/views/shared/_menu.html.haml new file mode 100644 index 0000000000..0531afcdcb --- /dev/null +++ b/app/views/shared/_menu.html.haml @@ -0,0 +1,11 @@ +%nav.top-bar + %section.top-bar-section + %ul.left + %li= link_to image_tag("ofn_logo_small.png"), new_landing_page_path + %li.divider + = render partial: "shared/login" + + %ul.right + %li= link_to "Distributors", "#", :data => { "reveal-id" => "become-distributor" } + %li.divider + %li= link_to "Farmers", "#", :data => { "reveal-id" => "become-farmer" } diff --git a/app/views/shop/_about_us.html.haml b/app/views/shop/_about_us.html.haml new file mode 100644 index 0000000000..74ef2eaa47 --- /dev/null +++ b/app/views/shop/_about_us.html.haml @@ -0,0 +1,3 @@ +.about.right.text-right.small-2.large-3.columns + %h3 About Us + %p= @distributor.long_description.andand.html_safe diff --git a/app/views/shop/_contact_us.html.haml b/app/views/shop/_contact_us.html.haml new file mode 100644 index 0000000000..2cb4d65984 --- /dev/null +++ b/app/views/shop/_contact_us.html.haml @@ -0,0 +1,8 @@ +.contact.small-2.large-3.columns + %h3 Contact + %ul + %li= @distributor.email + %li= @distributor.website + = @distributor.address.address1 + = @distributor.address.address2 + = @distributor.address.city diff --git a/app/views/shop/_last_order_cycle.html.haml b/app/views/shop/_last_order_cycle.html.haml new file mode 100644 index 0000000000..353529e60a --- /dev/null +++ b/app/views/shop/_last_order_cycle.html.haml @@ -0,0 +1,4 @@ +- if most_recently_closed = OrderCycle.most_recently_closed_for(@distributor) + The last cycle closed + = distance_of_time_in_words_to_now most_recently_closed.orders_close_at + ago diff --git a/app/views/shop/_next_order_cycle.html.haml b/app/views/shop/_next_order_cycle.html.haml new file mode 100644 index 0000000000..f4b3e5172f --- /dev/null +++ b/app/views/shop/_next_order_cycle.html.haml @@ -0,0 +1,3 @@ +- if next_oc = OrderCycle.first_opening_for(@distributor) + The next cycle opens in + = distance_of_time_in_words_to_now next_oc.orders_open_at diff --git a/app/views/shop/_order_cycle.rabl b/app/views/shop/_order_cycle.rabl new file mode 100644 index 0000000000..493dee1de2 --- /dev/null +++ b/app/views/shop/_order_cycle.rabl @@ -0,0 +1,3 @@ +object current_order_cycle +attributes :orders_close_at +attribute :id => :order_cycle_id diff --git a/app/views/shop/_order_cycles.html.haml b/app/views/shop/_order_cycles.html.haml new file mode 100644 index 0000000000..60d5997cf3 --- /dev/null +++ b/app/views/shop/_order_cycles.html.haml @@ -0,0 +1,28 @@ +%ordercycle{"ng-controller" => "OrderCycleCtrl"} + + :javascript + angular.module('Shop').value('orderCycleData', #{render "shop/order_cycle"}) + + + - if @order_cycles.empty? + Orders are currently closed for this hub + %p + Please contact your hub directly to see if they accept late orders, + or wait until the next cycle opens. + + = render partial: "shop/next_order_cycle" + = render partial: "shop/last_order_cycle" + + - else + %form.custom + %strong.avenir Ready for + %select.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id", + "ng-change" => "changeOrderCycle()", + "ng-options" => "oc.id as oc.time for oc in #{@order_cycles.map {|oc| {time: pickup_time(oc), id: oc.id}}.to_json}"} + + + %closing + -#%img{src: "/icon/goes/here"} + Orders close + %strong {{ order_cycle.orders_close_at | date:'EEEE MM'}} + diff --git a/app/views/shop/_products.html.haml b/app/views/shop/_products.html.haml new file mode 100644 index 0000000000..3019d22967 --- /dev/null +++ b/app/views/shop/_products.html.haml @@ -0,0 +1,48 @@ +%products{"ng-controller" => "ProductsCtrl"} + = form_for :order, :url => populate_orders_path, html: {:class => "custom"} do + %input#search.text{"ng-model" => "query", placeholder: "Search"} + %input.button.right{type: :submit, value: "Check Out"} + %table + %thead + %th{colspan: 2} Item + %th.notes Notes + %th Variant + %th QTY + %th Bulk + %th Price + %tbody{"ng-repeat" => "product in data.products | filter:query"} + %tr.product + %td + %img{"ng-src" => "{{ product.master.images[0].small_url }}"} + {{product.master.images[0].alt}} + %td + %h5 + {{ product.name }} + {{ product.supplier.name }} + %td.notes {{ product.description | truncate:80 }} + + %td {{ product.master.options_text }} + %td + %input{type: :number, value: 0, min: 0, name: "variants[{{product.master.id}}]"} + %td.group_buy + %span{"ng-show" => "product.group_buy"} + Available + %span{"ng-hide" => "product.group_buy"} + Not available + %td.price + %small from + ${{ product.price }} + %tr{"ng-repeat" => "variant in product.variants"} + %td{colspan: 3} + %td {{variant.options_text}} + %td + %input{type: :number, value: 0, min: 0, name: "variants[{{variant.id}}]"} + %td.group_buy + %span{"ng-show" => "product.group_buy"} + Available + %span{"ng-hide" => "product.group_buy"} + Not available + %td.price + %small from ${{variant.price }} + %input.button.right{type: :submit, value: "Check Out"} + -#%pre {{ data.products | json }} diff --git a/app/views/shop/products.rabl b/app/views/shop/products.rabl new file mode 100644 index 0000000000..7d245447e9 --- /dev/null +++ b/app/views/shop/products.rabl @@ -0,0 +1,25 @@ +collection @products +attributes :id, :name, :description, :price, :permalink + +child :supplier do + attributes :id, :name, :description +end +child :master => :master do + attributes :id, :is_master, :count_on_hand, :options_text + child :images => :images do + attributes :id, :alt + node do |img| + {:small_url => img.attachment.url(:small, false)} + end + end +end +child :variants => :variants do |variant| + attributes :id, :is_master, :count_on_hand, :options_text + child :images => :images do + attributes :id, :alt + node do |img| + {:small_url => img.attachment.url(:small, false)} + end + end +end + diff --git a/app/views/shop/show.html.haml b/app/views/shop/show.html.haml new file mode 100644 index 0000000000..a4d54423b6 --- /dev/null +++ b/app/views/shop/show.html.haml @@ -0,0 +1,50 @@ +%shop{"ng-app" => "Shop"} + %navigation + %distributor.details.row + %img.left{src: ""} + %h4 + = @distributor.name + %location= @distributor.address.city + %small + %a{href: "/"} Change location + = render partial: "shop/order_cycles" + + -#%description + + %tabs + .row + .section-container.auto{"data-section" => "", "data-options" => "one_up: false"} + %section + %p.title.avenir{"data-section-title" => ""} + %a{href: "#about"} About Us + .content{"data-section-content" => ""} + %p= @distributor.long_description.andand.html_safe + + %section + %p.title.avenir{"data-section-title" => ""} + %a{href: "#producers"} Our Producers + .content{"data-section-content" => ""} + %ul + - for producer in @producers + %li= producer.name + + %section + %p.title.avenir{"data-section-title" => ""} + %a{href: "#groups"} Our Groups + .content{"data-section-content" => ""} + %p Groups + + %section + %p.title.avenir{"data-section-title" => ""} + %a{href: "#contact"} Contact + .content{"data-section-content" => ""} + %p Contact + + + %products.row + = render partial: "shop/products" + #footer + %section.row + = render partial: "shop/contact_us" + = render partial: "shop/about_us" + = render partial: "shared/copyright" diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index 84572efaba..e5a4d4a3b7 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -13,20 +13,79 @@ -%div{ 'ng-app' => 'bulk_product_update', 'ng-controller' => 'AdminBulkProductsCtrl', 'ng-init' => "initialise('#{@spree_api_key}');" } +%div{ 'ng-app' => 'bulk_product_update', 'ng-controller' => 'AdminBulkProductsCtrl', 'ng-init' => "initialise('#{@spree_api_key}');loading=true;" } %div{ 'ng-show' => '!spree_api_key_ok' } {{ api_error_msg }} - %div - %div.options - Filter Results: - %input.search{ 'ng-model' => 'query', :type => 'text', 'placeholder' => 'Search Value' } - %input{ :type => 'button', :value => 'Toggle Columns', 'ofn-toggle-column-list' => true } - %div{ :style => 'display: none;' } - %ul.column-list{ style: 'border: 1px solid darkgray; background-color: white;' } - %li.column-list-item{ 'ofn-toggle-column' => 'column', 'ng-repeat' => 'column in columns' } - {{ column.name }} - %br.clear - %br.clear + %div.option_tab_titles{ :class => "sixteen columns alpha" } + %h6{ :class => "three columns alpha", 'ng-repeat' => "tab in optionTabs", "ng-click" => "shiftTab(tab)", "ng-class" => "tab.visible && 'selected' || !tab.visible && 'unselected'"} + {{ tab.title }} + %div.option_tabs{ :class => "sixteen columns alpha" } + %div.filters{ :class => "sixteen columns alpha", "ng-show" => 'optionTabs.filters.visible' } + %div{ :class => "four columns alpha" } + Column: + %br.clear + %select.select2.fullwidth{ 'ng-model' => 'filterProperty', :name => "filter_property", 'ng-options' => 'fc.name for fc in filterableColumns' } + %div{ :class => "four columns omega" } + Filter Type: + %br.clear + %select.select2.fullwidth{ 'ng-model' => 'filterPredicate', :name => "filter_predicate", 'ng-options' => 'ft.name for ft in filterTypes' } + %div{ :class => "six columns omega" } + Value: + %br.clear + %input{ :class => "four columns alpha", 'ng-model' => 'filterValue', :name => "filter_value", :type => "text", 'placeholder' => 'Filter Value' } + %div{ :class => "two columns omega" } +   + %input.fullwidth{ :name => "add_filter", :value => "Apply Filter", :type => "button", "ng-click" => "addFilter({property:filterProperty,predicate:filterPredicate,value:filterValue})" } + %div.applied_filters{ :class => "sixteen columns alpha", "ng-show" => 'optionTabs.filters.visible && currentFilters.length > 0' } + %div.applied_filter{ :class => "sixteen columns alpha", 'ng-repeat' => 'filter in currentFilters' } + %div{ :class => "four columns alpha" } + {{ filter.property.name }} + %div{ :class => "four columns omega" } + {{ filter.predicate.name }} + %div{ :class => "six columns omega" } + {{ filter.value }} + %div{ :class => "two columns omega" } + %a{ 'ng-click' => "removeFilter(filter)" } Remove Filter + %div.column_toggle{ :class => "sixteen columns alpha", "ng-show" => 'optionTabs.column_toggle.visible' } + %ul.column-list{ :class => "sixteen columns alpha" } + %li.column-list-item{ :class => "three columns alpha", 'ofn-toggle-column' => 'column', 'ng-repeat' => 'column in columns' } + {{ column.name }} + %hr + %div.loading{ 'ng-show' => 'loading' } + %h4 Loading Products... + %div{ 'ng-show' => '!loading && products.length == 0' } + %h4{ :style => 'color:red;' } No matching products found. + %div{ 'ng-show' => 'products.length == 500' } + %h6 Search returned too many products to display (500+), please apply more search filters to reduce the number of matching products + %div{ 'ng-hide' => 'loading || products.length == 500 || products.length == 0' } + %div.quick_search{ :class => "five columns omega" } + %input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' } + %div.pagination{ :class => "seven columns omega" } + %div.pagenav{ :class => "two columns alpha" } + %span.first + %a{ 'ng-click' => "currentPage = 1", 'ng-show' => "currentPage > 1" } + « First + %span.prev + %a{ 'ng-click' => "currentPage = currentPage - 1", 'ng-show' => "currentPage > 1" } + ‹ Prev + %div.pagenav{ :class => "columns omega" } + %span.page{ 'ng-repeat' => "page in [] | rangeArray:minPage():maxPage()", 'ng-class' => "{current: currentPage==page}" } + %a{ 'ng-click' => "setPage(page)" } + {{page}} + %span{ 'ng-show' => "maxPage() < totalPages()" } ... + %div.pagenav{ :class => "two columns omega" } + %span.next + %a{ 'ng-click' => "currentPage = currentPage + 1", 'ng-show' => "currentPage < totalPages()" } + Next › + %span.last + %a{ 'ng-click' => "currentPage = totalPages()", 'ng-show' => "currentPage < totalPages()" } + Last » + %div.pagination_info{ :class => 'four columns alpha' } + Show  + %select{ 'ng-model' => 'perPage', :name => 'perPage', 'ng-options' => 'pp for pp in [25,50,100,200]'} +  per page + %br + %span Displaying {{firstVisibleProduct()}}-{{lastVisibleProduct()}} of {{totalCount()}} products %table.index#listing_products.bulk %colgroup %col @@ -48,7 +107,7 @@ %th{ 'ng-show' => 'columns.on_hand.visible' } On Hand %th{ 'ng-show' => 'columns.available_on.visible' } Av. On %th.actions - %tbody{ 'ng-repeat' => 'product in products | filter:query', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } + %tbody{ 'ng-repeat' => 'product in filteredProducts = (products | filter:query)', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", 'ng-show' => "$index >= perPage*(currentPage-1) && $index < perPage*currentPage" } %tr.product %td.left-actions %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants icon-chevron-right", 'ng-show' => 'hasVariants(product)' } diff --git a/app/views/spree/api/products/bulk_index.v1.rabl b/app/views/spree/api/products/bulk_index.v1.rabl index 4e90cc01ca..e35c8c9d59 100644 --- a/app/views/spree/api/products/bulk_index.v1.rabl +++ b/app/views/spree/api/products/bulk_index.v1.rabl @@ -1,3 +1,2 @@ collection @products.order('id ASC') -extends "spree/api/products/bulk_show" -attributes :variant_unit, :variant_unit_scale, :variant_unit_name \ No newline at end of file +extends "spree/api/products/bulk_show" \ No newline at end of file diff --git a/app/views/spree/api/products/bulk_show.v1.rabl b/app/views/spree/api/products/bulk_show.v1.rabl index df45c86b42..534dd87846 100644 --- a/app/views/spree/api/products/bulk_show.v1.rabl +++ b/app/views/spree/api/products/bulk_show.v1.rabl @@ -1,5 +1,5 @@ object @product -attributes :id, :name, :price, :on_hand +attributes :id, :name, :price, :on_hand, :variant_unit, :variant_unit_scale, :variant_unit_name node( :available_on ) { |p| p.available_on.blank? ? "" : p.available_on.strftime("%F %T") } node( :permalink_live ) { |p| p.permalink } diff --git a/app/views/spree/products/_add_to_cart.html.haml b/app/views/spree/products/_add_to_cart.html.haml index 49334fcce9..621b999ddc 100644 --- a/app/views/spree/products/_add_to_cart.html.haml +++ b/app/views/spree/products/_add_to_cart.html.haml @@ -13,27 +13,6 @@ - else = render 'add_to_cart_quantity_fields', product: @product - -#temporary hiding the options to choose order cycles and distributors - %div.cleared.hide - %br - - available_distributors = available_distributors_for(order, @product) - - available_order_cycles = available_order_cycles_for(order, @product) - - - if available_distributors.length > 1 || order.andand.distributor.nil? - = render 'add_to_cart_distributor_choice', distributor_collection: available_distributors - - else - - distributor = available_distributors.first - - changing_distributor = distributor != order.andand.distributor - = render 'add_to_cart_distributor_fixed', distributor: distributor, changing_distributor: changing_distributor - - - if order_cycles_enabled? - - if available_order_cycles.length > 1 || order.andand.order_cycle.nil? - = render 'add_to_cart_order_cycle_choice', order_cycle_collection: available_order_cycles - - else - - order_cycle = available_order_cycles.first - - changing_order_cycle = order_cycle != order.andand.order_cycle - = render 'add_to_cart_order_cycle_fixed', order_cycle: order_cycle, changing_order_cycle: changing_order_cycle - %br = button_tag :class => 'large primary', :id => 'add-to-cart-button', :type => :submit do = t(:add_to_cart) diff --git a/config/application.rb b/config/application.rb index 590c658a7d..49936d7baf 100644 --- a/config/application.rb +++ b/config/application.rb @@ -81,6 +81,7 @@ module Openfoodnetwork config.assets.initialize_on_precompile = true config.assets.precompile += ['store/all.css', 'store/all.js', 'store/shop_front.js'] config.assets.precompile += ['admin/all.css', 'admin/restore_spree_from_cms.css', 'admin/*.js', 'admin/**/*.js'] + config.assets.precompile += ['darkswarm/all.css', 'darkswarm/all.js'] config.assets.precompile += ['comfortable_mexican_sofa/*'] config.assets.precompile += ['search/all.css', 'search/*.js'] config.assets.precompile += ['shared/*'] diff --git a/config/ng-test.conf.js b/config/ng-test.conf.js index 13b67ec688..f174433cf5 100644 --- a/config/ng-test.conf.js +++ b/config/ng-test.conf.js @@ -11,11 +11,17 @@ module.exports = function(config) { 'app/assets/javascripts/admin/order_cycle.js.erb.coffee', 'app/assets/javascripts/admin/bulk_product_update.js.coffee', + 'app/assets/javascripts/darkswarm/*.js*', + 'app/assets/javascripts/darkswarm/**/*.js*', 'spec/javascripts/unit/**/*.js*' ], - exclude: ['**/.#*'], + exclude: [ + '**/.#*', + 'app/assets/javascripts/darkswarm/all.js.coffee', + 'app/assets/javascripts/darkswarm/overrides.js.coffee' + ], coffeePreprocessor: { options: { diff --git a/config/routes.rb b/config/routes.rb index d96ba028e9..6e05b80384 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,13 @@ Openfoodnetwork::Application.routes.draw do root :to => 'home#temp_landing_page' + + resource :shop, controller: :shop do + get :products + post :order_cycle + get :order_cycle + end + resources :enterprises do collection do get :suppliers @@ -37,6 +44,7 @@ Openfoodnetwork::Application.routes.draw do end get "new_landing_page", :controller => 'home', :action => "new_landing_page" + get "darkswarm", controller: :darkswarm, action: :index get "about_us", :controller => 'home', :action => "about_us" namespace :open_food_network do diff --git a/db/schema.rb b/db/schema.rb index 4cf6c6dbd8..6b0e614cf4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -481,9 +481,9 @@ ActiveRecord::Schema.define(:version => 20140110040238) do t.string "email" t.text "special_instructions" t.integer "distributor_id" + t.integer "order_cycle_id" t.string "currency" t.string "last_ip_address" - t.integer "order_cycle_id" t.integer "cart_id" end diff --git a/public/AveniBla.eot b/public/AveniBla.eot new file mode 100644 index 0000000000..1ae59a3381 Binary files /dev/null and b/public/AveniBla.eot differ diff --git a/public/AveniMed.eot b/public/AveniMed.eot new file mode 100644 index 0000000000..61c35da54b Binary files /dev/null and b/public/AveniMed.eot differ diff --git a/public/AvenirLTStd-Black.otf b/public/AvenirLTStd-Black.otf new file mode 100644 index 0000000000..1a934a2af3 Binary files /dev/null and b/public/AvenirLTStd-Black.otf differ diff --git a/public/AvenirLTStd-Medium.otf b/public/AvenirLTStd-Medium.otf new file mode 100644 index 0000000000..e902718152 Binary files /dev/null and b/public/AvenirLTStd-Medium.otf differ diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb new file mode 100644 index 0000000000..71f776163a --- /dev/null +++ b/spec/controllers/shop_controller_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' + +describe ShopController do + let(:d) { create(:distributor_enterprise) } + + it "redirects to the home page if no distributor is selected" do + spree_get :show + response.should redirect_to root_path + end + + + describe "with a distributor in place" do + before do + controller.stub(:current_distributor).and_return d + end + + describe "Selecting order cycles" do + it "should select an order cycle when only one order cycle is open" do + oc1 = create(:order_cycle, distributors: [d]) + spree_get :show + controller.current_order_cycle.should == oc1 + end + + it "should not set an order cycle when multiple order cycles are open" do + oc1 = create(:order_cycle, distributors: [d]) + oc2 = create(:order_cycle, distributors: [d]) + spree_get :show + controller.current_order_cycle.should == nil + end + + it "should allow the user to post to select the current order cycle" do + oc1 = create(:order_cycle, distributors: [d]) + oc2 = create(:order_cycle, distributors: [d]) + + spree_post :order_cycle, order_cycle_id: oc2.id + response.should be_success + controller.current_order_cycle.should == oc2 + end + + context "RABL tests" do + render_views + it "should return the order cycle details when the oc is selected" do + oc1 = create(:order_cycle, distributors: [d]) + oc2 = create(:order_cycle, distributors: [d]) + + spree_post :order_cycle, order_cycle_id: oc2.id + response.should be_success + response.body.should have_content oc2.id + end + + it "should return the current order cycle when hit with GET" do + oc1 = create(:order_cycle, distributors: [d]) + controller.stub(:current_order_cycle).and_return oc1 + spree_get :order_cycle + response.body.should have_content oc1.id + end + + end + + + it "should not allow the user to select an invalid order cycle" do + oc1 = create(:order_cycle, distributors: [d]) + oc2 = create(:order_cycle, distributors: [d]) + oc3 = create(:order_cycle, distributors: [create(:distributor_enterprise)]) + + spree_post :order_cycle, order_cycle_id: oc3.id + response.status.should == 404 + controller.current_order_cycle.should == nil + end + end + + + describe "producers/suppliers" do + let(:supplier) { create(:supplier_enterprise) } + let(:product) { create(:product, supplier: supplier) } + let(:order_cycle) { create(:order_cycle, distributors: [d], coordinator: create(:distributor_enterprise)) } + + before do + exchange = Exchange.find(order_cycle.exchanges.to_enterprises(d).outgoing.first.id) + exchange.variants << product.master + end + + it "builds a list of producers/suppliers" do + spree_get :show + assigns[:producers].should == [supplier] + end + end + + describe "returning products" do + let(:product) { create(:product) } + let(:order_cycle) { create(:order_cycle, distributors: [d], coordinator: create(:distributor_enterprise)) } + + before do + exchange = Exchange.find(order_cycle.exchanges.to_enterprises(d).outgoing.first.id) + exchange.variants << product.master + end + + it "returns products via json" do + controller.stub(:current_order_cycle).and_return order_cycle + xhr :get, :products + response.should be_success + end + + it "does not return products if no order_cycle is selected" do + controller.stub(:current_order_cycle).and_return nil + xhr :get, :products + response.status.should == 404 + response.body.should be_empty + end + + context "RABL tests" do + render_views + it "only returns products for the current order cycle" do + controller.stub(:current_order_cycle).and_return order_cycle + xhr :get, :products + response.body.should have_content product.name + end + end + + end + end +end diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 64b4e27e2b..6e2c3b9543 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -168,7 +168,7 @@ describe Spree::Admin::ReportsController do it "should build distributors for the current user" do spree_get :products_and_inventory - assigns(:distributors).should == [d1, d2, d3] + assigns(:distributors).sort.should == [d1, d2, d3].sort end it "builds suppliers for the current user" do diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index 2d5dd64576..9883914736 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -1,10 +1,6 @@ require 'spec_helper' describe Spree::OrdersController do - def current_user - controller.current_user - end - it "selects distributors" do d = create(:distributor_enterprise) p = create(:product, :distributors => [d]) @@ -31,83 +27,18 @@ describe Spree::OrdersController do order.distributor.should be_nil end - - describe "adding a product to the cart with a distribution combination that can't service the existing cart" do - before do - @request.env["HTTP_REFERER"] = 'http://test.host/' - end - - it "errors when an invalid distributor is selected" do - # Given a product and some distributors - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p = create(:product, :price => 12.34) - oc = create(:simple_order_cycle, :distributors => [d1], :variants => [p.master]) - - # When I attempt to add the product to the cart with an invalid distributor, it should not be added - expect do - spree_post :populate, variants: {p.master.id => 1}, distributor_id: d2.id, order_cycle_id: oc.id - response.should redirect_to :back - end.to change(self, :num_items_in_cart).by(0) - - # And I should see an error - flash[:error].should == "That product is not available from the chosen distributor or order cycle." - end - - it "errors when an invalid order cycle is selected" do - # Given a product and some order cycles - d = create(:distributor_enterprise) - p = create(:product, :price => 12.34) - oc1 = create(:simple_order_cycle, :distributors => [d], :variants => [p.master]) - oc2 = create(:simple_order_cycle, :distributors => [d], :variants => []) - - # When I attempt to add the product to my cart with an invalid order cycle, it should not be added - expect do - spree_post :populate, variants: {p.master.id => 1}, distributor_id: d.id, order_cycle_id: oc2.id - response.should redirect_to :back - end.to change(self, :num_items_in_cart).by(0) - - # And I should see an error - flash[:error].should == "That product is not available from the chosen distributor or order cycle." - end - - it "errors when distribution is valid for the new product but does not cover the cart" do - # Given two products with different distributors - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p1 = create(:product, :price => 12.34) - p2 = create(:product, :price => 23.45) - oc1 = create(:simple_order_cycle, :distributors => [d1], :variants => [p1.master]) - oc2 = create(:simple_order_cycle, :distributors => [d2], :variants => [p2.master]) - - # When I add the first to my cart - expect do - spree_post :populate, variants: {p1.master.id => 1}, distributor_id: d1.id, order_cycle_id: oc1.id - response.should redirect_to spree.cart_path - end.to change(self, :num_items_in_cart).by(1) - - # And I attempt to add the second, then the product should not be added to my cart - expect do - spree_post :populate, variants: {p2.master.id => 1}, distributor_id: d2.id, order_cycle_id: oc2.id - response.should redirect_to :back - end.to change(self, :num_items_in_cart).by(0) - - # And I should see an error - flash[:error].should == "That distributor or order cycle can't supply all the products in your cart. Please choose another." - end - end - context "adding a group buy product to the cart" do it "sets a variant attribute for the max quantity" do distributor_product = create(:distributor_enterprise) p = create(:product, :distributors => [distributor_product], :group_buy => true) order = subject.current_order(true) + order.stub(:distributor) { distributor_product } order.should_receive(:set_variant_attributes).with(p.master, {'max_quantity' => '3'}) controller.stub(:current_order).and_return(order) expect do - spree_post :populate, :variants => {p.master.id => 1}, :variant_attributes => {p.master.id => {:max_quantity => 3}}, :distributor_id => distributor_product.id + spree_post :populate, :variants => {p.master.id => 1}, :variant_attributes => {p.master.id => {:max_quantity => 3}} end.to change(Spree::LineItem, :count).by(1) end end @@ -141,6 +72,7 @@ describe Spree::OrdersController do end end + private def num_items_in_cart diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 36007a19b1..f9935ce01e 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -21,14 +21,45 @@ feature %q{ login_to_admin_section end + it "displays a 'loading' splash for products" do + FactoryGirl.create(:simple_product) + + visit '/admin/products/bulk_edit' + + page.should have_selector "div.loading", :text => "Loading Products..." + end + it "displays a list of products" do p1 = FactoryGirl.create(:product) p2 = FactoryGirl.create(:product) visit '/admin/products/bulk_edit' - page.should have_field "product_name", with: p1.name - page.should have_field "product_name", with: p2.name + page.should have_field "product_name", with: p1.name, :visible => true + page.should have_field "product_name", with: p2.name, :visible => true + end + + it "displays a message when number of products is zero" do + visit '/admin/products/bulk_edit' + + page.should have_text "No matching products found." + end + + pending "displays a message when number of products is too great" do + 501.times { FactoryGirl.create(:simple_product) } + + visit '/admin/products/bulk_edit' + + page.should have_text "Search returned too many products to display (500+), please apply more search filters to reduce the number of matching products" + end + + it "displays pagination information" do + p1 = FactoryGirl.create(:product) + p2 = FactoryGirl.create(:product) + + visit '/admin/products/bulk_edit' + + page.should have_text "Displaying 1-2 of 2 products" end it "displays a select box for suppliers, with the appropriate supplier selected" do @@ -209,6 +240,9 @@ feature %q{ login_to_admin_section visit '/admin/products/bulk_edit' + + first("div.option_tab_titles h6", :text => "Toggle Columns").click + first("li.column-list-item", text: "Available On").click page.should have_field "product_name", with: p.name page.should have_select "supplier", selected: s1.name @@ -342,6 +376,53 @@ feature %q{ page.find("span#update-status-message").should have_content "Update complete" end + scenario "updating a product after cloning a product" do + FactoryGirl.create(:product, :name => "product 1") + login_to_admin_section + + visit '/admin/products/bulk_edit' + + first("a.clone-product").click + + fill_in "product_name", :with => "new product name" + + click_button 'Update' + page.find("span#update-status-message").should have_content "Update complete" + end + + scenario "updating when no changes have been made" do + Capybara.default_wait_time = 2 + FactoryGirl.create(:product, :name => "product 1") + FactoryGirl.create(:product, :name => "product 2") + login_to_admin_section + + visit '/admin/products/bulk_edit' + + click_button 'Update' + page.find("span#update-status-message").should have_content "No changes to update." + Capybara.default_wait_time = 5 + end + + scenario "updating when a filter has been applied" do + p1 = FactoryGirl.create(:simple_product, :name => "product1") + p2 = FactoryGirl.create(:simple_product, :name => "product2") + login_to_admin_section + + visit '/admin/products/bulk_edit' + + first("div.option_tab_titles h6", :text => "Filter Products").click + + select "Name", :from => "filter_property" + select "Contains", :from => "filter_predicate" + fill_in "filter_value", :with => "1" + click_button "Apply Filter" + page.should_not have_field "product_name", with: p2.name + fill_in "product_name", :with => "new product1" + + click_on 'Update' + page.find("span#update-status-message").should have_content "Update complete" + end + scenario "updating a product when there are more products than the default API page size" do 26.times { FactoryGirl.create(:simple_product) } login_to_admin_section @@ -447,7 +528,6 @@ feature %q{ page.should have_selector "a.clone-product", :count => 3 first("a.clone-product").click - page.should have_selector "a.clone-product", :count => 4 page.should have_field "product_name", with: "COPY OF #{p1.name}" page.should have_select "supplier", selected: "#{p1.supplier.name}" @@ -462,24 +542,61 @@ feature %q{ end describe "using the page" do - describe "using column display toggle" do + describe "using tabs to hide and display page controls" do it "shows a column display toggle button, which shows a list of columns when clicked" do + FactoryGirl.create(:simple_product) login_to_admin_section visit '/admin/products/bulk_edit' + page.should have_selector "div.column_toggle", :visible => false + + page.should have_selector "div.option_tab_titles h6.unselected", :text => "Toggle Columns" + first("div.option_tab_titles h6", :text => "Toggle Columns").click + + page.should have_selector "div.option_tab_titles h6.selected", :text => "Toggle Columns" + page.should have_selector "div.column_toggle", :visible => true + page.should have_selector "li.column-list-item", text: "Available On" + + page.should have_selector "div.filters", :visible => false + + page.should have_selector "div.option_tab_titles h6.unselected", :text => "Filter Products" + first("div.option_tab_titles h6", :text => "Filter Products").click + + page.should have_selector "div.option_tab_titles h6.unselected", :text => "Toggle Columns" + page.should have_selector "div.option_tab_titles h6.selected", :text => "Filter Products" + page.should have_selector "div.filters", :visible => true + page.should have_selector "li.column-list-item", text: "Available On" + + first("div.option_tab_titles h6", :text => "Filter Products").click + + page.should have_selector "div.option_tab_titles h6.unselected", :text => "Filter Products" + page.should have_selector "div.option_tab_titles h6.unselected", :text => "Toggle Columns" + page.should have_selector "div.filters", :visible => false + page.should have_selector "div.column_toggle", :visible => false + end + end + + describe "using column display toggle" do + it "shows a column display toggle button, which shows a list of columns when clicked" do + FactoryGirl.create(:simple_product) + login_to_admin_section + + visit '/admin/products/bulk_edit' + + first("div.option_tab_titles h6", :text => "Toggle Columns").click + first("li.column-list-item", text: "Available On").click + page.should have_selector "th", :text => "NAME" page.should have_selector "th", :text => "SUPPLIER" page.should have_selector "th", :text => "PRICE" page.should have_selector "th", :text => "ON HAND" page.should have_selector "th", :text => "AV. ON" - page.should have_button "Toggle Columns" - - click_button "Toggle Columns" + page.should have_selector "div.option_tab_titles h6", :text => "Toggle Columns" page.should have_selector "div ul.column-list li.column-list-item", text: "Supplier" - all("div ul.column-list li.column-list-item").select{ |e| e.text == "Supplier" }.first.click + first("li.column-list-item", text: "Supplier").click page.should_not have_selector "th", :text => "SUPPLIER" page.should have_selector "th", :text => "NAME" @@ -488,6 +605,147 @@ feature %q{ page.should have_selector "th", :text => "AV. ON" end end + + describe "using pagination controls" do + it "shows pagination controls" do + 27.times { FactoryGirl.create(:product) } + login_to_admin_section + + visit '/admin/products/bulk_edit' + + page.should have_select 'perPage', :selected => '25' + within '.pagination' do + page.should have_text "1 2" + page.should have_text "Next" + page.should have_text "Last" + end + end + + it "allows the number of visible products to be altered" do + 27.times { FactoryGirl.create(:product) } + login_to_admin_section + + visit '/admin/products/bulk_edit' + + select '25', :from => 'perPage' + page.all("input[name='product_name']").select{ |e| e.visible? }.length.should == 25 + select '50', :from => 'perPage' + page.all("input[name='product_name']").select{ |e| e.visible? }.length.should == 27 + end + + it "displays the correct products when changing pages" do + 25.times { FactoryGirl.create(:product, :name => "page1product") } + 5.times { FactoryGirl.create(:product, :name => "page2product") } + login_to_admin_section + + visit '/admin/products/bulk_edit' + + select '25', :from => 'perPage' + page.all("input[name='product_name']").select{ |e| e.visible? }.all?{ |e| e.value == "page1product" }.should == true + click_link "2" + page.all("input[name='product_name']").select{ |e| e.visible? }.all?{ |e| e.value == "page2product" }.should == true + end + + it "moves the user to the last available page when changing the number of pages in any way causes user to become orphaned" do + 50.times { FactoryGirl.create(:product) } + FactoryGirl.create(:product, :name => "fancy_product_name") + login_to_admin_section + + visit '/admin/products/bulk_edit' + + select '25', :from => 'perPage' + click_link "3" + select '50', :from => 'perPage' + page.first("div.pagenav span.page.current").should have_text "2" + page.all("input[name='product_name']", :visible => true).length.should == 1 + + select '25', :from => 'perPage' + fill_in "quick_filter", :with => "fancy_product_name" + page.first("div.pagenav span.page.current").should have_text "1" + page.all("input[name='product_name']", :visible => true).length.should == 1 + end + + it "paginates the filtered product list rather than all products" do + 25.times { FactoryGirl.create(:product, :name => "product_name") } + 3.times { FactoryGirl.create(:product, :name => "test_product_name") } + login_to_admin_section + + visit '/admin/products/bulk_edit' + + select '25', :from => 'perPage' + page.should have_text "1 2" + fill_in "quick_filter", :with => "test_product_name" + page.all("input[name='product_name']", :visible => true).length.should == 3 + page.all("input[name='product_name']", :visible => true).all?{ |e| e.value == "test_product_name" }.should == true + page.should_not have_text "1 2" + page.should have_text "1" + end + end + + describe "using filtering controls" do + it "displays basic filtering controls" do + FactoryGirl.create(:simple_product) + + login_to_admin_section + visit '/admin/products/bulk_edit' + + page.should have_selector "div.option_tab_titles h6", :text => "Filter Products" + first("div.option_tab_titles h6", :text => "Filter Products").click + + page.should have_select "filter_property", :with_options => ["Supplier", "Name"] + page.should have_select "filter_predicate", :with_options => ["Equals", "Contains"] + page.should have_field "filter_value" + end + + describe "clicking the 'Apply Filter' Button" do + before(:each) do + FactoryGirl.create(:simple_product, :name => "Product1") + FactoryGirl.create(:simple_product, :name => "Product2") + + login_to_admin_section + visit '/admin/products/bulk_edit' + + first("div.option_tab_titles h6", :text => "Filter Products").click + + select "Name", :from => "filter_property" + select "Equals", :from => "filter_predicate" + fill_in "filter_value", :with => "Product1" + click_button "Apply Filter" + end + + it "adds a new filter to the list of applied filters" do + page.should have_text "Name Equals Product1" + end + + it "displays the 'loading' splash" do + page.should have_selector "div.loading", :text => "Loading Products..." + end + + it "loads appropriate products" do + page.should have_field "product_name", :with => "Product1" + page.should_not have_field "product_name", :with => "Product2" + end + + describe "clicking the 'Remove Filter' link" do + before(:each) do + click_link "Remove Filter" + end + + it "removes the filter from the list of applied filters" do + page.should_not have_text "Name Equals Product1" + end + + it "displays the 'loading' splash" do + page.should have_selector "div.loading", :text => "Loading Products..." + end + + it "loads appropriate products" do + page.should have_field "product_name", :with => "Product1" + page.should have_field "product_name", :with => "Product2" + end + end + end + end end context "as an enterprise manager" do @@ -535,6 +793,9 @@ feature %q{ p = product_supplied visit '/admin/products/bulk_edit' + + first("div.option_tab_titles h6", :text => "Toggle Columns").click + first("li.column-list-item", text: "Available On").click page.should have_field "product_name", with: p.name page.should have_select "supplier", selected: s1.name diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 4e6564756c..8f2c1b2a34 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -379,8 +379,8 @@ feature %q{ # Then my times should have been saved flash_message.should == 'Order cycles have been updated.' - OrderCycle.all.map { |oc| oc.orders_open_at.sec }.should == [0, 2, 4] - OrderCycle.all.map { |oc| oc.orders_close_at.sec }.should == [1, 3, 5] + OrderCycle.order('id ASC').map { |oc| oc.orders_open_at.sec }.should == [0, 2, 4] + OrderCycle.order('id ASC').map { |oc| oc.orders_close_at.sec }.should == [1, 3, 5] end scenario "cloning an order cycle" do diff --git a/spec/features/admin/shipping_methods_spec.rb b/spec/features/admin/shipping_methods_spec.rb index 86a2348bd0..575e2b7da4 100644 --- a/spec/features/admin/shipping_methods_spec.rb +++ b/spec/features/admin/shipping_methods_spec.rb @@ -30,7 +30,7 @@ feature 'shipping methods' do sm = Spree::ShippingMethod.last sm.name.should == 'Carrier Pidgeon' - sm.distributors.should == [d1, d2] + sm.distributors.sort.should == [d1, d2].sort end it "at checkout, user can only see shipping methods for their current distributor (checkout spec)" diff --git a/spec/features/consumer/add_to_cart_spec.rb b/spec/features/consumer/add_to_cart_spec.rb index a2a677f556..7e7650a9b9 100644 --- a/spec/features/consumer/add_to_cart_spec.rb +++ b/spec/features/consumer/add_to_cart_spec.rb @@ -21,123 +21,13 @@ feature %q{ click_button 'Add To Cart' # Then I should see an error message - page.should have_content "Please choose a distributor for this order." + page.should have_content "That product is not available from the chosen distributor or order cycle" # And the product should not have been added to my cart Spree::Order.last.line_items.should be_empty end - scenario "adding the first product to the cart", :future => true do - # Given a product, some distributors and a defined shipping cost - d1 = create(:distributor_enterprise, :name => "Green Grass") - d2 = create(:distributor_enterprise, :name => "AusFarmers United") - create(:product, :distributors => [d2]) - p = create(:product, :price => 12.34) - create(:product_distribution, :product => p, :distributor => d1) - - # ... with a flat rate distribution fee of $1.23 - ef = p.product_distributions.first.enterprise_fee - ef.calculator = Spree::Calculator::FlatRate.new preferred_amount: 1.23 - ef.calculator.save! - - # When I choose a distributor - visit spree.root_path - click_on "AusFarmers United" - - # And I add an item to my cart from a different distributor - visit spree.product_path p - select(d1.name, :from => 'distributor_id') - click_button 'Add To Cart' - - # Then the correct totals should be displayed - page.should have_selector 'span.item-total', :text => '$12.34' - page.should have_selector 'span.distribution-total', :text => '$1.23' - page.should have_selector 'span.grand-total', :text => '$13.57' - - # And the item should be in my cart - order = Spree::Order.last - line_item = order.line_items.first - line_item.product.should == p - - # And my order should have its distributor set to the chosen distributor - order.distributor.should == d1 - end - context "adding a subsequent product to the cart" do - it "when there are several valid distributors, allows a choice from these options" do - # Given two products, both distributed by two distributors - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p1 = create(:product, :distributors => [d1, d2]) - p2 = create(:product, :distributors => [d1, d2]) - - # When I add the first to my cart via d1 - visit spree.product_path p1 - select d1.name, :from => 'distributor_id' - click_button 'Add To Cart' - - # And I go to add the second, I should have a choice of distributor - visit spree.product_path p2 - page.should have_selector '#distributor_id option', :text => d1.name - page.should have_selector '#distributor_id option', :text => d2.name - - # When I add the second, both should be in my cart, and my distributor should be the one chosen second - select d2.name, :from => 'distributor_id' - click_button 'Add To Cart' - visit spree.cart_path - page.should have_selector 'h4 a', :text => p1.name - page.should have_selector 'h4 a', :text => p2.name - page.should have_selector "#logo h1 a", :text => d2.name - end - - it "when the only valid distributor is the chosen one, does not allow the user to choose a distributor" do - # Given two products, each at the same distributor - d = create(:distributor_enterprise) - p1 = create(:product, :distributors => [d]) - p2 = create(:product, :distributors => [d]) - - # When I add the first to my cart - visit spree.product_path p1 - select d.name, :from => 'distributor_id' - click_button 'Add To Cart' - - # And I go to add the second, I should not have a choice of distributor - visit spree.product_path p2 - page.should_not have_selector 'select#distributor_id' - page.should have_selector '.distributor-fixed', :text => "Your distributor for this order is #{d.name}" - - # When I add the second, both should be in my cart - click_button 'Add To Cart' - visit spree.cart_path - page.should have_selector 'h4 a', :text => p1.name - page.should have_selector 'h4 a', :text => p2.name - end - - it "when the only valid distributor differs from the chosen one, alerts the user and changes distributor on add to cart" do - # Given two products, one available at only one distributor - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p1 = create(:product, :distributors => [d1, d2]) - p2 = create(:product, :distributors => [d2]) - - # When I add the first to my cart - visit spree.product_path p1 - select d1.name, from: 'distributor_id' - click_button 'Add To Cart' - - # And I go to add the second - visit spree.product_path p2 - - # Then I should see a message offering to change distributor for my order - page.should have_content "Your distributor for this order will be changed to #{d2.name} if you add this product to your cart." - - # When I add the second to my cart - click_button 'Add To Cart' - - # Then My distributor should have changed - page.should have_selector "#logo h1 a", :text => d2.name - end - it "does not allow the user to add a product from a distributor that cannot supply the cart's products" do # Given two products, each at a different distributor d1 = create(:distributor_enterprise) @@ -146,8 +36,8 @@ feature %q{ p2 = create(:product, :distributors => [d2]) # When I add one of them to my cart + select_distribution d1 visit spree.product_path p1 - select d1.name, :from => 'distributor_id' click_button 'Add To Cart' # And I attempt to add the other @@ -205,7 +95,7 @@ feature %q{ click_button 'Add To Cart' # Then I should see an error message - page.should have_content "Please choose a distributor and order cycle for this order." + page.should have_content "Please choose an order cycle for this order." # And the product should not have been added to my cart Spree::Order.last.line_items.should be_empty @@ -218,9 +108,8 @@ feature %q{ oc = create(:simple_order_cycle, :distributors => [d], :variants => [p.master]) # When I add an item to my cart + select_distribution d, oc visit spree.product_path p - select d.name, :from => 'distributor_id' - select oc.name, :from => 'order_cycle_id' click_button 'Add To Cart' # Then the correct totals should be displayed @@ -239,144 +128,6 @@ feature %q{ order.distributor.should == d order.order_cycle.should == oc end - - scenario "adding a product to the cart with an invalid distribution combination" do - # Given a product and some distributors - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p = create(:product, :price => 12.34) - oc1 = create(:simple_order_cycle, :distributors => [d1], :variants => [p.master]) - oc2 = create(:simple_order_cycle, :distributors => [d2], :variants => [p.master]) - - # When I attempt to add the product to my cart with an invalid distribution - visit spree.product_path p - select d1.name, :from => 'distributor_id' - select oc2.name, :from => 'order_cycle_id' - click_button 'Add To Cart' - - # Then I should see an error message - page.should have_content "That product is not available from the chosen distributor or order cycle." - - # And the product should not be in my cart - Spree::Order.last.line_items.should be_empty - end - - - context "adding a subsequent product to the cart" do - it "when there are several valid order cycles, allows a choice from these options" do - # Given two products, both distributed by two distributors - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p1 = create(:product) - p2 = create(:product) - oc1 = create(:simple_order_cycle, - :distributors => [d1, d2], :variants => [p1.master, p2.master]) - oc2 = create(:simple_order_cycle, - :distributors => [d1, d2], :variants => [p1.master, p2.master]) - - # When I add the first to my cart via d1/oc1 - visit spree.product_path p1 - select d1.name, :from => 'distributor_id' - select oc1.name, :from => 'order_cycle_id' - click_button 'Add To Cart' - - # And I go to add the second, I should have a choice of order cycle and distributor - visit spree.product_path p2 - page.should have_selector '#distributor_id option', :text => d1.name - page.should have_selector '#distributor_id option', :text => d2.name - page.should have_selector '#order_cycle_id option', :text => oc1.name - page.should have_selector '#order_cycle_id option', :text => oc2.name - - # When I add the second, both should be in my cart, and my - # distributor and order cycle should be the one chosen second - select d2.name, :from => 'distributor_id' - select oc2.name, :from => 'order_cycle_id' - click_button 'Add To Cart' - visit spree.cart_path - page.should have_selector 'h4 a', :text => p1.name - page.should have_selector 'h4 a', :text => p2.name - page.should have_selector "#logo h1 a", :text => d2.name - end - - it "when the only valid order cycle is the chosen one, does not allow the user to choose an order cycle" do - # Given two products, each at the same distributor - d = create(:distributor_enterprise) - p1 = create(:product) - p2 = create(:product) - oc = create(:simple_order_cycle, :distributors => [d], - :variants => [p1.master, p2.master]) - - # When I add the first to my cart - visit spree.product_path p1 - select d.name, :from => 'distributor_id' - select oc.name, :from => 'order_cycle_id' - click_button 'Add To Cart' - - # And I go to add the second, I should not have a choice of distributor or order cycle - visit spree.product_path p2 - page.should_not have_selector 'select#distributor_id' - page.should have_selector '.distributor-fixed', :text => "Your distributor for this order is #{d.name}" - page.should_not have_selector 'select#order_cycle_id' - page.should have_selector '.order-cycle-fixed', :text => "Your order cycle for this order is #{oc.name}" - - # When I add the second, both should be in my cart - click_button 'Add To Cart' - visit spree.cart_path - page.should have_selector 'h4 a', :text => p1.name - page.should have_selector 'h4 a', :text => p2.name - end - - it "when the only valid distributor differs from the chosen one, alerts the user and changes distributor on add to cart" do - # Given two products, one available at only one distributor - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p1 = create(:product) - p2 = create(:product) - oc1 = create(:simple_order_cycle, :distributors => [d1], :variants => [p1.master]) - oc2 = create(:simple_order_cycle, :distributors => [d2], :variants => [p1.master, p2.master]) - - # When I add the first to my cart - visit spree.product_path p1 - select d1.name, from: 'distributor_id' - select oc1.name, from: 'order_cycle_id' - - click_button 'Add To Cart' - - # And I go to add the second - visit spree.product_path p2 - - # Then I should see a message offering to change distributor for my order - page.should have_content "Your distributor for this order will be changed to #{d2.name} if you add this product to your cart." - - # When I add the second to my cart - click_button 'Add To Cart' - - # Then my distributor should have changed - page.should have_selector "#logo h1 a", :text => d2.name - end - - it "does not allow the user to add a product from an order cycle that cannot supply the cart's products" do - # Given two products, each at a different order cycle - d = create(:distributor_enterprise) - p1 = create(:product) - p2 = create(:product) - oc1 = create(:simple_order_cycle, :distributors => [d], :variants => [p1.master]) - oc2 = create(:simple_order_cycle, :distributors => [d], :variants => [p2.master]) - - # When I add one of them to my cart - visit spree.product_path p1 - select d.name, :from => 'distributor_id' - select oc1.name, :from => 'order_cycle_id' - click_button 'Add To Cart' - - # And I attempt to add the other - visit spree.product_path p2 - - # Then I should not be allowed to add the product - page.should_not have_selector "button#add-to-cart-button" - page.should have_content "Please complete your order from #{oc1.name} before shopping in a different order cycle." - end - end end context "group buys" do @@ -386,8 +137,8 @@ feature %q{ p = create(:product, :distributors => [d], :group_buy => true) # When I add the item to my cart + select_distribution d visit spree.product_path p - select d.name, :from => 'distributor_id' fill_in "variants_#{p.master.id}", :with => 2 fill_in "variant_attributes_#{p.master.id}_max_quantity", :with => 3 click_button 'Add To Cart' @@ -407,8 +158,8 @@ feature %q{ create(:variant, :product => p) # When I add the item to my cart + select_distribution d visit spree.product_path p - select d.name, :from => 'distributor_id' fill_in "quantity", :with => 2 fill_in "max_quantity", :with => 3 click_button 'Add To Cart' @@ -438,8 +189,8 @@ feature %q{ p = create(:product, :distributors => [d], :group_buy => true) # When I add the item to my cart + select_distribution d visit spree.product_path p - select d.name, :from => 'distributor_id' fill_in "variants_#{p.master.id}", :with => 2 fill_in "variant_attributes_#{p.master.id}_max_quantity", :with => 1 click_button 'Add To Cart' diff --git a/spec/features/consumer/browse_products_spec.rb b/spec/features/consumer/browse_products_spec.rb index c14f290ac7..a12899d4bc 100644 --- a/spec/features/consumer/browse_products_spec.rb +++ b/spec/features/consumer/browse_products_spec.rb @@ -90,79 +90,5 @@ feature %q{ page.all('#product-variants li input').count.should == 1 end end - - context "viewing a product, it provides a choice of distributor when adding to cart" do - it "works when no distributor is chosen" do - # Given a distributor and a product under it - distributor = create(:distributor_enterprise) - product = create(:product, :distributors => [distributor]) - - # When we view the product - visit spree.product_path(product) - - # Then we should see a choice of distributor, with no default - page.should have_selector "select#distributor_id option", :text => distributor.name - page.should_not have_selector "select#distributor_id option[selected='selected']" - end - - it "displays the local distributor as the default choice when available for the current product" do - # Given a distributor and a product under it - distributor1 = create(:distributor_enterprise) - distributor2 = create(:distributor_enterprise) - product = create(:product, :distributors => [distributor1,distributor2]) - - # When we select the distributor and view the product - visit spree.select_distributor_order_path(distributor1) - visit spree.product_path(product) - - # Then we should see our distributor as the default option when adding the item to our cart - page.should have_selector "select#distributor_id option[value='#{distributor1.id}'][selected='selected']" - end - - it "works when viewing a product from a remote distributor" do - # Given two distributors and our product under one - distributor_product = create(:distributor_enterprise) - distributor_no_product = create(:distributor_enterprise) - product = create(:product, :distributors => [distributor_product]) - create(:product, :distributors => [distributor_no_product]) - - # When we select the distributor without the product and then view the product - visit spree.select_distributor_order_path(distributor_no_product) - visit spree.root_path - visit spree.product_path(product) - - # Then we should be told that our distributor will be set to the one with the product - page.should_not have_selector "select#distributor_id" - page.should have_content "our distributor for this order will be changed to #{distributor_product.name} if you add this product to your cart." - end - end - end - - describe "selecting an order cycle" do - - before(:each) do - OrderCyclesHelper.class_eval do - def order_cycles_enabled? - true - end - end - end - - it "displays the distributor and order cycle name on the home page when an order cycle is selected" do - # Given a distributor with a product - d = create(:distributor_enterprise, :name => 'Melb Uni Co-op') - p = create(:product) - oc = create(:simple_order_cycle, :name => 'Bulk Foods', :distributors => [d], :variants => [p.master]) - - # When I select the distributor and order cycle - visit spree.product_path p - select d.name, :from => 'distributor_id' - select oc.name, :from => 'order_cycle_id' - click_button 'Add To Cart' - - # Then I should see the name of the distributor and order cycle that I've selected - page.should have_content 'Melb Uni Co-op' - page.should_not have_selector 'div.distributor-description' - end end end diff --git a/spec/features/consumer/checkout_spec.rb b/spec/features/consumer/checkout_spec.rb index 98cafcfdac..487ab9f230 100644 --- a/spec/features/consumer/checkout_spec.rb +++ b/spec/features/consumer/checkout_spec.rb @@ -122,6 +122,7 @@ feature %q{ click_button 'Add To Cart' # Then I should see a breakdown of my delivery fees: + checkout_fees_table.should == [["Bananas - packing fee by supplier Supplier 1", "$3.00", ""], ["Bananas - transport fee by supplier Supplier 1", "$4.00", ""], diff --git a/spec/features/consumer/order_cycles_spec.rb b/spec/features/consumer/order_cycles_spec.rb index dedd77e1cb..591b7af002 100644 --- a/spec/features/consumer/order_cycles_spec.rb +++ b/spec/features/consumer/order_cycles_spec.rb @@ -279,25 +279,6 @@ feature %q{ page.should have_selector "input[value='#{@oc1.id}'][checked='checked']" page.should have_selector "option[value='#{@d1.id}'][selected='selected']" end - - scenario "selection form is not shown when there are products in the cart" do - # Given a product - d = create(:distributor_enterprise) - p = create(:product, :distributors => [d]) - - # When I go to the products listing page, I should see the selection form - visit spree.products_path - page.should have_selector "#distribution-selection" - - # When I add a product to the cart - visit spree.product_path p - select d.name, :from => 'distributor_id' - click_button 'Add To Cart' - - # Then I should no longer see the selection form - visit spree.products_path - page.should_not have_selector "#distribution-selection" - end end end diff --git a/spec/features/consumer/product_spec.rb b/spec/features/consumer/product_spec.rb index ee45de379b..9b45b21ef1 100644 --- a/spec/features/consumer/product_spec.rb +++ b/spec/features/consumer/product_spec.rb @@ -51,24 +51,5 @@ feature %q{ end end end - - context "with Javascript", js: true do - it "changes distributor details when the distributor is changed" do - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - d3 = create(:distributor_enterprise) - p = create(:product, :distributors => [d1, d2, d3]) - - visit spree.product_path p - - [d1, d2, d3].each do |d| - select d.name, :from => 'distributor_id' - - within '#product-distributor-details' do - page.should have_selector 'h2', :text => d.name - end - end - end - end end end diff --git a/spec/features/consumer/shopping_spec.rb b/spec/features/consumer/shopping_spec.rb new file mode 100644 index 0000000000..bbf127f1cb --- /dev/null +++ b/spec/features/consumer/shopping_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' + +feature "As a consumer I want to shop with a distributor", js: true do + include AuthenticationWorkflow + include WebHelper + + describe "Viewing a distributor" do + let(:distributor) { create(:distributor_enterprise) } + + before do #temporarily using the old way to select distributor + create_enterprise_group_for distributor + visit "/" + click_link distributor.name + end + it "shows a distributor" do + visit shop_path + page.should have_text distributor.name + end + + describe "With products in order cycles" do + let(:supplier) { create(:supplier_enterprise) } + let(:product) { create(:product, supplier: supplier) } + let(:order_cycle) { create(:order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) } + + before do + exchange = Exchange.find(order_cycle.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.variants << product.master + end + + it "shows the suppliers/producers for a distributor" do + visit shop_path + click_link "Our Producers" + page.should have_content supplier.name + end + + end + + + describe "selecting an order cycle" do + it "selects an order cycle if only one is open" do + # create order cycle + oc1 = create(:simple_order_cycle, distributors: [distributor]) + exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.update_attribute :pickup_time, "turtles" + + visit shop_path + page.should have_selector "option[selected]", text: 'turtles' + end + + describe "with multiple order cycles" do + let(:oc1) {create(:simple_order_cycle, distributors: [distributor])} + let(:oc2) {create(:simple_order_cycle, distributors: [distributor])} + before do + exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.update_attribute :pickup_time, "frogs" + exchange = Exchange.find(oc2.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.update_attribute :pickup_time, "turtles" + end + + it "shows a select with all order cycles" do + visit shop_path + page.should have_selector "option", text: 'frogs' + page.should have_selector "option", text: 'turtles' + end + + describe "with products in our order cycle" do + let(:product) { create(:simple_product) } + before do + exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.variants << product.master + visit shop_path + end + + it "allows us to select an order cycle" do + select "frogs", :from => "order_cycle_id" + Spree::Order.last.order_cycle.should == nil + page.should have_selector "products" + page.should have_content "Orders close #{oc1.orders_close_at.strftime('%A %m')}" + Spree::Order.last.order_cycle.should == oc1 + end + + it "doesn't show products before an order cycle is selected" do + page.should_not have_content product.name + end + + it "shows products when an order cycle has been selected" do + select "frogs", :from => "order_cycle_id" + page.should have_content product.name + end + + it "updates the orders close note when order cycle is changed" do + select "frogs", :from => "order_cycle_id" + page.should have_content "Orders close #{oc1.orders_close_at.strftime('%A %m')}" + end + end + end + + describe "adding products to cart" do + let(:oc) { create(:simple_order_cycle, distributors: [distributor]) } + let(:product) { create(:simple_product) } + let(:variant) { create(:variant, product: product) } + before do + exchange = Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.update_attribute :pickup_time, "frogs" + exchange.variants << product.master + exchange.variants << variant + visit shop_path + select "frogs", :from => "order_cycle_id" + end + it "should let us add products to our cart" do + fill_in "variants[#{variant.id}]", with: "1" + first("form.custom > input.button.right").click + current_path.should == "/cart" + page.should have_content product.name + end + end + + context "when no order cycles are available" do + it "tells us orders are closed" do + visit shop_path + page.should have_content "Orders are currently closed for this hub" + end + it "shows the last order cycle" do + oc1 = create(:simple_order_cycle, distributors: [distributor], orders_close_at: 10.days.ago) + visit shop_path + page.should have_content "The last cycle closed 10 days ago" + end + it "shows the next order cycle" do + oc1 = create(:simple_order_cycle, distributors: [distributor], orders_open_at: 10.days.from_now) + visit shop_path + page.should have_content "The next cycle opens in 10 days" + end + end + end + end +end diff --git a/spec/helpers/order_cycles_helper_spec.rb b/spec/helpers/order_cycles_helper_spec.rb index 45eab95396..fce1095591 100644 --- a/spec/helpers/order_cycles_helper_spec.rb +++ b/spec/helpers/order_cycles_helper_spec.rb @@ -33,4 +33,16 @@ describe OrderCyclesHelper do helper.pickup_time.should == "turtles" end + it "should give me the pickup time for any order cycle" do + d = create(:distributor_enterprise, name: 'Green Grass') + oc1 = create(:simple_order_cycle, name: 'oc 1', distributors: [d]) + oc2= create(:simple_order_cycle, name: 'oc 1', distributors: [d]) + + exchange = Exchange.find(oc2.exchanges.to_enterprises(d).outgoing.first.id) + exchange.update_attribute :pickup_time, "turtles" + + helper.stub!(:current_order_cycle).and_return oc1 + helper.stub!(:current_distributor).and_return d + helper.pickup_time(oc2).should == "turtles" + end end diff --git a/spec/helpers/shop_helper_spec.rb b/spec/helpers/shop_helper_spec.rb new file mode 100644 index 0000000000..39ef99ba65 --- /dev/null +++ b/spec/helpers/shop_helper_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' +describe ShopHelper do + + it "should build order cycle select options" do + d = create(:distributor_enterprise) + o1 = create(:simple_order_cycle, distributors: [d]) + helper.stub(:current_distributor).and_return d + + helper.order_cycles_name_and_pickup_times([o1]).should == [[helper.pickup_time(o1), o1.id]] + end +end diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index a36935b3f1..c26a31ba8b 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -1,4 +1,4 @@ -describe "filtering products", -> +describe "filtering products for submission to database", -> it "accepts an object or an array and only returns an array", -> expect(filterSubmitProducts([])).toEqual [] expect(filterSubmitProducts({})).toEqual [] @@ -276,15 +276,41 @@ describe "AdminBulkProductsCtrl", -> it "gets a list of suppliers and then resets products with a list of data", -> httpBackend.expectGET("/api/users/authorise_api?token=api_key").respond success: "Use of API Authorised" httpBackend.expectGET("/api/enterprises/managed?template=bulk_index&q[is_primary_producer_eq]=true").respond "list of suppliers" - httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500").respond "list of products" - spyOn scope, "resetProducts" + spyOn(scope, "fetchProducts").andReturn "nothing" scope.initialise "api_key" httpBackend.flush() expect(scope.suppliers).toEqual "list of suppliers" - expect(scope.resetProducts).toHaveBeenCalledWith "list of products" + expect(scope.fetchProducts.calls.length).toEqual 1 expect(scope.spree_api_key_ok).toEqual true + describe "fetching products", -> + it "makes a standard call to dataFetcher when no filters exist", -> + httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;").respond "list of products" + scope.fetchProducts() + + it "calls resetProducts after data has been received", -> + spyOn scope, "resetProducts" + httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;").respond "list of products" + scope.fetchProducts() + httpBackend.flush() + expect(scope.resetProducts).toHaveBeenCalledWith "list of products" + + it "applies filters when they are present", -> + filter = {property: scope.filterableColumns[1], predicate:scope.filterTypes[0], value:"Product1"} + scope.currentFilters.push filter # Don't use addFilter as that is not what we are testing + expect(scope.currentFilters).toEqual [filter] + httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;q[name_eq]=Product1;").respond "list of products" + scope.fetchProducts() + + it "sets the loading property to true before fetching products and unsets it when loading is complete", -> + httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;").respond "list of products" + scope.fetchProducts() + expect(scope.loading).toEqual true + httpBackend.flush() + expect(scope.loading).toEqual false + + describe "resetting products", -> beforeEach -> spyOn scope, "unpackProduct" @@ -681,6 +707,7 @@ describe "AdminBulkProductsCtrl", -> ] scope.updateProducts "list of dirty products" httpBackend.flush() + timeout.flush() expect(scope.displaySuccess).toHaveBeenCalled() it "runs displayFailure() when post return data does not match $scope.products", -> @@ -972,6 +999,62 @@ describe "AdminBulkProductsCtrl", -> ] + describe "filtering products", -> + describe "adding a filter to the filter list", -> + it "adds objects sent to addFilter() to $scope.currentFilters", -> + spyOn(scope, "fetchProducts").andReturn "nothing" + filterObject1 = {property: scope.filterableColumns[0], predicate: scope.filterTypes[0], value: "Product1"} + filterObject2 = {property: scope.filterableColumns[0], predicate: scope.filterTypes[0], value: "Product2"} + scope.addFilter filterObject1 + scope.addFilter filterObject2 + expect(scope.currentFilters).toEqual [filterObject1, filterObject2] + + it "ignores objects sent to addFilter() which do not contain a 'property' with a corresponding key in filterableColumns", -> + spyOn(scope, "fetchProducts").andReturn "nothing" + filterObject1 = {property: scope.filterableColumns[0], predicate: scope.filterTypes[0], value: "value1"} + filterObject2 = {property: scope.filterableColumns[1], predicate: scope.filterTypes[0], value: "value2"} + filterObject3 = {property: "some_random_property", predicate: scope.filterTypes[0], value: "value3"} + scope.addFilter filterObject1 + scope.addFilter filterObject2 + scope.addFilter filterObject3 + expect(scope.currentFilters).toEqual [filterObject1, filterObject2] + + it "ignores objects sent to addFilter() which do not contain a query with a corresponding key in filterTypes", -> + spyOn(scope, "fetchProducts").andReturn "nothing" + filterObject1 = {property: scope.filterableColumns[0], predicate: scope.filterTypes[0], value: "value1"} + filterObject2 = {property: scope.filterableColumns[0], predicate: scope.filterTypes[1], value: "value2"} + filterObject3 = {property: scope.filterableColumns[0], predicate: "something", value: "value3"} + scope.addFilter filterObject1 + scope.addFilter filterObject2 + scope.addFilter filterObject3 + expect(scope.currentFilters).toEqual [filterObject1, filterObject2] + + it "calls fetchProducts when adding a new filter", -> + spyOn(scope, "fetchProducts").andReturn "nothing" + scope.addFilter( { property: scope.filterableColumns[0], predicate: scope.filterTypes[0], value: "value1" } ) + expect(scope.fetchProducts.calls.length).toEqual(1) + + describe "removing a filter from the filter list", -> + filterObject1 = filterObject2 = null + + beforeEach -> + filterObject1 = {property: scope.filterableColumns[0], predicate: scope.filterTypes[0], value: "Product1"} + filterObject2 = {property: scope.filterableColumns[0], predicate: scope.filterTypes[0], value: "Product2"} + scope.currentFilters = [ filterObject1, filterObject2 ] + + it "removes the specified filter from $scope.currentFilters and calls fetchProducts", -> + spyOn(scope, "fetchProducts").andReturn "nothing" + scope.removeFilter filterObject1 + expect(scope.currentFilters).toEqual [ filterObject2 ] + expect(scope.fetchProducts.calls.length).toEqual 1 + + it "ignores filters which do not exist in currentFilters", -> + spyOn(scope, "fetchProducts").andReturn "nothing" + filterObject3 = {property: scope.filterableColumns[1], predicate: scope.filterTypes[1], value: "SomethingElse"} + scope.removeFilter filterObject3 + expect(scope.currentFilters).toEqual [ filterObject1, filterObject2 ] + expect(scope.fetchProducts.calls.length).toEqual 0 + describe "converting arrays of objects with ids to an object with ids as keys", -> it "returns an object", -> diff --git a/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee new file mode 100644 index 0000000000..0ea7488225 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee @@ -0,0 +1,40 @@ +describe 'All controllers', -> + describe 'ProductsCtrl', -> + ctrl = null + scope = null + event = null + rootScope = null + Product = null + + beforeEach -> + module('Shop') + Product = + all: -> + update: -> + data: "testy mctest" + + inject ($controller, $rootScope) -> + rootScope = $rootScope + scope = $rootScope.$new() + ctrl = $controller 'ProductsCtrl', {$scope: scope, Product : Product} + + it 'Fetches products from Product', -> + expect(scope.data).toEqual 'testy mctest' + + + describe 'OrderCycleCtrl', -> + ctrl = null + scope = null + event = null + rootScope = null + product_ctrl = null + OrderCycle = null + + beforeEach -> + module 'Shop' + scope = {} + inject ($controller, $rootScope) -> + rootScope = $rootScope + scope = $rootScope.$new() + ctrl = $controller 'OrderCycleCtrl', {$scope: scope} + diff --git a/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee b/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee new file mode 100644 index 0000000000..e7ef7f60b1 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee @@ -0,0 +1,34 @@ +describe 'OrderCycle service', -> + $httpBackend = null + OrderCycle = null + mockProduct = { + update: -> + } + + beforeEach -> + angular.module('Shop').value('orderCycleData', {}) + module 'Shop', ($provide)-> + $provide.value "Product", mockProduct + null # IMPORTANT + # You must return null because module() is a bit dumb + + inject (_OrderCycle_, _$httpBackend_)-> + $httpBackend = _$httpBackend_ + OrderCycle = _OrderCycle_ + + + it "posts the order_cycle ID and tells product to update", -> + $httpBackend.expectPOST("/shop/order_cycle", {"order_cycle_id" : 10}).respond(200) + spyOn(mockProduct, "update") + OrderCycle.order_cycle.order_cycle_id = 10 + OrderCycle.push_order_cycle() + $httpBackend.flush() + expect(mockProduct.update).toHaveBeenCalled() + + it "updates the orders_close_at attr after update", -> + datestring = "2013-12-20T00:00:00+11:00" + $httpBackend.expectPOST("/shop/order_cycle").respond({orders_close_at: datestring}) + OrderCycle.push_order_cycle() + $httpBackend.flush() + expect(OrderCycle.order_cycle.orders_close_at).toEqual(datestring) + diff --git a/spec/javascripts/unit/darkswarm/product_spec.js.coffee b/spec/javascripts/unit/darkswarm/product_spec.js.coffee new file mode 100644 index 0000000000..fd2a8f4edb --- /dev/null +++ b/spec/javascripts/unit/darkswarm/product_spec.js.coffee @@ -0,0 +1,14 @@ +describe 'Product service', -> + $httpBackend = null + Product = null + + beforeEach -> + module 'Shop' + inject ($injector, _$httpBackend_)-> + Product = $injector.get("Product") + $httpBackend = _$httpBackend_ + + it "Fetches products from the backend on init", -> + $httpBackend.expectGET("/shop/products").respond([{test : "cats"}]) + products = Product.all() + $httpBackend.flush() diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 6d75d2b8ea..53f451dd54 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -754,4 +754,4 @@ describe 'OrderCycle services', -> expect(data.incoming_exchanges[0].enterprise_fees).toBeUndefined() expect(data.outgoing_exchanges[0].enterprise_fees).toBeUndefined() expect(data.incoming_exchanges[0].enterprise_fee_ids).toEqual [1, 2] - expect(data.outgoing_exchanges[0].enterprise_fee_ids).toEqual [3, 4] \ No newline at end of file + expect(data.outgoing_exchanges[0].enterprise_fee_ids).toEqual [3, 4] diff --git a/spec/models/spree/order_populator_spec.rb b/spec/models/spree/order_populator_spec.rb index e224aad805..1e983872bb 100644 --- a/spec/models/spree/order_populator_spec.rb +++ b/spec/models/spree/order_populator_spec.rb @@ -7,59 +7,20 @@ module Spree let(:params) { double(:params) } let(:distributor) { double(:distributor) } let(:order_cycle) { double(:order_cycle) } - let(:orig_distributor) { double(:distributor) } - let(:orig_order_cycle) { double(:order_cycle) } let(:op) { OrderPopulator.new(order, currency) } describe "populate" do it "checks that distribution can supply all products in the cart" do - op.should_receive(:load_distributor_and_order_cycle).with(params). + op.should_receive(:distributor_and_order_cycle). and_return([distributor, order_cycle]) op.should_receive(:distribution_can_supply_products_in_cart). with(distributor, order_cycle).and_return(false) - op.stub(:orig_distributor_and_order_cycle).and_return([orig_distributor, - orig_order_cycle]) op.should_receive(:populate_without_distribution_validation).never - op.should_receive(:set_cart_distributor_and_order_cycle).never op.populate(params).should be_false op.errors.to_a.should == ["That distributor or order cycle can't supply all the products in your cart. Please choose another."] end - - it "resets cart distributor and order cycle if populate fails" do - op.should_receive(:load_distributor_and_order_cycle).with(params). - and_return([distributor, order_cycle]) - op.should_receive(:distribution_can_supply_products_in_cart). - with(distributor, order_cycle).and_return(true) - op.stub(:orig_distributor_and_order_cycle).and_return([orig_distributor, - orig_order_cycle]) - - op.class_eval do - def populate_without_distribution_validation(from_hash) - errors.add(:base, "Something went wrong.") - end - end - - op.should_receive(:set_cart_distributor_and_order_cycle).with(distributor, order_cycle) - op.should_receive(:set_cart_distributor_and_order_cycle).with(orig_distributor, orig_order_cycle) - - op.populate(params).should be_false - op.errors.to_a.should == ["Something went wrong."] - end - - it "sets cart distributor and order cycle when populate succeeds" do - op.should_receive(:load_distributor_and_order_cycle).with(params). - and_return([distributor, order_cycle]) - op.should_receive(:distribution_can_supply_products_in_cart). - with(distributor, order_cycle).and_return(true) - op.stub(:orig_distributor_and_order_cycle).and_return([orig_distributor, - orig_order_cycle]) - op.should_receive(:populate_without_distribution_validation).with(params) - op.should_receive(:set_cart_distributor_and_order_cycle).with(distributor, order_cycle) - - op.populate(params).should be_true - end end describe "attempt_cart_add" do @@ -69,7 +30,7 @@ module Spree Spree::Variant.stub(:find).and_return(variant) op.should_receive(:check_stock_levels).with(variant, quantity).and_return(true) - op.should_receive(:check_distribution_provided_for).with(variant).and_return(true) + op.should_receive(:check_order_cycle_provided_for).with(variant).and_return(true) op.should_receive(:check_variant_available_under_distribution).with(variant). and_return(true) order.should_receive(:add_variant).with(variant, quantity, currency) @@ -89,30 +50,22 @@ module Spree end end - describe "checking distribution is provided for a variant" do + describe "checking order cycle is provided for a variant, OR is not needed" do let(:variant) { double(:variant) } - it "returns false and errors when distribution is not provided and order cycle is required" do - op.should_receive(:distribution_provided_for).with(variant).and_return(false) - op.should_receive(:order_cycle_required_for).with(variant).and_return(true) - - op.send(:check_distribution_provided_for, variant).should be_false - op.errors.to_a.should == ["Please choose a distributor and order cycle for this order."] + it "returns false and errors when order cycle is not provided and is required" do + op.stub(:order_cycle_required_for).and_return true + op.send(:check_order_cycle_provided_for, variant).should be_false + op.errors.to_a.should == ["Please choose an order cycle for this order."] end - - it "returns false and errors when distribution is not provided and order cycle is not required" do - op.should_receive(:distribution_provided_for).with(variant).and_return(false) - op.should_receive(:order_cycle_required_for).with(variant).and_return(false) - - op.send(:check_distribution_provided_for, variant).should be_false - op.errors.to_a.should == ["Please choose a distributor for this order."] + it "returns true when order cycle is provided" do + op.stub(:order_cycle_required_for).and_return true + op.instance_variable_set :@order_cycle, double(:order_cycle) + op.send(:check_order_cycle_provided_for, variant).should be_true end - - it "returns true and does not error otherwise" do - op.should_receive(:distribution_provided_for).with(variant).and_return(true) - - op.send(:check_distribution_provided_for, variant).should be_true - op.errors.should be_empty + it "returns true when order cycle is not required" do + op.stub(:order_cycle_required_for).and_return false + op.send(:check_order_cycle_provided_for, variant).should be_true end end @@ -142,60 +95,6 @@ module Spree describe "support" do - describe "loading distributor and order cycle from hash" do - it "loads distributor and order cycle when present" do - params = {distributor_id: 1, order_cycle_id: 2} - distributor = double(:distributor) - order_cycle = double(:order_cycle) - - enterprise_scope = double(:enterprise_scope) - enterprise_scope.should_receive(:find).with(1).and_return(distributor) - Enterprise.should_receive(:is_distributor).and_return(enterprise_scope) - OrderCycle.should_receive(:find).with(2).and_return(order_cycle) - - op.send(:load_distributor_and_order_cycle, params).should == - [distributor, order_cycle] - end - - it "returns nil when not present" do - op.send(:load_distributor_and_order_cycle, {}).should == [nil, nil] - end - end - - it "sets cart distributor and order cycle" do - Spree::Order.should_receive(:find).with(order.id).and_return(order) - order.should_receive(:set_distribution!).with(distributor, order_cycle) - - op.send(:set_cart_distributor_and_order_cycle, distributor, order_cycle) - end - - describe "checking if distribution is provided for a variant" do - let(:variant) { double(:variant) } - - it "returns false when distributor is nil" do - op.instance_eval { @distributor = nil } - op.send(:distribution_provided_for, variant).should be_false - end - - it "returns false when order cycle is nil when it's required" do - op.instance_eval { @distributor = 1; @order_cycle = nil } - op.should_receive(:order_cycle_required_for).with(variant).and_return(true) - op.send(:distribution_provided_for, variant).should be_false - end - - it "returns true when distributor is present and order cycle is not required" do - op.instance_eval { @distributor = 1; @order_cycle = nil } - op.should_receive(:order_cycle_required_for).with(variant).and_return(false) - op.send(:distribution_provided_for, variant).should be_true - end - - it "returns true when distributor is present and order cycle is required and present" do - op.instance_eval { @distributor = 1; @order_cycle = 1 } - op.should_receive(:order_cycle_required_for).with(variant).and_return(true) - op.send(:distribution_provided_for, variant).should be_true - end - end - describe "checking if order cycle is required for a variant" do it "requires an order cycle when the product has no product distributions" do product = double(:product, product_distributions: []) @@ -210,11 +109,11 @@ module Spree end end - it "provides the original distributor and order cycle for the order" do - order.should_receive(:distributor).and_return(orig_distributor) - order.should_receive(:order_cycle).and_return(orig_order_cycle) - op.send(:orig_distributor_and_order_cycle).should == [orig_distributor, - orig_order_cycle] + it "provides the distributor and order cycle for the order" do + order.should_receive(:distributor).and_return(distributor) + order.should_receive(:order_cycle).and_return(order_cycle) + op.send(:distributor_and_order_cycle).should == [distributor, + order_cycle] end end end diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index 2e4a7e6923..b2aad5b4db 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -15,7 +15,7 @@ module Spree sm.distributors << d1 sm.distributors << d2 - sm.reload.distributors.should == [d1, d2] + sm.reload.distributors.sort.should == [d1, d2].sort end it "finds shipping methods for a particular distributor" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 13c0730862..76d72d7291 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,13 +32,15 @@ ActiveRecord::Fixtures.create_fixtures(fixtures_dir, ['spree/states', 'spree/cou require 'capybara/poltergeist' Capybara.javascript_driver = :poltergeist -# For debugging: -# - Extend poltergeist's timeout to allow ample time to use pry in browser thread -# - Enable the remote inspector: Use page.driver.debug to open a remote debugger in chrome -# Capybara.register_driver :poltergeist do |app| -# Capybara::Poltergeist::Driver.new(app, timeout: 5.minutes) -# Capybara::Poltergeist::Driver.new(app, inspector: true) -# end +Capybara.register_driver :poltergeist do |app| + options = {phantomjs_options: ['--load-images=no']} + # Extend poltergeist's timeout to allow ample time to use pry in browser thread + #options.merge! {timeout: 5.minutes} + # Enable the remote inspector: Use page.driver.debug to open a remote debugger in chrome + #options.merge! {inspector: true} + + Capybara::Poltergeist::Driver.new(app, options) +end require "paperclip/matchers" @@ -92,6 +94,7 @@ RSpec.configure do |config| config.include Devise::TestHelpers, :type => :controller config.include OpenFoodNetwork::FeatureToggleHelper config.include OpenFoodNetwork::EnterpriseGroupsHelper + config.include OpenFoodNetwork::DistributionHelper config.include ActionView::Helpers::DateHelper # Factory girl diff --git a/spec/support/request/authentication_workflow.rb b/spec/support/request/authentication_workflow.rb index 850157e2a0..3bcdabfcf2 100644 --- a/spec/support/request/authentication_workflow.rb +++ b/spec/support/request/authentication_workflow.rb @@ -1,13 +1,12 @@ module AuthenticationWorkflow def login_to_admin_section admin_role = Spree::Role.find_or_create_by_name!('admin') - admin_user = Spree::User.create!({ - :email => 'admin@ofn.org', + admin_user = create(:user, :password => 'passw0rd', :password_confirmation => 'passw0rd', :remember_me => false, :persistence_token => 'pass', - :login => 'admin@ofn.org'}) + :login => 'admin@ofn.org') admin_user.spree_roles << admin_role diff --git a/spec/support/request/distribution_helper.rb b/spec/support/request/distribution_helper.rb new file mode 100644 index 0000000000..a35c4d0c61 --- /dev/null +++ b/spec/support/request/distribution_helper.rb @@ -0,0 +1,14 @@ +module OpenFoodNetwork + module DistributionHelper + + def select_distribution(distributor, order_cycle=nil) + create_enterprise_group_for distributor + visit root_path + click_link distributor.name + + if order_cycle && page.has_select?('order_order_cycle_id') + select_by_value order_cycle.id, from: 'order_order_cycle_id' + end + end + end +end