From a05e6e92dedf4fee88ade8e556a017a576c1bfbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:25:13 +0000 Subject: [PATCH 1/4] Initial plan From caa619c2ec397a0c368d1fd615f81f14a6261bcb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:28:53 +0000 Subject: [PATCH 2/4] Initial analysis and fix Java version for compilation Co-authored-by: nstubbe <20206435+nstubbe@users.noreply.github.com> --- java/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index 73ba4a7..c62567d 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -45,8 +45,8 @@ maven-compiler-plugin 3.8.0 - 22 - 22 + 17 + 17 From e5dfd3953f9170d523164116572887369fe81f33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:32:17 +0000 Subject: [PATCH 3/4] Implement Strategy and Factory patterns for discount calculations Co-authored-by: nstubbe <20206435+nstubbe@users.noreply.github.com> --- .../supermarket/model/DiscountStrategy.java | 5 + .../model/DiscountStrategyFactory.java | 18 ++++ .../model/FiveForAmountDiscountStrategy.java | 15 +++ .../dojo/supermarket/model/ShoppingCart.java | 36 ++----- .../model/TenPercentDiscountStrategy.java | 8 ++ .../model/ThreeForTwoDiscountStrategy.java | 15 +++ .../model/TwoForAmountDiscountStrategy.java | 15 +++ .../model/DiscountStrategyFactoryTest.java | 24 +++++ .../model/DiscountStrategyTest.java | 100 +++++++++++++++++ .../model/ShoppingCartDiscountTest.java | 101 ++++++++++++++++++ 10 files changed, 307 insertions(+), 30 deletions(-) create mode 100644 java/src/main/java/dojo/supermarket/model/DiscountStrategy.java create mode 100644 java/src/main/java/dojo/supermarket/model/DiscountStrategyFactory.java create mode 100644 java/src/main/java/dojo/supermarket/model/FiveForAmountDiscountStrategy.java create mode 100644 java/src/main/java/dojo/supermarket/model/TenPercentDiscountStrategy.java create mode 100644 java/src/main/java/dojo/supermarket/model/ThreeForTwoDiscountStrategy.java create mode 100644 java/src/main/java/dojo/supermarket/model/TwoForAmountDiscountStrategy.java create mode 100644 java/src/test/java/dojo/supermarket/model/DiscountStrategyFactoryTest.java create mode 100644 java/src/test/java/dojo/supermarket/model/DiscountStrategyTest.java create mode 100644 java/src/test/java/dojo/supermarket/model/ShoppingCartDiscountTest.java diff --git a/java/src/main/java/dojo/supermarket/model/DiscountStrategy.java b/java/src/main/java/dojo/supermarket/model/DiscountStrategy.java new file mode 100644 index 0000000..414b016 --- /dev/null +++ b/java/src/main/java/dojo/supermarket/model/DiscountStrategy.java @@ -0,0 +1,5 @@ +package dojo.supermarket.model; + +public interface DiscountStrategy { + Discount calculateDiscount(Product product, double quantity, double unitPrice, double argument); +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/model/DiscountStrategyFactory.java b/java/src/main/java/dojo/supermarket/model/DiscountStrategyFactory.java new file mode 100644 index 0000000..c6eb91a --- /dev/null +++ b/java/src/main/java/dojo/supermarket/model/DiscountStrategyFactory.java @@ -0,0 +1,18 @@ +package dojo.supermarket.model; + +public class DiscountStrategyFactory { + public static DiscountStrategy createStrategy(SpecialOfferType offerType) { + switch (offerType) { + case THREE_FOR_TWO: + return new ThreeForTwoDiscountStrategy(); + case TEN_PERCENT_DISCOUNT: + return new TenPercentDiscountStrategy(); + case TWO_FOR_AMOUNT: + return new TwoForAmountDiscountStrategy(); + case FIVE_FOR_AMOUNT: + return new FiveForAmountDiscountStrategy(); + default: + throw new IllegalArgumentException("Unknown offer type: " + offerType); + } + } +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/model/FiveForAmountDiscountStrategy.java b/java/src/main/java/dojo/supermarket/model/FiveForAmountDiscountStrategy.java new file mode 100644 index 0000000..cdc7363 --- /dev/null +++ b/java/src/main/java/dojo/supermarket/model/FiveForAmountDiscountStrategy.java @@ -0,0 +1,15 @@ +package dojo.supermarket.model; + +public class FiveForAmountDiscountStrategy implements DiscountStrategy { + @Override + public Discount calculateDiscount(Product product, double quantity, double unitPrice, double argument) { + int quantityAsInt = (int) quantity; + if (quantityAsInt >= 5) { + int x = 5; + int numberOfXs = quantityAsInt / x; + double discountTotal = unitPrice * quantity - (argument * numberOfXs + quantityAsInt % 5 * unitPrice); + return new Discount(product, x + " for " + argument, -discountTotal); + } + return null; + } +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/model/ShoppingCart.java b/java/src/main/java/dojo/supermarket/model/ShoppingCart.java index 9293086..bfe5a78 100644 --- a/java/src/main/java/dojo/supermarket/model/ShoppingCart.java +++ b/java/src/main/java/dojo/supermarket/model/ShoppingCart.java @@ -38,37 +38,13 @@ void handleOffers(Receipt receipt, Map offers, SupermarketCatalo if (offers.containsKey(p)) { Offer offer = offers.get(p); double unitPrice = catalog.getUnitPrice(p); - int quantityAsInt = (int) quantity; - Discount discount = null; - int x = 1; - if (offer.offerType == SpecialOfferType.THREE_FOR_TWO) { - x = 3; - - } else if (offer.offerType == SpecialOfferType.TWO_FOR_AMOUNT) { - x = 2; - if (quantityAsInt >= 2) { - double total = offer.argument * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice; - double discountN = unitPrice * quantity - total; - discount = new Discount(p, "2 for " + offer.argument, -discountN); - } - - } if (offer.offerType == SpecialOfferType.FIVE_FOR_AMOUNT) { - x = 5; - } - int numberOfXs = quantityAsInt / x; - if (offer.offerType == SpecialOfferType.THREE_FOR_TWO && quantityAsInt > 2) { - double discountAmount = quantity * unitPrice - ((numberOfXs * 2 * unitPrice) + quantityAsInt % 3 * unitPrice); - discount = new Discount(p, "3 for 2", -discountAmount); - } - if (offer.offerType == SpecialOfferType.TEN_PERCENT_DISCOUNT) { - discount = new Discount(p, offer.argument + "% off", -quantity * unitPrice * offer.argument / 100.0); - } - if (offer.offerType == SpecialOfferType.FIVE_FOR_AMOUNT && quantityAsInt >= 5) { - double discountTotal = unitPrice * quantity - (offer.argument * numberOfXs + quantityAsInt % 5 * unitPrice); - discount = new Discount(p, x + " for " + offer.argument, -discountTotal); - } - if (discount != null) + + DiscountStrategy strategy = DiscountStrategyFactory.createStrategy(offer.offerType); + Discount discount = strategy.calculateDiscount(p, quantity, unitPrice, offer.argument); + + if (discount != null) { receipt.addDiscount(discount); + } } } } diff --git a/java/src/main/java/dojo/supermarket/model/TenPercentDiscountStrategy.java b/java/src/main/java/dojo/supermarket/model/TenPercentDiscountStrategy.java new file mode 100644 index 0000000..837d5a9 --- /dev/null +++ b/java/src/main/java/dojo/supermarket/model/TenPercentDiscountStrategy.java @@ -0,0 +1,8 @@ +package dojo.supermarket.model; + +public class TenPercentDiscountStrategy implements DiscountStrategy { + @Override + public Discount calculateDiscount(Product product, double quantity, double unitPrice, double argument) { + return new Discount(product, argument + "% off", -quantity * unitPrice * argument / 100.0); + } +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/model/ThreeForTwoDiscountStrategy.java b/java/src/main/java/dojo/supermarket/model/ThreeForTwoDiscountStrategy.java new file mode 100644 index 0000000..3d856fb --- /dev/null +++ b/java/src/main/java/dojo/supermarket/model/ThreeForTwoDiscountStrategy.java @@ -0,0 +1,15 @@ +package dojo.supermarket.model; + +public class ThreeForTwoDiscountStrategy implements DiscountStrategy { + @Override + public Discount calculateDiscount(Product product, double quantity, double unitPrice, double argument) { + int quantityAsInt = (int) quantity; + if (quantityAsInt > 2) { + int x = 3; + int numberOfXs = quantityAsInt / x; + double discountAmount = quantity * unitPrice - ((numberOfXs * 2 * unitPrice) + quantityAsInt % 3 * unitPrice); + return new Discount(product, "3 for 2", -discountAmount); + } + return null; + } +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/model/TwoForAmountDiscountStrategy.java b/java/src/main/java/dojo/supermarket/model/TwoForAmountDiscountStrategy.java new file mode 100644 index 0000000..472ca15 --- /dev/null +++ b/java/src/main/java/dojo/supermarket/model/TwoForAmountDiscountStrategy.java @@ -0,0 +1,15 @@ +package dojo.supermarket.model; + +public class TwoForAmountDiscountStrategy implements DiscountStrategy { + @Override + public Discount calculateDiscount(Product product, double quantity, double unitPrice, double argument) { + int quantityAsInt = (int) quantity; + if (quantityAsInt >= 2) { + int x = 2; + double total = argument * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice; + double discountN = unitPrice * quantity - total; + return new Discount(product, "2 for " + argument, -discountN); + } + return null; + } +} \ No newline at end of file diff --git a/java/src/test/java/dojo/supermarket/model/DiscountStrategyFactoryTest.java b/java/src/test/java/dojo/supermarket/model/DiscountStrategyFactoryTest.java new file mode 100644 index 0000000..7125547 --- /dev/null +++ b/java/src/test/java/dojo/supermarket/model/DiscountStrategyFactoryTest.java @@ -0,0 +1,24 @@ +package dojo.supermarket.model; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class DiscountStrategyFactoryTest { + + @Test + void createStrategy_shouldReturnCorrectStrategyForEachOfferType() { + DiscountStrategy strategy; + + strategy = DiscountStrategyFactory.createStrategy(SpecialOfferType.THREE_FOR_TWO); + assertTrue(strategy instanceof ThreeForTwoDiscountStrategy); + + strategy = DiscountStrategyFactory.createStrategy(SpecialOfferType.TEN_PERCENT_DISCOUNT); + assertTrue(strategy instanceof TenPercentDiscountStrategy); + + strategy = DiscountStrategyFactory.createStrategy(SpecialOfferType.TWO_FOR_AMOUNT); + assertTrue(strategy instanceof TwoForAmountDiscountStrategy); + + strategy = DiscountStrategyFactory.createStrategy(SpecialOfferType.FIVE_FOR_AMOUNT); + assertTrue(strategy instanceof FiveForAmountDiscountStrategy); + } +} \ No newline at end of file diff --git a/java/src/test/java/dojo/supermarket/model/DiscountStrategyTest.java b/java/src/test/java/dojo/supermarket/model/DiscountStrategyTest.java new file mode 100644 index 0000000..e714639 --- /dev/null +++ b/java/src/test/java/dojo/supermarket/model/DiscountStrategyTest.java @@ -0,0 +1,100 @@ +package dojo.supermarket.model; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class DiscountStrategyTest { + + private final Product testProduct = new Product("test", ProductUnit.EACH); + + @Test + void threeForTwoStrategy_shouldCalculateCorrectDiscount() { + DiscountStrategy strategy = new ThreeForTwoDiscountStrategy(); + + // Test with 3 items - should get discount + Discount discount = strategy.calculateDiscount(testProduct, 3.0, 1.0, 0.0); + assertNotNull(discount); + assertEquals("3 for 2", discount.getDescription()); + assertEquals(-1.0, discount.getDiscountAmount(), 0.01); // Pay for 2, get 1 free + + // Test with 6 items - should get discount for 2 sets + discount = strategy.calculateDiscount(testProduct, 6.0, 1.0, 0.0); + assertNotNull(discount); + assertEquals(-2.0, discount.getDiscountAmount(), 0.01); // Pay for 4, get 2 free + + // Test with 2 items - should not get discount + discount = strategy.calculateDiscount(testProduct, 2.0, 1.0, 0.0); + assertNull(discount); + + // Test with 5 items - should get discount for 1 set, pay full for remaining 2 + discount = strategy.calculateDiscount(testProduct, 5.0, 1.0, 0.0); + assertNotNull(discount); + assertEquals(-1.0, discount.getDiscountAmount(), 0.01); + } + + @Test + void tenPercentDiscountStrategy_shouldCalculateCorrectDiscount() { + DiscountStrategy strategy = new TenPercentDiscountStrategy(); + + Discount discount = strategy.calculateDiscount(testProduct, 2.0, 1.0, 10.0); + assertNotNull(discount); + assertEquals("10.0% off", discount.getDescription()); + assertEquals(-0.2, discount.getDiscountAmount(), 0.01); // 10% of 2.0 + + // Test with different percentage + discount = strategy.calculateDiscount(testProduct, 5.0, 2.0, 20.0); + assertNotNull(discount); + assertEquals("20.0% off", discount.getDescription()); + assertEquals(-2.0, discount.getDiscountAmount(), 0.01); // 20% of 10.0 + } + + @Test + void twoForAmountStrategy_shouldCalculateCorrectDiscount() { + DiscountStrategy strategy = new TwoForAmountDiscountStrategy(); + + // Test with 2 items - should get discount + Discount discount = strategy.calculateDiscount(testProduct, 2.0, 1.0, 1.5); + assertNotNull(discount); + assertEquals("2 for 1.5", discount.getDescription()); + assertEquals(-0.5, discount.getDiscountAmount(), 0.01); // Save 0.5 (2.0 - 1.5) + + // Test with 4 items - should get discount for 2 sets + discount = strategy.calculateDiscount(testProduct, 4.0, 1.0, 1.5); + assertNotNull(discount); + assertEquals(-1.0, discount.getDiscountAmount(), 0.01); // Save 1.0 (4.0 - 3.0) + + // Test with 1 item - should not get discount + discount = strategy.calculateDiscount(testProduct, 1.0, 1.0, 1.5); + assertNull(discount); + + // Test with 3 items - should get discount for 1 set, pay full for remaining 1 + discount = strategy.calculateDiscount(testProduct, 3.0, 1.0, 1.5); + assertNotNull(discount); + assertEquals(-0.5, discount.getDiscountAmount(), 0.01); // Save 0.5 (3.0 - 2.5) + } + + @Test + void fiveForAmountStrategy_shouldCalculateCorrectDiscount() { + DiscountStrategy strategy = new FiveForAmountDiscountStrategy(); + + // Test with 5 items - should get discount + Discount discount = strategy.calculateDiscount(testProduct, 5.0, 2.0, 7.0); + assertNotNull(discount); + assertEquals("5 for 7.0", discount.getDescription()); + assertEquals(-3.0, discount.getDiscountAmount(), 0.01); // Save 3.0 (10.0 - 7.0) + + // Test with 10 items - should get discount for 2 sets + discount = strategy.calculateDiscount(testProduct, 10.0, 2.0, 7.0); + assertNotNull(discount); + assertEquals(-6.0, discount.getDiscountAmount(), 0.01); // Save 6.0 (20.0 - 14.0) + + // Test with 4 items - should not get discount + discount = strategy.calculateDiscount(testProduct, 4.0, 2.0, 7.0); + assertNull(discount); + + // Test with 7 items - should get discount for 1 set, pay full for remaining 2 + discount = strategy.calculateDiscount(testProduct, 7.0, 2.0, 7.0); + assertNotNull(discount); + assertEquals(-3.0, discount.getDiscountAmount(), 0.01); // Save 3.0 (14.0 - 11.0) + } +} \ No newline at end of file diff --git a/java/src/test/java/dojo/supermarket/model/ShoppingCartDiscountTest.java b/java/src/test/java/dojo/supermarket/model/ShoppingCartDiscountTest.java new file mode 100644 index 0000000..e2e233e --- /dev/null +++ b/java/src/test/java/dojo/supermarket/model/ShoppingCartDiscountTest.java @@ -0,0 +1,101 @@ +package dojo.supermarket.model; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ShoppingCartDiscountTest { + + @Test + void handleOffers_shouldApplyThreeForTwoDiscount() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + + Teller teller = new Teller(catalog); + teller.addSpecialOffer(SpecialOfferType.THREE_FOR_TWO, toothbrush, 0.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 3.0); + + Receipt receipt = teller.checksOutArticlesFrom(cart); + + assertEquals(1.98, receipt.getTotalPrice(), 0.01); // Pay for 2, get 1 free + assertEquals(1, receipt.getDiscounts().size()); + assertEquals("3 for 2", receipt.getDiscounts().get(0).getDescription()); + } + + @Test + void handleOffers_shouldApplyTenPercentDiscount() { + SupermarketCatalog catalog = new FakeCatalog(); + Product apples = new Product("apples", ProductUnit.KILO); + catalog.addProduct(apples, 1.99); + + Teller teller = new Teller(catalog); + teller.addSpecialOffer(SpecialOfferType.TEN_PERCENT_DISCOUNT, apples, 10.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(apples, 2.0); + + Receipt receipt = teller.checksOutArticlesFrom(cart); + + assertEquals(3.582, receipt.getTotalPrice(), 0.01); // 2 * 1.99 * 0.9 + assertEquals(1, receipt.getDiscounts().size()); + assertEquals("10.0% off", receipt.getDiscounts().get(0).getDescription()); + } + + @Test + void handleOffers_shouldApplyTwoForAmountDiscount() { + SupermarketCatalog catalog = new FakeCatalog(); + Product cherryTomatoes = new Product("cherry tomatoes", ProductUnit.EACH); + catalog.addProduct(cherryTomatoes, 0.69); + + Teller teller = new Teller(catalog); + teller.addSpecialOffer(SpecialOfferType.TWO_FOR_AMOUNT, cherryTomatoes, 0.99); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(cherryTomatoes, 2.0); + + Receipt receipt = teller.checksOutArticlesFrom(cart); + + assertEquals(0.99, receipt.getTotalPrice(), 0.01); + assertEquals(1, receipt.getDiscounts().size()); + assertEquals("2 for 0.99", receipt.getDiscounts().get(0).getDescription()); + } + + @Test + void handleOffers_shouldApplyFiveForAmountDiscount() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothpaste = new Product("toothpaste", ProductUnit.EACH); + catalog.addProduct(toothpaste, 1.79); + + Teller teller = new Teller(catalog); + teller.addSpecialOffer(SpecialOfferType.FIVE_FOR_AMOUNT, toothpaste, 7.49); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothpaste, 5.0); + + Receipt receipt = teller.checksOutArticlesFrom(cart); + + assertEquals(7.49, receipt.getTotalPrice(), 0.01); + assertEquals(1, receipt.getDiscounts().size()); + assertEquals("5 for 7.49", receipt.getDiscounts().get(0).getDescription()); + } + + @Test + void handleOffers_shouldNotApplyDiscountWhenQuantityTooLow() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + + Teller teller = new Teller(catalog); + teller.addSpecialOffer(SpecialOfferType.THREE_FOR_TWO, toothbrush, 0.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 2.0); + + Receipt receipt = teller.checksOutArticlesFrom(cart); + + assertEquals(1.98, receipt.getTotalPrice(), 0.01); // No discount + assertEquals(0, receipt.getDiscounts().size()); + } +} \ No newline at end of file From 5aa614352a3a81e2392cfd882d2f8de015c7ef9e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:36:33 +0000 Subject: [PATCH 4/4] Implement Strategy pattern for receipt formatting (Text and HTML) Co-authored-by: nstubbe <20206435+nstubbe@users.noreply.github.com> --- .../supermarket/HtmlReceiptFormatter.java | 82 +++++++++++++ .../dojo/supermarket/ReceiptFormatter.java | 7 ++ .../java/dojo/supermarket/ReceiptPrinter.java | 30 +++++ .../supermarket/TextReceiptFormatter.java} | 41 +++---- .../supermarket/ReceiptFormatterTest.java | 115 ++++++++++++++++++ 5 files changed, 251 insertions(+), 24 deletions(-) create mode 100644 java/src/main/java/dojo/supermarket/HtmlReceiptFormatter.java create mode 100644 java/src/main/java/dojo/supermarket/ReceiptFormatter.java create mode 100644 java/src/main/java/dojo/supermarket/ReceiptPrinter.java rename java/src/{test/java/dojo/supermarket/ReceiptPrinter.java => main/java/dojo/supermarket/TextReceiptFormatter.java} (71%) create mode 100644 java/src/test/java/dojo/supermarket/ReceiptFormatterTest.java diff --git a/java/src/main/java/dojo/supermarket/HtmlReceiptFormatter.java b/java/src/main/java/dojo/supermarket/HtmlReceiptFormatter.java new file mode 100644 index 0000000..a044a59 --- /dev/null +++ b/java/src/main/java/dojo/supermarket/HtmlReceiptFormatter.java @@ -0,0 +1,82 @@ +package dojo.supermarket; + +import dojo.supermarket.model.*; + +import java.util.Locale; + +public class HtmlReceiptFormatter implements ReceiptFormatter { + + @Override + public String formatReceipt(Receipt receipt, int columns) { + StringBuilder result = new StringBuilder(); + + result.append("\n\n"); + result.append("

Receipt

\n"); + result.append("\n"); + + for (ReceiptItem item : receipt.getItems()) { + String receiptItem = presentReceiptItem(item); + result.append(receiptItem); + } + + for (Discount discount : receipt.getDiscounts()) { + String discountPresentation = presentDiscount(discount); + result.append(discountPresentation); + } + + result.append(presentTotal(receipt)); + result.append("
\n\n"); + return result.toString(); + } + + private String presentReceiptItem(ReceiptItem item) { + StringBuilder result = new StringBuilder(); + result.append(" \n"); + result.append(" ").append(escapeHtml(item.getProduct().getName())).append("\n"); + result.append(" ").append(presentPrice(item.getTotalPrice())).append("\n"); + result.append(" \n"); + + if (item.getQuantity() != 1) { + result.append(" \n"); + result.append(" ").append(presentPrice(item.getPrice())) + .append(" * ").append(presentQuantity(item)).append("\n"); + result.append(" \n"); + } + return result.toString(); + } + + private String presentDiscount(Discount discount) { + String name = discount.getDescription() + "(" + discount.getProduct().getName() + ")"; + String value = presentPrice(discount.getDiscountAmount()); + + return " \n" + + " " + escapeHtml(name) + "\n" + + " " + value + "\n" + + " \n"; + } + + private String presentTotal(Receipt receipt) { + return " \n" + + " Total:\n" + + " " + presentPrice(receipt.getTotalPrice()) + "\n" + + " \n"; + } + + private static String presentPrice(double price) { + return String.format(Locale.UK, "%.2f", price); + } + + private static String presentQuantity(ReceiptItem item) { + return ProductUnit.EACH.equals(item.getProduct().getUnit()) + ? String.format("%d", (int)item.getQuantity()) + : String.format(Locale.UK, "%.3f", item.getQuantity()); + } + + private static String escapeHtml(String text) { + return text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/ReceiptFormatter.java b/java/src/main/java/dojo/supermarket/ReceiptFormatter.java new file mode 100644 index 0000000..e8d131e --- /dev/null +++ b/java/src/main/java/dojo/supermarket/ReceiptFormatter.java @@ -0,0 +1,7 @@ +package dojo.supermarket; + +import dojo.supermarket.model.Receipt; + +public interface ReceiptFormatter { + String formatReceipt(Receipt receipt, int columns); +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/ReceiptPrinter.java b/java/src/main/java/dojo/supermarket/ReceiptPrinter.java new file mode 100644 index 0000000..5a802cb --- /dev/null +++ b/java/src/main/java/dojo/supermarket/ReceiptPrinter.java @@ -0,0 +1,30 @@ +package dojo.supermarket; + +import dojo.supermarket.model.*; + +public class ReceiptPrinter { + + private final int columns; + private final ReceiptFormatter formatter; + + public ReceiptPrinter() { + this(40, new TextReceiptFormatter()); + } + + public ReceiptPrinter(int columns) { + this(columns, new TextReceiptFormatter()); + } + + public ReceiptPrinter(ReceiptFormatter formatter) { + this(40, formatter); + } + + public ReceiptPrinter(int columns, ReceiptFormatter formatter) { + this.columns = columns; + this.formatter = formatter; + } + + public String printReceipt(Receipt receipt) { + return formatter.formatReceipt(receipt, columns); + } +} diff --git a/java/src/test/java/dojo/supermarket/ReceiptPrinter.java b/java/src/main/java/dojo/supermarket/TextReceiptFormatter.java similarity index 71% rename from java/src/test/java/dojo/supermarket/ReceiptPrinter.java rename to java/src/main/java/dojo/supermarket/TextReceiptFormatter.java index 070a13f..4e155ce 100644 --- a/java/src/test/java/dojo/supermarket/ReceiptPrinter.java +++ b/java/src/main/java/dojo/supermarket/TextReceiptFormatter.java @@ -4,39 +4,32 @@ import java.util.Locale; -public class ReceiptPrinter { +public class TextReceiptFormatter implements ReceiptFormatter { - private final int columns; - - public ReceiptPrinter() { - this(40); - } - - public ReceiptPrinter(int columns) { - this.columns = columns; - } - - public String printReceipt(Receipt receipt) { + @Override + public String formatReceipt(Receipt receipt, int columns) { StringBuilder result = new StringBuilder(); + for (ReceiptItem item : receipt.getItems()) { - String receiptItem = presentReceiptItem(item); + String receiptItem = presentReceiptItem(item, columns); result.append(receiptItem); } + for (Discount discount : receipt.getDiscounts()) { - String discountPresentation = presentDiscount(discount); + String discountPresentation = presentDiscount(discount, columns); result.append(discountPresentation); } result.append("\n"); - result.append(presentTotal(receipt)); + result.append(presentTotal(receipt, columns)); return result.toString(); } - private String presentReceiptItem(ReceiptItem item) { + private String presentReceiptItem(ReceiptItem item, int columns) { String totalPricePresentation = presentPrice(item.getTotalPrice()); String name = item.getProduct().getName(); - String line = formatLineWithWhitespace(name, totalPricePresentation); + String line = formatLineWithWhitespace(name, totalPricePresentation, columns); if (item.getQuantity() != 1) { line += " " + presentPrice(item.getPrice()) + " * " + presentQuantity(item) + "\n"; @@ -44,23 +37,23 @@ private String presentReceiptItem(ReceiptItem item) { return line; } - private String presentDiscount(Discount discount) { + private String presentDiscount(Discount discount, int columns) { String name = discount.getDescription() + "(" + discount.getProduct().getName() + ")"; String value = presentPrice(discount.getDiscountAmount()); - return formatLineWithWhitespace(name, value); + return formatLineWithWhitespace(name, value, columns); } - private String presentTotal(Receipt receipt) { + private String presentTotal(Receipt receipt, int columns) { String name = "Total: "; String value = presentPrice(receipt.getTotalPrice()); - return formatLineWithWhitespace(name, value); + return formatLineWithWhitespace(name, value, columns); } - private String formatLineWithWhitespace(String name, String value) { + private String formatLineWithWhitespace(String name, String value, int columns) { StringBuilder line = new StringBuilder(); line.append(name); - int whitespaceSize = this.columns - name.length() - value.length(); + int whitespaceSize = columns - name.length() - value.length(); for (int i = 0; i < whitespaceSize; i++) { line.append(" "); } @@ -78,4 +71,4 @@ private static String presentQuantity(ReceiptItem item) { ? String.format("%d", (int)item.getQuantity()) : String.format(Locale.UK, "%.3f", item.getQuantity()); } -} +} \ No newline at end of file diff --git a/java/src/test/java/dojo/supermarket/ReceiptFormatterTest.java b/java/src/test/java/dojo/supermarket/ReceiptFormatterTest.java new file mode 100644 index 0000000..1d5266e --- /dev/null +++ b/java/src/test/java/dojo/supermarket/ReceiptFormatterTest.java @@ -0,0 +1,115 @@ +package dojo.supermarket; + +import dojo.supermarket.model.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ReceiptFormatterTest { + + private Receipt createTestReceipt() { + SupermarketCatalog catalog = new FakeCatalog(); + Product apples = new Product("apples", ProductUnit.KILO); + catalog.addProduct(apples, 1.99); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + + Teller teller = new Teller(catalog); + teller.addSpecialOffer(SpecialOfferType.TEN_PERCENT_DISCOUNT, toothbrush, 10.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(apples, 2.5); + cart.addItemQuantity(toothbrush, 1.0); + + return teller.checksOutArticlesFrom(cart); + } + + @Test + void textFormatter_shouldFormatReceiptCorrectly() { + Receipt receipt = createTestReceipt(); + TextReceiptFormatter formatter = new TextReceiptFormatter(); + + String result = formatter.formatReceipt(receipt, 40); + + assertTrue(result.contains("apples")); + assertTrue(result.contains("toothbrush")); + assertTrue(result.contains("10.0% off")); + assertTrue(result.contains("Total:")); + // Should be plain text format + assertFalse(result.contains("")); + assertFalse(result.contains("")); + } + + @Test + void htmlFormatter_shouldFormatReceiptCorrectly() { + Receipt receipt = createTestReceipt(); + HtmlReceiptFormatter formatter = new HtmlReceiptFormatter(); + + String result = formatter.formatReceipt(receipt, 40); + + // Should contain HTML structure + assertTrue(result.contains("")); + assertTrue(result.contains("")); + assertTrue(result.contains("
")); + assertTrue(result.contains("")); + + // Should contain receipt data + assertTrue(result.contains("apples")); + assertTrue(result.contains("toothbrush")); + assertTrue(result.contains("10.0% off")); + assertTrue(result.contains("Total:")); + + // Should escape HTML characters + assertTrue(result.contains("<") || !result.contains("