import 'package:test/test.dart'; import '../lib/shopping_cart.dart'; // ============================================================ // SHOPPING CART — Workshop Starter // Follow Red → Green → Refactor strictly. // Uncomment one test at a time. Make it pass. Then the next. // ============================================================ void main() { late ShoppingCart cart; setUp(() { cart = ShoppingCart(); }); // ────────────────────────────────────────────────────────── // STEP 1 — Empty cart // Before adding anything, the cart should be empty. // Trivial, but sets the baseline. Always start here. // ────────────────────────────────────────────────────────── group('Empty cart', () { test('subtotal is zero when cart is empty', () { // TODO: uncomment when ready // expect(cart.subtotal(), equals(0.0)); }); test('item count is zero when cart is empty', () { // TODO: uncomment when ready // expect(cart.itemCount(), equals(0)); }); }); // ────────────────────────────────────────────────────────── // STEP 2 — Adding items // A single item: subtotal = price × quantity. // ────────────────────────────────────────────────────────── group('Adding items', () { test('subtotal reflects a single item added', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 3)); // expect(cart.subtotal(), equals(4.50)); }); test('item count reflects quantity of a single item', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 3)); // expect(cart.itemCount(), equals(3)); }); test('subtotal accumulates across multiple different items', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 2)); // 3.00 // cart.addItem(CartItem(name: 'Banana', price: 0.75, quantity: 4)); // 3.00 // expect(cart.subtotal(), equals(6.00)); }); test('item count sums quantities across multiple items', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 2)); // cart.addItem(CartItem(name: 'Banana', price: 0.75, quantity: 4)); // expect(cart.itemCount(), equals(6)); }); }); // ────────────────────────────────────────────────────────── // STEP 3 — Removing items // Removing an item by name should drop it from the subtotal. // ────────────────────────────────────────────────────────── group('Removing items', () { test('removing an item reduces the subtotal', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 2)); // cart.addItem(CartItem(name: 'Banana', price: 0.75, quantity: 4)); // cart.removeItem('Apple'); // expect(cart.subtotal(), equals(3.00)); }); test('removing an item reduces the item count', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 2)); // cart.addItem(CartItem(name: 'Banana', price: 0.75, quantity: 4)); // cart.removeItem('Apple'); // expect(cart.itemCount(), equals(4)); }); test('removing a non-existent item does nothing', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 2)); // cart.removeItem('Orange'); // not in cart // expect(cart.subtotal(), equals(3.00)); }); test('cart is empty after removing the only item', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 2)); // cart.removeItem('Apple'); // expect(cart.subtotal(), equals(0.0)); // expect(cart.itemCount(), equals(0)); }); }); // ────────────────────────────────────────────────────────── // STEP 4 — Discount // A percentage discount reduces the subtotal proportionally. // Think: what is 10% off $50.00? → $45.00 // ────────────────────────────────────────────────────────── group('Applying a percentage discount', () { test('10% discount reduces subtotal correctly', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 10.00, quantity: 5)); // 50.00 // expect(cart.totalAfterDiscount(10), equals(45.00)); }); test('0% discount returns the full subtotal', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 10.00, quantity: 5)); // expect(cart.totalAfterDiscount(0), equals(50.00)); }); test('100% discount results in zero total', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Apple', price: 10.00, quantity: 5)); // expect(cart.totalAfterDiscount(100), equals(0.0)); }); test('discount on empty cart returns zero', () { // TODO: uncomment when ready // expect(cart.totalAfterDiscount(20), equals(0.0)); }); }); // ────────────────────────────────────────────────────────── // STEP 5 — CartItem validation (Value Object) // Invalid items should be rejected at construction time. // This is the "Value Object" pattern: impossible to create bad state. // ────────────────────────────────────────────────────────── group('CartItem validation', () { test('rejects an item with a zero price', () { // TODO: uncomment when ready // expect( // () => CartItem(name: 'Free thing', price: 0, quantity: 1), // throwsArgumentError, // ); }); test('rejects an item with a negative price', () { // TODO: uncomment when ready // expect( // () => CartItem(name: 'Broken item', price: -5.00, quantity: 1), // throwsArgumentError, // ); }); test('rejects an item with zero quantity', () { // TODO: uncomment when ready // expect( // () => CartItem(name: 'Apple', price: 1.50, quantity: 0), // throwsArgumentError, // ); }); }); // ────────────────────────────────────────────────────────── // BONUS — Edge cases (if you finish early) // ────────────────────────────────────────────────────────── group('Edge cases', () { test('adding the same item name twice replaces it (or accumulates — you decide!)', () { // TODO: uncomment and decide the behavior, then implement it // Hint: this is a design decision. TDD lets the test define the rule. // // Option A — replace: // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 2)); // cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 5)); // expect(cart.itemCount(), equals(5)); // replaced // // Option B — accumulate: // expect(cart.itemCount(), equals(7)); // stacked }); test('subtotal handles floating point prices accurately', () { // TODO: uncomment when ready // cart.addItem(CartItem(name: 'Item', price: 0.1, quantity: 3)); // 0.1 + 0.1 + 0.1 // expect(cart.subtotal(), closeTo(0.30, 0.001)); // use closeTo for floats! }); }); }