Rebuild the Checkout class, no discounts yet
Checkout now stores its items using a quantity and a link back to the original product instance. It has a reference to the store to make sure that product IDs added are valid
This commit is contained in:
+19
-38
@@ -1,62 +1,43 @@
|
||||
require 'product_in_checkout.rb'
|
||||
|
||||
class Checkout
|
||||
class InvalidItem < StandardError; end
|
||||
|
||||
AVAILABLE_ITEMS = {
|
||||
PI5: 60.00,
|
||||
ZERO2W: 15.00,
|
||||
PICO2: 4.00
|
||||
}
|
||||
|
||||
def initialize
|
||||
def initialize(shop:)
|
||||
@shop = shop
|
||||
@items = []
|
||||
end
|
||||
|
||||
def add(item)
|
||||
raise InvalidItem unless valid_item?(item)
|
||||
@items.push item
|
||||
end
|
||||
def add(product_id:, quantity: 1)
|
||||
product = @shop.product_by_id(product_id)
|
||||
|
||||
def remove(item)
|
||||
if index = @items.index(item)
|
||||
@items.delete_at(index)
|
||||
end
|
||||
raise InvalidItem unless product
|
||||
|
||||
@items
|
||||
product_in_checkout = new_or_existing_product_in_checkout(product)
|
||||
product_in_checkout.add_quantity(quantity)
|
||||
|
||||
@items.push product_in_checkout
|
||||
end
|
||||
|
||||
def items
|
||||
@items
|
||||
end
|
||||
|
||||
def total
|
||||
pre_discount_total - current_discount
|
||||
def item_quantities
|
||||
@items.map { |item| [item.product_id, item.quantity] }.to_h
|
||||
end
|
||||
|
||||
def clear
|
||||
@items = []
|
||||
end
|
||||
|
||||
def total
|
||||
@items.reduce(0.00) { |running_total, item| running_total + item.total }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_item?(item)
|
||||
return AVAILABLE_ITEMS.keys.include?(item)
|
||||
end
|
||||
|
||||
def current_discount
|
||||
# BOGOF ZERO2W
|
||||
discount = (@items.count(:ZERO2W) / 2.0).floor * AVAILABLE_ITEMS[:ZERO2W]
|
||||
|
||||
# Bulk PICO2
|
||||
if(items.count(:PICO2) >= 3)
|
||||
discount += items.count(:PICO2) * 0.50
|
||||
end
|
||||
|
||||
return discount
|
||||
end
|
||||
|
||||
def pre_discount_total
|
||||
@items.reduce(0.00) do |running_total, item|
|
||||
running_total + AVAILABLE_ITEMS[item]
|
||||
end
|
||||
def new_or_existing_product_in_checkout(product)
|
||||
@items.find { |item| item.product_id == product.id } || ProductInCheckout.new(product: product)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
class ProductInCheckout
|
||||
def initialize(product:, quantity: 0)
|
||||
@product = product
|
||||
@quantity = quantity
|
||||
end
|
||||
|
||||
def product_id
|
||||
@product.id
|
||||
end
|
||||
|
||||
def quantity
|
||||
@quantity
|
||||
end
|
||||
|
||||
def add_quantity(quantity)
|
||||
@quantity += quantity
|
||||
end
|
||||
|
||||
def total
|
||||
@product.price * quantity
|
||||
end
|
||||
end
|
||||
@@ -9,14 +9,12 @@ class Shop
|
||||
end
|
||||
|
||||
def add_product(product)
|
||||
raise DuplicateProductIdError if duplicate_id?(product.id)
|
||||
raise DuplicateProductIdError if product_by_id(product.id)
|
||||
|
||||
@products.push(product)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def duplicate_id?(id)
|
||||
@products.map(&:id).include?(id)
|
||||
def product_by_id(id)
|
||||
@products.find { |product| product.id == id }
|
||||
end
|
||||
end
|
||||
|
||||
+47
-15
@@ -4,35 +4,47 @@ require "checkout.rb"
|
||||
|
||||
class CheckoutTest < Minitest::Test
|
||||
def setup
|
||||
@checkout = Checkout.new
|
||||
@shop = Shop.new
|
||||
@shop.add_product(Product.new(id: 'PI5', price: 60.00))
|
||||
@shop.add_product(Product.new(id: 'ZERO2W', price: 15.00))
|
||||
@shop.add_product(Product.new(id: 'PICO2', price: 4.00))
|
||||
@checkout = Checkout.new(shop: @shop)
|
||||
end
|
||||
|
||||
def test_that_unknown_items_cannot_be_added
|
||||
assert_raises Checkout::InvalidItem do
|
||||
@checkout.add(:foo)
|
||||
@checkout.add(product_id: 'PI5000')
|
||||
end
|
||||
end
|
||||
|
||||
def test_that_nil_is_not_allowed_to_be_added
|
||||
assert_raises Checkout::InvalidItem do
|
||||
@checkout.add(nil)
|
||||
@checkout.add(product_id: nil)
|
||||
end
|
||||
end
|
||||
|
||||
def test_that_allowed_items_are_added
|
||||
@checkout.add(:PI5)
|
||||
@checkout.add(:ZERO2W)
|
||||
@checkout.add(:PICO2)
|
||||
@checkout.add(product_id: 'PI5')
|
||||
@checkout.add(product_id: 'ZERO2W')
|
||||
@checkout.add(product_id: 'PICO2')
|
||||
|
||||
assert_equal [:PI5, :ZERO2W, :PICO2].sort, @checkout.items.sort
|
||||
assert_equal({ 'PI5' => 1, 'ZERO2W' => 1, 'PICO2' => 1 }.sort, @checkout.item_quantities.sort)
|
||||
end
|
||||
|
||||
def test_that_multiples_can_be_added_at_once
|
||||
@checkout.add(product_id: 'PI5', quantity: 5)
|
||||
assert_equal({ 'PI5' => 5 }.sort, @checkout.item_quantities.sort)
|
||||
|
||||
@checkout.add(product_id: 'PI5', quantity: 5)
|
||||
assert_equal({ 'PI5' => 10 }.sort, @checkout.item_quantities.sort)
|
||||
end
|
||||
|
||||
def test_that_total_is_the_sum_of_all_items_when_no_discounts_apply
|
||||
@checkout.add(:PI5) # 60.00
|
||||
@checkout.add(:ZERO2W) # 15.00
|
||||
@checkout.add(:PICO2) # 4.00
|
||||
# -----
|
||||
# 79.00 total
|
||||
@checkout.add(product_id: 'PI5') # 60.00
|
||||
@checkout.add(product_id: 'ZERO2W') # 15.00
|
||||
@checkout.add(product_id: 'PICO2') # 4.00
|
||||
# -----
|
||||
# 79.00 total
|
||||
|
||||
assert_equal 79.00, @checkout.total
|
||||
end
|
||||
@@ -42,9 +54,9 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_we_can_empty_the_checkout
|
||||
@checkout.add(:PI5)
|
||||
@checkout.add(:ZERO2W)
|
||||
@checkout.add(:PICO2)
|
||||
@checkout.add(product_id: 'PI5')
|
||||
@checkout.add(product_id: 'ZERO2W')
|
||||
@checkout.add(product_id: 'PICO2')
|
||||
|
||||
refute_empty @checkout.items
|
||||
|
||||
@@ -57,6 +69,8 @@ class CheckoutTest < Minitest::Test
|
||||
# Discounts
|
||||
|
||||
def test_that_bogof_applied
|
||||
skip
|
||||
|
||||
4.times do
|
||||
@checkout.add(:ZERO2W) # Should remove 2 Zeros
|
||||
end
|
||||
@@ -65,6 +79,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_bogof_applied_with_odd_number
|
||||
skip
|
||||
|
||||
5.times do
|
||||
@checkout.add(:ZERO2W) # Should still remove 2 Zeros, 5th is full price
|
||||
end
|
||||
@@ -73,6 +89,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_price_is_reduced_for_3_picos
|
||||
skip
|
||||
|
||||
3.times do
|
||||
@checkout.add(:PICO2) # 3.50 * 3 = 10.50
|
||||
end
|
||||
@@ -81,6 +99,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_price_is_reduced_for_more_than_3_picos
|
||||
skip
|
||||
|
||||
4.times do
|
||||
@checkout.add(:PICO2) # 3.50 * 4 = 14
|
||||
end
|
||||
@@ -89,6 +109,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_price_is_not_reduced_for_less_than_3_picos
|
||||
skip
|
||||
|
||||
2.times do
|
||||
@checkout.add(:PICO2) # 4.00 * 2 = 8
|
||||
end
|
||||
@@ -97,6 +119,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_both_discounts_apply_at_the_same_time
|
||||
skip
|
||||
|
||||
4.times do
|
||||
@checkout.add(:ZERO2W) # 30.00 with BOGOF
|
||||
end
|
||||
@@ -111,6 +135,8 @@ class CheckoutTest < Minitest::Test
|
||||
# Item removal
|
||||
|
||||
def test_that_removing_an_item_not_in_the_checkout_removes_nothing
|
||||
skip
|
||||
|
||||
@checkout.add(:PICO2)
|
||||
@checkout.remove(:PI5)
|
||||
|
||||
@@ -118,6 +144,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_items_are_removed_one_by_one
|
||||
skip
|
||||
|
||||
4.times do
|
||||
@checkout.add(:ZERO2W)
|
||||
end
|
||||
@@ -144,6 +172,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_total_is_correctly_recalculated_when_items_are_added
|
||||
skip
|
||||
|
||||
2.times do
|
||||
@checkout.add(:PICO2)
|
||||
end
|
||||
@@ -162,6 +192,8 @@ class CheckoutTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_that_total_is_correctly_recalculated_when_items_are_removed
|
||||
skip
|
||||
|
||||
3.times do
|
||||
@checkout.add(:PICO2) # Bulk discount applies
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user