Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

final class PaintCalculator {

private static final String SHAPE_MENU = "Select room shape: 1) Rectangular 2) Round";

private static final String LENGTH_PROMPT = "What is the length of the room in feet? ";

private static final String WIDTH_PROMPT = "What is the width of the room in feet? ";

private static final String RADIUS_PROMPT = "What is the radius of the room in feet? ";

private final LineReader lineReader;

private final LineWriter lineWriter;
Expand All @@ -17,13 +25,30 @@ final class PaintCalculator {
}

void calculatePaint() {
RoomDimension length = readDimension("What is the length of the room in feet? ");
RoomDimension width = readDimension("What is the width of the room in feet? ");
this.lineWriter.writeLine(SHAPE_MENU);
String shapeChoice = this.lineReader.readLine();

RoomDimensions dimensions = switch (shapeChoice != null ? shapeChoice.trim() : null) {
case "1" -> readRectangularDimensions();
case "2" -> readRoundDimensions();
case null, default -> throw new IllegalArgumentException("Please enter 1 or 2");
};

PaintEstimate estimate = PaintEstimator.estimate(new RoomDimensions(length, width));
PaintEstimate estimate = PaintEstimator.estimate(dimensions);

this.lineWriter.writeLine("You will need to purchase %s %s of paint to cover %s square feet.".formatted(
estimate.gallons(), estimate.gallonWord(), estimate.area()));
}

private RectangularRoom readRectangularDimensions() {
RoomDimension length = readDimension(LENGTH_PROMPT);
RoomDimension width = readDimension(WIDTH_PROMPT);
return new RectangularRoom(length, width);
}

this.lineWriter.writeLine("You will need to purchase %s %s of paint to cover %s square feet."
.formatted(estimate.gallons(), estimate.gallonWord(), estimate.area()));
private RoundRoom readRoundDimensions() {
RoomDimension radius = readDimension(RADIUS_PROMPT);
return new RoundRoom(radius);
}

private RoomDimension readDimension(String prompt) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.delivercraft.paint;

import java.math.BigDecimal;
import java.util.Objects;

public record RectangularRoom(RoomDimension length, RoomDimension width) implements RoomDimensions {

public RectangularRoom(RoomDimension length, RoomDimension width) {
this.length = Objects.requireNonNull(length, "length must not be null");
this.width = Objects.requireNonNull(width, "width must not be null");
}

@Override
public BigDecimal area() {
return this.length.value().multiply(this.width.value());
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
package dev.delivercraft.paint;

import java.math.BigDecimal;
import java.util.Objects;

record RoomDimensions(RoomDimension length, RoomDimension width) {
sealed interface RoomDimensions permits RectangularRoom, RoundRoom {

RoomDimensions {
Objects.requireNonNull(length, "length must not be null");
Objects.requireNonNull(width, "width must not be null");
}

BigDecimal area() {
return this.length.value().multiply(this.width.value());
}
BigDecimal area();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.delivercraft.paint;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;

public record RoundRoom(RoomDimension radius) implements RoomDimensions {

public RoundRoom(RoomDimension radius) {
this.radius = Objects.requireNonNull(radius, "radius must not be null");
}

@Override
public BigDecimal area() {
return this.radius.value()
.multiply(this.radius.value())
.multiply(BigDecimal.valueOf(Math.PI))
.setScale(2, RoundingMode.HALF_UP);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class MainTest {
void main_GivenValidInput_ShouldPrintPaintEstimate() throws IOException {
InputStream originalInput = System.in;
PrintStream originalOutput = System.out;
String inputText = "18%n20%n".formatted();
String inputText = "1%n18%n20%n".formatted();
ByteArrayInputStream input = new ByteArrayInputStream(inputText.getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream output = new ByteArrayOutputStream();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package dev.delivercraft.paint;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

import java.util.stream.Stream;

import dev.delivercraft.io.CapturingLineWriter;
import dev.delivercraft.io.LineWriter;
import dev.delivercraft.io.StubLineReader;
Expand All @@ -14,6 +9,11 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

class PaintCalculatorTest {

private static final String VALID_LENGTH = "18";
Expand All @@ -36,59 +36,82 @@ class PaintCalculatorTest {

private static final String WIDTH_PROMPT = "What is the width of the room in feet? ";

private static final String SHAPE_MENU = "Select room shape: 1) Rectangular 2) Round";

private static final String RADIUS_PROMPT = "What is the radius of the room in feet? ";

private static final String RECTANGULAR_SHAPE = "1";

private static final String TEN_FEET = "10";

private static final String INVALID_LENGTH_OUTPUT =
SHAPE_MENU + System.lineSeparator() + LENGTH_PROMPT;

private static final String INVALID_WIDTH_OUTPUT =
SHAPE_MENU + System.lineSeparator() + LENGTH_PROMPT + WIDTH_PROMPT;

@Test
void calculatePaint_GivenExampleDimensions_ShouldDisplayGallonsAndArea() {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader(VALID_LENGTH, VALID_WIDTH), lineWriter);
PaintCalculator calculator = new PaintCalculator(
new StubLineReader(RECTANGULAR_SHAPE, VALID_LENGTH, VALID_WIDTH), lineWriter);

calculator.calculatePaint();

assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
+ LENGTH_PROMPT + WIDTH_PROMPT
+ "You will need to purchase 2 gallons of paint to cover 360 square feet."
+ System.lineSeparator());
}

@Test
void calculatePaint_GivenSmallCeiling_ShouldUseSingularGallon() {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader("10", "10"), lineWriter);
PaintCalculator calculator = new PaintCalculator(
new StubLineReader(RECTANGULAR_SHAPE, TEN_FEET, TEN_FEET), lineWriter);

calculator.calculatePaint();

assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
+ LENGTH_PROMPT + WIDTH_PROMPT
+ "You will need to purchase 1 gallon of paint to cover 100 square feet."
+ System.lineSeparator());
}

@Test
void calculatePaint_GivenFractionalDimensions_ShouldDisplayExactDecimalArea() {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader("10.5", "10.1"), lineWriter);
PaintCalculator calculator = new PaintCalculator(
new StubLineReader(RECTANGULAR_SHAPE, "10.5", "10.1"), lineWriter);

calculator.calculatePaint();

assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
+ LENGTH_PROMPT + WIDTH_PROMPT
+ "You will need to purchase 1 gallon of paint to cover 106.05 square feet."
+ System.lineSeparator());
}

@Test
void calculatePaint_GivenAreaJustOverOneGallon_ShouldRoundGallonsUp() {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader("35.1", "10"), lineWriter);
PaintCalculator calculator = new PaintCalculator(
new StubLineReader(RECTANGULAR_SHAPE, "35.1", "10"), lineWriter);

calculator.calculatePaint();

assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
+ LENGTH_PROMPT + WIDTH_PROMPT
+ "You will need to purchase 2 gallons of paint to cover 351 square feet."
+ System.lineSeparator());
}

@ParameterizedTest
@MethodSource("invalidInputFlows")
void calculatePaint_GivenInvalidInput_ShouldFailFast(String length, String width, String expectedOutput) {
void calculatePaint_GivenInvalidInput_ShouldFailFast(String shape, String length, String width,
String expectedOutput) {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader(length, width), lineWriter);
PaintCalculator calculator = new PaintCalculator(new StubLineReader(shape, length, width), lineWriter);

assertThatIllegalArgumentException()
.isThrownBy(calculator::calculatePaint);
Expand All @@ -98,30 +121,67 @@ void calculatePaint_GivenInvalidInput_ShouldFailFast(String length, String width

private static Stream<Arguments> invalidInputFlows() {
return Stream.of(
Arguments.of(EMPTY_INPUT, VALID_WIDTH, LENGTH_PROMPT),
Arguments.of(null, VALID_WIDTH, LENGTH_PROMPT),
Arguments.of(NON_NUMERIC_INPUT, VALID_WIDTH, LENGTH_PROMPT),
Arguments.of(ZERO_INPUT, VALID_WIDTH, LENGTH_PROMPT),
Arguments.of(NEGATIVE_INPUT, VALID_WIDTH, LENGTH_PROMPT),
Arguments.of(SCIENTIFIC_INPUT, VALID_WIDTH, LENGTH_PROMPT),
Arguments.of(SHORT_DECIMAL, VALID_WIDTH, LENGTH_PROMPT),
Arguments.of(VALID_LENGTH, EMPTY_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
Arguments.of(VALID_LENGTH, null, LENGTH_PROMPT + WIDTH_PROMPT),
Arguments.of(VALID_LENGTH, NON_NUMERIC_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
Arguments.of(VALID_LENGTH, ZERO_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
Arguments.of(VALID_LENGTH, NEGATIVE_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
Arguments.of(VALID_LENGTH, SCIENTIFIC_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
Arguments.of(VALID_LENGTH, SHORT_DECIMAL, LENGTH_PROMPT + WIDTH_PROMPT));
Arguments.of(RECTANGULAR_SHAPE, EMPTY_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, null, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, NON_NUMERIC_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, ZERO_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, NEGATIVE_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, SCIENTIFIC_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, SHORT_DECIMAL, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, EMPTY_INPUT, INVALID_WIDTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, null, INVALID_WIDTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, NON_NUMERIC_INPUT, INVALID_WIDTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, ZERO_INPUT, INVALID_WIDTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, NEGATIVE_INPUT, INVALID_WIDTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, SCIENTIFIC_INPUT, INVALID_WIDTH_OUTPUT),
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, SHORT_DECIMAL, INVALID_WIDTH_OUTPUT));
}

@ParameterizedTest
@ValueSource(strings = {"18", " 18 ", "18.0", "0.5"})
void calculatePaint_GivenPlainDecimalInput_ShouldAcceptInput(String input) {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader(input, "20"), lineWriter);
PaintCalculator calculator = new PaintCalculator(
new StubLineReader(RECTANGULAR_SHAPE, input, "20"), lineWriter);

calculator.calculatePaint();

assertThat(lineWriter.toString()).contains("You will need to purchase");
}

@Test
void calculatePaint_GivenRoundRoomSelection_ShouldCalculateCorrectArea() {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader("2", "10"), lineWriter);

calculator.calculatePaint();

assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
+ RADIUS_PROMPT
+ "You will need to purchase 1 gallon of paint to cover 314.16 square feet."
+ System.lineSeparator());
}

@ParameterizedTest
@ValueSource(strings = {"3", "0", "abc", "", " "})
void calculatePaint_GivenInvalidShapeInput_ShouldThrowIllegalArgumentException(String invalidInput) {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(new StubLineReader(invalidInput), lineWriter);

assertThatIllegalArgumentException()
.isThrownBy(calculator::calculatePaint)
.withMessage("Please enter 1 or 2");
assertThat(lineWriter.toString()).isEqualTo(SHAPE_MENU + System.lineSeparator());
}

@Test
void calculatePaint_GivenNullShapeInput_ShouldThrowIllegalArgumentException() {
LineWriter lineWriter = new CapturingLineWriter();
PaintCalculator calculator = new PaintCalculator(() -> null, lineWriter);

assertThatIllegalArgumentException()
.isThrownBy(calculator::calculatePaint)
.withMessage("Please enter 1 or 2");
assertThat(lineWriter.toString()).isEqualTo(SHAPE_MENU + System.lineSeparator());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class PaintEstimatorTest {

@Test
void estimate_GivenExampleDimensions_ShouldCalculateAreaAndGallons() {
RoomDimensions dimensions = new RoomDimensions(
RoomDimensions dimensions = new RectangularRoom(
RoomDimension.of("18"),
RoomDimension.of("20"));

Expand All @@ -22,7 +22,7 @@ void estimate_GivenExampleDimensions_ShouldCalculateAreaAndGallons() {

@Test
void estimate_GivenAreaJustOverOneGallon_ShouldRoundGallonsUp() {
RoomDimensions dimensions = new RoomDimensions(
RoomDimensions dimensions = new RectangularRoom(
RoomDimension.of("35.1"),
RoomDimension.of("10"));

Expand Down
Loading