diff --git a/src/Cart.php b/src/Cart.php index 73019ce..b6b09e8 100644 --- a/src/Cart.php +++ b/src/Cart.php @@ -5,10 +5,13 @@ namespace Wearepixel\Cart; use Closure; +use PHPUnit\Framework\Assert; use Wearepixel\Cart\Coupons\Coupon; +use Wearepixel\Cart\Drivers\NullDriver; use Wearepixel\Cart\Helpers\Helpers; use Wearepixel\Cart\Shipping\ShippingRate; use Wearepixel\Cart\Tax\TaxRule; +use Wearepixel\Cart\Testing\CartFactory; use Wearepixel\Cart\Validators\CartItemValidator; use Wearepixel\Cart\Exceptions\InvalidItemException; use Wearepixel\Cart\Exceptions\UnknownModelException; @@ -49,6 +52,81 @@ public function getDriver(): CartDriver return $this->driver; } + public function fake(): CartFactory + { + $this->driver = new NullDriver($this->driver->getSessionKey()); + + return new CartFactory($this); + } + + public function assertContains(int|string $id): void + { + Assert::assertTrue( + $this->has($id), + "Failed asserting that cart contains item [{$id}]." + ); + } + + public function assertCount(int $count): void + { + Assert::assertCount( + $count, + $this->getContent(), + "Failed asserting that cart has [{$count}] items." + ); + } + + public function assertTotalQuantity(int|float $quantity): void + { + Assert::assertEquals( + $quantity, + $this->getTotalQuantity(), + "Failed asserting that cart total quantity is [{$quantity}]." + ); + } + + public function assertSubTotal(float $amount): void + { + Assert::assertEquals( + $amount, + $this->getSubTotal(false), + "Failed asserting that cart subtotal is [{$amount}]." + ); + } + + public function assertTotal(float $amount): void + { + Assert::assertEquals( + $amount, + $this->getTotal(), + "Failed asserting that cart total is [{$amount}]." + ); + } + + public function assertConditionApplied(string $conditionName): void + { + Assert::assertNotNull( + $this->getCondition($conditionName), + "Failed asserting that condition [{$conditionName}] is applied to the cart." + ); + } + + public function assertEmpty(): void + { + Assert::assertTrue( + $this->isEmpty(), + 'Failed asserting that the cart is empty.' + ); + } + + public function assertNotEmpty(): void + { + Assert::assertFalse( + $this->isEmpty(), + 'Failed asserting that the cart is not empty.' + ); + } + public function getSessionKey(): string { return $this->driver->getSessionKey(); diff --git a/src/Testing/CartFactory.php b/src/Testing/CartFactory.php new file mode 100644 index 0000000..9de5429 --- /dev/null +++ b/src/Testing/CartFactory.php @@ -0,0 +1,37 @@ +cart->add($i, "Test Item {$i}", 10.00, 1); + } + + return $this; + } + + public function withCondition(CartCondition $condition): static + { + $this->cart->condition($condition); + + return $this; + } + + public function withCoupon(Coupon $coupon): static + { + $this->cart->coupon($coupon); + + return $this; + } +} diff --git a/tests/Unit/Testing/CartFactoryTest.php b/tests/Unit/Testing/CartFactoryTest.php new file mode 100644 index 0000000..7127cb5 --- /dev/null +++ b/tests/Unit/Testing/CartFactoryTest.php @@ -0,0 +1,60 @@ +shouldReceive('dispatch'); + + return new Cart( + new SessionDriver(new SessionMock, 'TESTKEY'), + $events, + 'cart', + require __DIR__ . '/../../Helpers/ConfigMock.php', + ); +} + +beforeEach(function () { + $this->cart = makeCartForFactory(); + $this->factory = new CartFactory($this->cart); +}); + +afterEach(fn() => Mockery::close()); + +describe('CartFactory', function () { + test('withItems adds N generic items to the cart', function () { + $this->factory->withItems(3); + + expect($this->cart->getContent()->count())->toBe(3); + expect($this->cart->getTotalQuantity())->toBe(3); + }); + + test('withCondition adds a condition to the cart', function () { + $condition = new CartCondition(['name' => 'GST', 'type' => 'tax', 'value' => '10%', 'target' => 'subtotal']); + $this->factory->withCondition($condition); + + expect($this->cart->getCondition('GST'))->not->toBeNull(); + }); + + test('methods are chainable', function () { + $result = $this->factory->withItems(2)->withCondition( + new CartCondition(['name' => 'Discount', 'type' => 'discount', 'value' => '-5%', 'target' => 'subtotal']) + ); + + expect($result)->toBeInstanceOf(CartFactory::class); + expect($this->cart->getContent()->count())->toBe(2); + expect($this->cart->getCondition('Discount'))->not->toBeNull(); + }); + + test('withItems generates unique item IDs', function () { + $this->factory->withItems(3); + $ids = $this->cart->getContent()->keys()->toArray(); + + expect(array_unique($ids))->toHaveCount(3); + }); +}); diff --git a/tests/Unit/Testing/FakeCartTest.php b/tests/Unit/Testing/FakeCartTest.php new file mode 100644 index 0000000..58073ef --- /dev/null +++ b/tests/Unit/Testing/FakeCartTest.php @@ -0,0 +1,112 @@ +shouldReceive('dispatch'); + + return new Cart( + new SessionDriver(new SessionMock, 'TESTKEY'), + $events, + 'cart', + require __DIR__ . '/../../Helpers/ConfigMock.php', + ); +} + +beforeEach(function () { + $this->cart = makeFakeableCart(); +}); + +afterEach(fn() => Mockery::close()); + +describe('Cart::fake()', function () { + test('returns the CartFactory instance', function () { + expect($this->cart->fake())->toBeInstanceOf(\Wearepixel\Cart\Testing\CartFactory::class); + }); + + test('swaps driver to NullDriver', function () { + $this->cart->fake(); + expect($this->cart->getDriver()->getDriverName())->toBe('null'); + }); + + test('Cart operations work after fake()', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 2); + + expect($this->cart->isEmpty())->toBeFalse(); + expect($this->cart->getContent()->count())->toBe(1); + }); + + test('assertContains passes when item is in cart', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 1); + + expect(fn() => $this->cart->assertContains(1))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertContains fails when item is not in cart', function () { + $this->cart->fake(); + + expect(fn() => $this->cart->assertContains(99))->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertCount passes with correct count', function () { + $this->cart->fake(); + $this->cart->add(1, 'A', 10.00, 1); + $this->cart->add(2, 'B', 20.00, 1); + + expect(fn() => $this->cart->assertCount(2))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertTotalQuantity passes with correct quantity', function () { + $this->cart->fake(); + $this->cart->add(1, 'A', 10.00, 3); + + expect(fn() => $this->cart->assertTotalQuantity(3))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertSubTotal passes with correct subtotal', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 2); + + expect(fn() => $this->cart->assertSubTotal(100.00))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertTotal passes with correct total', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 1); + + expect(fn() => $this->cart->assertTotal(50.00))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertConditionApplied passes when condition exists', function () { + $this->cart->fake(); + $this->cart->condition(new CartCondition(['name' => 'GST', 'type' => 'tax', 'value' => '10%', 'target' => 'subtotal'])); + + expect(fn() => $this->cart->assertConditionApplied('GST'))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertConditionApplied fails when condition does not exist', function () { + $this->cart->fake(); + + expect(fn() => $this->cart->assertConditionApplied('GST'))->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertEmpty passes when cart is empty', function () { + $this->cart->fake(); + + expect(fn() => $this->cart->assertEmpty())->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertNotEmpty passes when cart has items', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 10.00, 1); + + expect(fn() => $this->cart->assertNotEmpty())->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); +});