From d60724d2ade3081b167817e0c90eb5dd3bb31822 Mon Sep 17 00:00:00 2001 From: "Fabian @ Blax Software" Date: Tue, 9 Dec 2025 10:30:53 +0100 Subject: [PATCH] A removeFromCart cart method --- src/Models/Cart.php | 35 +++++ tests/Feature/CartManagementTest.php | 205 +++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/src/Models/Cart.php b/src/Models/Cart.php index a243644..7c2902d 100644 --- a/src/Models/Cart.php +++ b/src/Models/Cart.php @@ -198,6 +198,41 @@ class Cart extends Model return $cartItem->fresh(); } + public function removeFromCart( + Model $cartable, + int $quantity = 1, + array $parameters = [] + ): CartItem|true { + $item = $this->items() + ->where('purchasable_id', $cartable->getKey()) + ->where('purchasable_type', get_class($cartable)) + ->get() + ->first(function ($item) use ($parameters) { + $existingParams = is_array($item->parameters) + ? $item->parameters + : (array) $item->parameters; + ksort($existingParams); + ksort($parameters); + return $existingParams === $parameters; + }); + + if ($item) { + if ($item->quantity > $quantity) { + // Decrease quantity + $newQuantity = $item->quantity - $quantity; + $item->update([ + 'quantity' => $newQuantity, + 'subtotal' => ($cartable->getCurrentPrice()) * $newQuantity, + ]); + } else { + // Remove item from cart + $item->delete(); + } + } + + return $item ?? true; + } + public function checkout(): static { $items = $this->items() diff --git a/tests/Feature/CartManagementTest.php b/tests/Feature/CartManagementTest.php index e2eb0de..a51a929 100644 --- a/tests/Feature/CartManagementTest.php +++ b/tests/Feature/CartManagementTest.php @@ -396,4 +396,209 @@ class CartManagementTest extends TestCase $this->assertEquals(4, $cartWithProduct->getTotalItems()); $this->assertEquals((150.00 * 2) + (120 * 2), $cartWithProduct->getTotal()); } + + /** @test */ + public function it_can_remove_item_from_cart_completely() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 50.00, + ]); + + $cartItem = $cart->addToCart($price, quantity: 2); + $this->assertCount(1, $cart->items); + + $result = $cart->removeFromCart($price, quantity: 2); + + $this->assertCount(0, $cart->refresh()->items); + $this->assertTrue(true); // Item was deleted + } + + /** @test */ + public function it_can_decrease_cart_item_quantity() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 75.00, + ]); + + $cartItem = $cart->addToCart($price, quantity: 5); + $this->assertEquals(5, $cartItem->quantity); + + $result = $cart->removeFromCart($price, quantity: 2); + + $updatedItem = $cart->items->first(); + $this->assertEquals(3, $updatedItem->quantity); + $this->assertEquals(75.00 * 3, $updatedItem->subtotal); + } + + /** @test */ + public function it_updates_subtotal_correctly_when_decreasing_quantity() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 100.00, + ]); + + $cart->addToCart($price, quantity: 4); + + $cart->removeFromCart($price, quantity: 1); + + $cartItem = $cart->items->first(); + $this->assertEquals(3, $cartItem->quantity); + $this->assertEquals(300.00, $cartItem->subtotal); + } + + /** @test */ + public function it_respects_parameters_when_removing_from_cart() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 50.00, + ]); + + // Add same product with different parameters + $cartItem1 = $cart->addToCart( + $price, + quantity: 2, + parameters: ['color' => 'blue'] + ); + + $cartItem2 = $cart->addToCart( + $price, + quantity: 3, + parameters: ['color' => 'red'] + ); + + $this->assertCount(2, $cart->items); + + // Remove only the blue item + $cart->removeFromCart($price, quantity: 2, parameters: ['color' => 'blue']); + + $this->assertCount(1, $cart->refresh()->items); + $this->assertEquals('red', $cart->items->first()->parameters['color']); + $this->assertEquals(3, $cart->items->first()->quantity); + } + + /** @test */ + public function it_decreases_only_matching_parameters_when_removing() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 50.00, + ]); + + $cart->addToCart( + $price, + quantity: 5, + parameters: ['size' => 'large'] + ); + + $cart->removeFromCart($price, quantity: 2, parameters: ['size' => 'large']); + + $cartItem = $cart->items->first(); + $this->assertEquals(3, $cartItem->quantity); + $this->assertEquals('large', $cartItem->parameters['size']); + } + + /** @test */ + public function it_returns_cart_item_when_quantity_is_decreased() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 50.00, + ]); + + $cart->addToCart($price, quantity: 5); + + $result = $cart->removeFromCart($price, quantity: 2); + + $this->assertInstanceOf(CartItem::class, $result); + $this->assertEquals(3, $result->quantity); + } + + /** @test */ + public function it_handles_removing_nonexistent_item_gracefully() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 50.00, + ]); + + $result = $cart->removeFromCart($price, quantity: 1); + + // Should return true when item doesn't exist + $this->assertTrue($result); + $this->assertCount(0, $cart->items); + } + + /** @test */ + public function it_updates_cart_total_after_removing_items() + { + $cart = Cart::create(); + $product = Product::factory()->create(); + $price = ProductPrice::factory()->create([ + 'purchasable_id' => $product->id, + 'purchasable_type' => get_class($product), + 'unit_amount' => 50.00, + ]); + + $cart->addToCart($price, quantity: 5); + $this->assertEquals(250.00, $cart->getTotal()); + + $cart->removeFromCart($price, quantity: 2); + + $this->assertEquals(150.00, $cart->refresh()->getTotal()); + } + + /** @test */ + public function it_can_remove_from_cart_with_multiple_items() + { + $cart = Cart::create(); + $product1 = Product::factory()->create(); + $product2 = Product::factory()->create(); + + $price1 = ProductPrice::factory()->create([ + 'purchasable_id' => $product1->id, + 'purchasable_type' => get_class($product1), + 'unit_amount' => 50.00, + ]); + + $price2 = ProductPrice::factory()->create([ + 'purchasable_id' => $product2->id, + 'purchasable_type' => get_class($product2), + 'unit_amount' => 75.00, + ]); + + $cart->addToCart($price1, quantity: 2); + $cart->addToCart($price2, quantity: 3); + $this->assertCount(2, $cart->items); + + $cart->removeFromCart($price1, quantity: 2); + + $this->assertCount(1, $cart->refresh()->items); + $this->assertEquals($price2->id, $cart->items->first()->purchasable_id); + $this->assertEquals(225.00, $cart->getTotal()); + } }