From 0efd4378a8c4b58faef21b00101fef838802b714 Mon Sep 17 00:00:00 2001 From: Joel Male Date: Wed, 27 May 2026 21:38:44 +1000 Subject: [PATCH] feat: add HasCart trait and ShareCartWithInertia --- src/Concerns/HasCart.php | 62 +++++++++++++ src/Concerns/ShareCartWithInertia.php | 22 +++++ tests/Unit/Concerns/HasCartTest.php | 87 +++++++++++++++++++ .../Concerns/ShareCartWithInertiaTest.php | 54 ++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 src/Concerns/HasCart.php create mode 100644 src/Concerns/ShareCartWithInertia.php create mode 100644 tests/Unit/Concerns/HasCartTest.php create mode 100644 tests/Unit/Concerns/ShareCartWithInertiaTest.php diff --git a/src/Concerns/HasCart.php b/src/Concerns/HasCart.php new file mode 100644 index 0000000..b2fc165 --- /dev/null +++ b/src/Concerns/HasCart.php @@ -0,0 +1,62 @@ +getCart(); + + $this->cartItems = $cart->getContent()->toArray(); + $this->cartSubTotal = (float) $cart->getSubTotal(false); + $this->cartTotal = (float) $cart->getTotal(); + $this->cartCount = (int) $cart->getTotalQuantity(); + } + + public function cartAdd( + int|string $id, + string $name, + float $price, + int $quantity = 1, + array $attributes = [], + ): void { + $this->getCart()->add($id, $name, $price, $quantity, $attributes); + $this->refreshCart(); + } + + public function cartRemove(int|string $id): void + { + $this->getCart()->remove($id); + $this->refreshCart(); + } + + public function cartUpdate(int|string $id, array $data): void + { + $this->getCart()->update($id, $data); + $this->refreshCart(); + } + + public function cartClear(): void + { + $this->getCart()->clear(); + $this->refreshCart(); + } +} diff --git a/src/Concerns/ShareCartWithInertia.php b/src/Concerns/ShareCartWithInertia.php new file mode 100644 index 0000000..b5b733b --- /dev/null +++ b/src/Concerns/ShareCartWithInertia.php @@ -0,0 +1,22 @@ + $cart->getContent()->toArray(), + 'subtotal' => (float) $cart->getSubTotal(false), + 'total' => (float) $cart->getTotal(), + 'count' => (int) $cart->getTotalQuantity(), + ]; + } +} diff --git a/tests/Unit/Concerns/HasCartTest.php b/tests/Unit/Concerns/HasCartTest.php new file mode 100644 index 0000000..e2bd8fb --- /dev/null +++ b/tests/Unit/Concerns/HasCartTest.php @@ -0,0 +1,87 @@ +cart; + } +} + +function makeComponent(): FakeComponent +{ + $events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); + $events->shouldReceive('dispatch'); + + $cart = new Cart( + new SessionDriver(new SessionMock, 'TESTKEY'), + $events, + 'cart', + require __DIR__ . '/../../Helpers/ConfigMock.php', + ); + + return new FakeComponent($cart); +} + +beforeEach(function () { + $this->component = makeComponent(); +}); + +afterEach(fn() => Mockery::close()); + +describe('HasCart trait', function () { + test('refreshCart() populates cartItems', function () { + $this->component->cartAdd(1, 'Widget', 50.00, 2); + + expect($this->component->cartItems)->not->toBeEmpty(); + }); + + test('cartAdd() adds an item and refreshes state', function () { + $this->component->cartAdd(1, 'Widget', 50.00, 1); + + expect($this->component->cartCount)->toBe(1); + expect($this->component->cartSubTotal)->toBe(50.00); + }); + + test('cartRemove() removes an item and refreshes state', function () { + $this->component->cartAdd(1, 'Widget', 50.00, 1); + $this->component->cartRemove(1); + + expect($this->component->cartCount)->toBe(0); + }); + + test('cartUpdate() updates an item and refreshes state', function () { + $this->component->cartAdd(1, 'Widget', 50.00, 1); + $this->component->cartUpdate(1, ['quantity' => ['relative' => false, 'value' => 3]]); + + expect($this->component->cartCount)->toBe(3); + }); + + test('cartClear() empties the cart and refreshes state', function () { + $this->component->cartAdd(1, 'Widget', 50.00, 1); + $this->component->cartClear(); + + expect($this->component->cartCount)->toBe(0); + expect($this->component->cartItems)->toBeEmpty(); + }); + + test('cartTotal reflects the cart total', function () { + $this->component->cartAdd(1, 'Widget', 100.00, 1); + + expect($this->component->cartTotal)->toBe(100.00); + }); +}); diff --git a/tests/Unit/Concerns/ShareCartWithInertiaTest.php b/tests/Unit/Concerns/ShareCartWithInertiaTest.php new file mode 100644 index 0000000..895de26 --- /dev/null +++ b/tests/Unit/Concerns/ShareCartWithInertiaTest.php @@ -0,0 +1,54 @@ +shouldReceive('dispatch'); + + return new Cart( + new SessionDriver(new SessionMock, 'TESTKEY'), + $events, + 'cart', + require __DIR__ . '/../../Helpers/ConfigMock.php', + ); +} + +beforeEach(function () { + $this->cart = makeCartForInertia(); +}); + +afterEach(fn() => Mockery::close()); + +describe('ShareCartWithInertia', function () { + test('data() returns items, total, subtotal, and count', function () { + $this->cart->add(1, 'Widget', 50.00, 2); + + $data = ShareCartWithInertia::data($this->cart); + + expect($data)->toHaveKeys(['items', 'total', 'subtotal', 'count']); + expect($data['count'])->toBe(2); + expect($data['subtotal'])->toBe(100.00); + expect($data['total'])->toBe(100.00); + }); + + test('data() returns empty state when cart is empty', function () { + $data = ShareCartWithInertia::data($this->cart); + + expect($data['items'])->toBeEmpty(); + expect($data['count'])->toBe(0); + }); + + test('data() items are plain arrays suitable for JSON serialization', function () { + $this->cart->add(1, 'Widget', 50.00, 1); + + $data = ShareCartWithInertia::data($this->cart); + + expect($data['items'])->toBeArray(); + expect(json_encode($data))->toBeString(); + }); +});