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("