1
0

Initial Commit

This commit is contained in:
2025-07-16 18:06:02 +02:00
commit 2358585522
33 changed files with 1523 additions and 0 deletions

View File

@ -0,0 +1,87 @@
package com.qrouland.katatondeuse;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
public class MainIntegrationTest {
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;
private ByteArrayOutputStream outContent;
private ByteArrayOutputStream errContent;
@BeforeEach
public void setUpStreams() {
outContent = new ByteArrayOutputStream();
errContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
}
@AfterEach
public void restoreStreams() {
System.setOut(originalOut);
System.setErr(originalErr);
}
@Test
public void testMain_ValidInputFile() throws IOException {
Path tempFile = Files.createTempFile("test-input", ".txt");
Files.writeString(tempFile,
"5 5\n" +
"1 2 N\n" +
"GAGAGAGAA\n" +
"3 3 E\n" +
"AADAADADDA\n"
);
int code = Main.run(new String[]{tempFile.toString()});
String output = outContent.toString();
assertEquals(0, code);
assertTrue(output.contains("Pelouse 5 x 5"));
assertTrue(output.contains("Moved"));
Files.deleteIfExists(tempFile);
}
@Test
public void testMain_MissingFileArg() {
int code = Main.run(new String[]{});
assertEquals(1, code);
String errorOutput = errContent.toString();
assertTrue(errorOutput.contains("Error: Missing input file argument."));
assertTrue(errorOutput.contains("Usage: java Main <inputfile>"));
}
@Test
public void testMain_NonexistentFile() {
int code = Main.run(new String[]{"nonexistent-file.txt"});
assertEquals(1, code);
String errorOutput = errContent.toString();
assertTrue(errorOutput.contains("File does not exist"));
}
@Test
public void testMain_withMalformedInputFile() throws IOException {
Path tempFile = Files.createTempFile("malformed-input", ".txt");
Files.writeString(tempFile,
"invalid pelouse line\n"
);
int code = Main.run(new String[]{tempFile.toString()});
assertEquals(1, code);
String errorOutput = errContent.toString();
assertTrue(errorOutput.contains("Invalid Pelouse size format"));
Files.deleteIfExists(tempFile);
}
}

View File

@ -0,0 +1,34 @@
package com.qrouland.katatondeuse;
import com.qrouland.katatondeuse.controller.TontePelouseController;
import com.qrouland.katatondeuse.parser.FileInputParser;
import com.qrouland.katatondeuse.view.ConsolePelouseView;
import com.qrouland.katatondeuse.view.PelouseView;
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
int exitCode = run(args);
System.exit(exitCode);
}
public static int run(String[] args) {
PelouseView view = new ConsolePelouseView();
try {
File inputFile = FileInputParser.parseInputFile(args);
TontePelouseController controller = new TontePelouseController(inputFile, view);
controller.run();
} catch (IllegalArgumentException e) {
view.displayError("Error: " + e.getMessage());
view.displayError("Usage: java Main <inputfile>");
return 1;
} catch (IOException e) {
view.displayError("Error reading file: " + e.getMessage());
return 1;
}
return 0;
}
}

View File

@ -0,0 +1,72 @@
package com.qrouland.katatondeuse.controller;
import com.qrouland.katatondeuse.model.Direction;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
import com.qrouland.katatondeuse.parser.FileInputParser;
import com.qrouland.katatondeuse.view.PelouseView;
import org.apache.commons.lang3.tuple.ImmutablePair;
import java.io.*;
import java.util.List;
public class TontePelouseController {
private final File instructionsFile;
private final PelouseView pelouseView;
public TontePelouseController(File instructionsFile, PelouseView view) {
this.instructionsFile = instructionsFile;
this.pelouseView = view;
}
/**
* Starts the tonte (mowing) simulation by reading instructions from the input file.
*
* @throws IOException an I/O error occurs.
*/
public void run() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(instructionsFile))) {
Pelouse pelouse = initPelouse(reader);
executeInstructions(reader, pelouse);
}
}
/**
* Reads and executes the {@link Tondeuse} movement instructions from the reader.
*
* @param reader BufferedReader for reading lines from the instructions file.
* @param pelouse The {@link Pelouse} on which {@link Tondeuse} operate.
* @throws IOException an I/O error occurs.
*/
private void executeInstructions(BufferedReader reader, Pelouse pelouse) throws IOException {
String positionLine;
while ((positionLine = reader.readLine()) != null) {
String instructionsLine = reader.readLine();
ImmutablePair<Tondeuse, List<Direction>> instructions =
FileInputParser.parseTondeuseInstructions(positionLine, instructionsLine);
Tondeuse tondeuse = instructions.left;
List<Direction> directions = instructions.right;
pelouse.addTondeuse(tondeuse);
pelouseView.displayTondeuseAdd(tondeuse);
TonteTondeuseController controller = new TonteTondeuseController(tondeuse, pelouse, this.pelouseView);
controller.executeInstructions(directions);
pelouseView.displayPelouseState(pelouse);
}
}
/**
* Parses the first line of the input to initialize the .
*
* @param reader BufferedReader reading from the instructions file.
* @return The initialized {@link Pelouse}.
* @throws IOException an I/O error occurs.
*/
private Pelouse initPelouse(BufferedReader reader) throws IOException {
String firstLine = reader.readLine();
Pelouse pelouse = FileInputParser.parsePelouseDefinition(firstLine);
pelouseView.displayPelouseState(pelouse);
return pelouse;
}
}

View File

@ -0,0 +1,47 @@
package com.qrouland.katatondeuse.controller;
import com.qrouland.katatondeuse.controller.movement.MovementAhead;
import com.qrouland.katatondeuse.controller.movement.MovementLeft;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.controller.movement.MovementRight;
import com.qrouland.katatondeuse.model.*;
import com.qrouland.katatondeuse.view.PelouseView;
import java.util.List;
import java.util.Map;
/**
* Controls a {@link Tondeuse} on {@link Pelouse}.
*/
public class TonteTondeuseController {
private final Pelouse pelouse;
private final Tondeuse tondeuse;
private final PelouseView pelouseView;
private static final Map<Direction, MovementStrategy> MOVEMENTS = Map.of(
Direction.D, new MovementRight(),
Direction.G, new MovementLeft(),
Direction.A, new MovementAhead()
);
public TonteTondeuseController(Tondeuse tondeuse, Pelouse pelouse, PelouseView view) {
this.pelouse = pelouse;
this.tondeuse = tondeuse;
this.pelouseView = view;
}
/**
* Executes a list of instructions of movement directions.
*
* @param directions the list of movement directions (D, G, A).
*/
public void executeInstructions(List<Direction> directions) {
for (Direction direction : directions) {
Position initial_position = tondeuse.getPosition();
MovementStrategy movement = MOVEMENTS.get(direction);
movement.move(this.pelouse, this.tondeuse);
pelouseView.displayTondeuseMove(initial_position, tondeuse.getPosition(), movement);
}
}
}

View File

@ -0,0 +1,35 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class MovementAhead implements MovementStrategy {
/**
* Moves the {@link Tondeuse} one step ahead in the direction of its current orientation.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
@Override
public void move(Pelouse pelouse, Tondeuse tondeuse) {
Position current = tondeuse.getPosition();
int x = current.getX();
int y = current.getY();
Orientation orientation = current.getOrientation();
switch (orientation) {
case N -> y += 1;
case S -> y -= 1;
case E -> x += 1;
case W -> x -= 1;
}
if (x >= 0 && x <= pelouse.getMaxX() && y >= 0 && y <= pelouse.getMaxY()) {
tondeuse.setPosition(new Position(x, y, orientation));
} else {
System.err.println("Move ignored: out of grid bounds.");
}
}
}

View File

@ -0,0 +1,28 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class MovementLeft implements MovementStrategy {
/**
* Rotates the {@link Tondeuse} 90 degrees to the left.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
@Override
public void move(Pelouse pelouse, Tondeuse tondeuse) {
Position position = tondeuse.getPosition();
Orientation new_orientation = switch (position.getOrientation()) {
case N -> Orientation.W;
case W -> Orientation.S;
case S -> Orientation.E;
case E -> Orientation.N;
};
tondeuse.setPosition(new Position(position.getX(), position.getY(), new_orientation));
}
}

View File

@ -0,0 +1,26 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class MovementRight implements MovementStrategy {
/**
* Rotates the {@link Tondeuse} 90 degrees to the right.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
@Override
public void move(Pelouse pelouse, Tondeuse tondeuse) {
Position position = tondeuse.getPosition();
Orientation new_orientation = switch (position.getOrientation()) {
case N -> Orientation.E;
case E -> Orientation.S;
case S -> Orientation.W;
case W -> Orientation.N;
};
tondeuse.setPosition(new Position(position.getX(), position.getY(), new_orientation));
}
}

View File

@ -0,0 +1,14 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
public interface MovementStrategy {
/**
* Executes a movement of a {@link Tondeuse} on the {@link Pelouse}.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
void move(Pelouse pelouse, Tondeuse tondeuse);
}

View File

@ -0,0 +1,20 @@
package com.qrouland.katatondeuse.model;
/**
* Three directions for moving a {@link Tondeuse} using a {@link com.qrouland.katatondeuse.controller.movement.MovementStrategy} .
*/
public enum Direction {
D, // Droite (droite)
G, // Gauche (left)
A; // Avance (ahead)
public static Direction fromChar(char c) {
return switch (c) {
case 'A' -> A;
case 'G' -> G;
case 'D' -> D;
default -> throw new IllegalArgumentException("Invalid direction character: " + c);
};
}
}

View File

@ -0,0 +1,22 @@
package com.qrouland.katatondeuse.model;
/**
* Four cardinal directions for orientations of the {@link Tondeuse}.
*/
public enum Orientation {
N, // North
E, // East
W, // West
S; // South
public static Orientation fromChar(char c) {
return switch (c) {
case 'N' -> N;
case 'E' -> E;
case 'S' -> S;
case 'W' -> W;
default -> throw new IllegalArgumentException("Invalid orientation character: " + c);
};
}
}

View File

@ -0,0 +1,47 @@
package com.qrouland.katatondeuse.model;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a pelouse (lawn) on which {@link Tondeuse} (mowers) operate.
*/
public class Pelouse {
private final int maxX;
private final int maxY;
private final List<Tondeuse> tondeuses;
/**
* Constructs a Pelouse.
*
* @param maxX the max x coordinate.
* @param maxY the max x coordinate.
*/
public Pelouse(int maxX, int maxY) {
this.maxX = maxX;
this.maxY = maxY;
this.tondeuses = new ArrayList<>();
}
public int getMaxX() {
return maxX;
}
public int getMaxY() {
return maxY;
}
public List<Tondeuse> getTondeuses() {
return tondeuses;
}
public void addTondeuse(Tondeuse tondeuse) {
this.tondeuses.add(tondeuse);
}
@Override
public String toString() {
return String.format("Pelouse{maxX=%d, maxY=%d, tondeuses=%s}", maxX, maxY, tondeuses);
}
}

View File

@ -0,0 +1,41 @@
package com.qrouland.katatondeuse.model;
/**
* Represents the position of a {@link Tondeuse} on a 2D grid (x,y) and its
* orientation (N, E, S, W).
*/
public class Position {
private final int x;
private final int y;
private final Orientation orientation;
/**
* Constructs a Position.
*
* @param x the x coordinate.
* @param y the y coordinate.
* @param orientation the orientation (N, E, S, W).
*/
public Position(int x, int y, Orientation orientation) {
this.x = x;
this.y = y;
this.orientation = orientation;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Orientation getOrientation() {
return orientation;
}
@Override
public String toString() {
return String.format("%d %d %s", x, y, orientation.toString());
}
}

View File

@ -0,0 +1,30 @@
package com.qrouland.katatondeuse.model;
/**
* Represents a Tondeuse (mower) with a current {@link Position} on a 2D grid.
*/
public class Tondeuse {
private Position position;
/**
* Constructs a Tondeuse at the given position.
*
* @param position the initial position of the Tondeuse.
*/
public Tondeuse(Position position) {
this.position = position;
}
public Position getPosition() {
return position;
}
public void setPosition(Position position) {
this.position = position;
}
@Override
public String toString() {
return String.format("Tondeuse { position = %s }", position.toString());
}
}

View File

@ -0,0 +1,82 @@
package com.qrouland.katatondeuse.parser;
import com.qrouland.katatondeuse.model.*;
import org.apache.commons.lang3.tuple.ImmutablePair;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class FileInputParser {
/**
* Parse instructions for a {@link Tondeuse} initial position and its movement instructions.
*
* @param positionLine string representing the {@link Tondeuse} initial position.
* @param instructionsLine string representing movement instructions.
* @return An ImmutablePair of {@link Tondeuse} and {@link Direction}.
*/
public static ImmutablePair<Tondeuse, List<Direction>> parseTondeuseInstructions(
String positionLine, String instructionsLine) {
if (!positionLine.matches("^\\d+ \\d+ [NESW]$")) {
throw new IllegalArgumentException("Invalid tondeuse position format : " + positionLine);
}
if (!instructionsLine.matches("^[AGD]+$")) {
throw new IllegalArgumentException("Invalid tondeuse instruction format : " + instructionsLine);
}
String[] parts = positionLine.split(" ");
int x = Integer.parseInt(parts[0]);
int y = Integer.parseInt(parts[1]);
Orientation orientation = Orientation.fromChar(parts[2].charAt(0));
List<Direction> directions = new ArrayList<>();
for (char c : instructionsLine.toCharArray()) {
directions.add(Direction.fromChar(c));
}
Tondeuse tondeuse = new Tondeuse(new Position(x, y, orientation));
return new ImmutablePair<>(tondeuse, directions);
}
/**
* Parse a line to instantiate a {@link Pelouse}.
*
* @param line The line of the pelouse definition to parse.
* @return a Pelouse of the correct format.
*/
public static Pelouse parsePelouseDefinition(String line) {
String pattern = "^\\d+ \\d+$";
if (!line.matches(pattern)) {
throw new IllegalArgumentException("Invalid Pelouse size format: " + line);
}
String[] parts = line.split(" ");
int xMax = Integer.parseInt(parts[0]);
int yMax = Integer.parseInt(parts[1]);
return new Pelouse(xMax, yMax);
}
/**
* Parses the input arguments to retrieve the input file.
*
* @param args cli args.
* @return a File object representing the input file.
* @throws IllegalArgumentException if no arguments provided or the file does not exist.
*/
public static File parseInputFile(String[] args) {
if (args.length < 1) {
throw new IllegalArgumentException("Missing input file argument.");
}
File inputFile = new File(args[0]);
if (!inputFile.exists()) {
throw new IllegalArgumentException("File does not exist: " + args[0]);
}
return inputFile;
}
}

View File

@ -0,0 +1,58 @@
package com.qrouland.katatondeuse.view;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class ConsolePelouseView implements PelouseView {
/**
* Displays the current state of the {@link Pelouse}, including the positions of all {@link Tondeuse}.
*
* @param pelouse The {@link Pelouse} to be displayed.
*/
@Override
public void displayPelouseState(Pelouse pelouse) {
System.out.printf("Pelouse %d x %d : ", pelouse.getMaxX(), pelouse.getMaxY());
for (int i = 0; i < pelouse.getTondeuses().size(); i++) {
Tondeuse tondeuse = pelouse.getTondeuses().get(i);
System.out.print(tondeuse.getPosition());
if (i < pelouse.getTondeuses().size() - 1) {
System.out.print(" | ");
}
}
System.out.println();
}
/**
* Displays a message indicating a new {@link Tondeuse} has been added.
*
* @param tondeuse The {@link Tondeuse} that was added.
*/
@Override
public void displayTondeuseAdd(Tondeuse tondeuse) {
System.out.printf("Added : %s%n", tondeuse.getPosition());
}
/**
* Displays a message indicating a {@link Tondeuse} has moved from its origin to its destination.
*
* @param origin The original position of the {@link Tondeuse}.
* @param destination The new position of the {@link Tondeuse}.
* @param movement The {@link MovementStrategy} used for the movement.
*/
@Override
public void displayTondeuseMove(Position origin, Position destination, MovementStrategy movement) {
System.out.printf("Moved : %s to %s using %s%n", origin, destination, movement.getClass().getSimpleName());
}
/**
* Displays an error message.
*
* @param message The error message.
*/
@Override
public void displayError(String message) {
System.err.println(message);
}
}

View File

@ -0,0 +1,39 @@
package com.qrouland.katatondeuse.view;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public interface PelouseView {
/**
* Displays the current state of the {@link Pelouse}, including the positions of all {@link Tondeuse}.
*
* @param pelouse The {@link Pelouse} to be displayed.
*/
void displayPelouseState(Pelouse pelouse);
/**
* Displays a message indicating a new {@link Tondeuse} has been added.
*
* @param tondeuse The {@link Tondeuse} that was added.
*/
void displayTondeuseAdd(Tondeuse tondeuse);
/**
* Displays a message indicating a {@link Tondeuse} has moved from its origin {@link Position} to its destination {@link Position}.
*
* @param origin The original {@link Position} of the {@link Tondeuse}.
* @param destination The new {@link Position} of the {@link Tondeuse}.
* @param movement The {@link MovementStrategy} used for the movement.
*/
void displayTondeuseMove(Position origin, Position destination, MovementStrategy movement);
/**
* Displays an error message.
*
* @param message The error message.
*/
void displayError(String message);
}

View File

@ -0,0 +1,62 @@
package com.qrouland.katatondeuse.controller;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
import com.qrouland.katatondeuse.view.PelouseView;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.io.*;
import java.util.List;
public class TontePelouseControllerTest {
private PelouseView pelouseViewMock;
@BeforeEach
public void setup() {
pelouseViewMock = mock(PelouseView.class);
}
@Test
public void testRun() throws IOException {
String fileContent = """
5 5
1 2 N
GAGAGAGAA
3 3 E
AADAADADDA
""";
File tempFile = File.createTempFile("testInstructions", ".txt");
tempFile.deleteOnExit();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
writer.write(fileContent);
}
TontePelouseController controller = new TontePelouseController(tempFile, pelouseViewMock);
controller.run();
verify(pelouseViewMock, atLeastOnce()).displayPelouseState(any(Pelouse.class));
verify(pelouseViewMock, atLeastOnce()).displayTondeuseAdd(any(Tondeuse.class));
ArgumentCaptor<Tondeuse> tondeuseCaptor = ArgumentCaptor.forClass(Tondeuse.class);
verify(pelouseViewMock, atLeastOnce()).displayTondeuseAdd(tondeuseCaptor.capture());
List<Tondeuse> addedTondeuses = tondeuseCaptor.getAllValues();
assertEquals(2, addedTondeuses.size());
assertEquals(1, addedTondeuses.get(0).getPosition().getX());
assertEquals(3, addedTondeuses.get(0).getPosition().getY());
assertEquals(com.qrouland.katatondeuse.model.Orientation.N, addedTondeuses.get(0).getPosition().getOrientation());
assertEquals(5, addedTondeuses.get(1).getPosition().getX());
assertEquals(1, addedTondeuses.get(1).getPosition().getY());
assertEquals(com.qrouland.katatondeuse.model.Orientation.E, addedTondeuses.get(1).getPosition().getOrientation());
}
}

View File

@ -0,0 +1,41 @@
package com.qrouland.katatondeuse.controller;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.model.*;
import com.qrouland.katatondeuse.view.PelouseView;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
public class TonteTondeuseControllerTest {
private Pelouse pelouse;
private Tondeuse tondeuse;
private PelouseView pelouseView;
private TonteTondeuseController controller;
@BeforeEach
public void setup() {
pelouse = new Pelouse(5, 5);
Position initialPosition = new Position(1, 2, Orientation.N);
tondeuse = new Tondeuse(initialPosition);
pelouseView = mock(PelouseView.class);
controller = new TonteTondeuseController(tondeuse, pelouse, pelouseView);
}
@Test
public void testExecuteInstructions() {
List<Direction> instructions = List.of(Direction.A, Direction.D, Direction.G);
controller.executeInstructions(instructions);
verify(pelouseView, times(3))
.displayTondeuseMove(any(Position.class), any(Position.class), any(MovementStrategy.class));
Position finalPosition = tondeuse.getPosition();
assertEquals("1 3 N", finalPosition.toString());
}
}

View File

@ -0,0 +1,103 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MovementAheadTest {
private MovementAhead movementAhead;
private Pelouse pelouse;
@BeforeEach
void setUp() {
movementAhead = new MovementAhead();
pelouse = new Pelouse(5, 5);
}
@Test
void moveNorth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.N));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
@Test
void moveSouth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.S));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void moveEast() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.E));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(3, pos.getX());
assertEquals(2, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void moveWest() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.W));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(2, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
@Test
void move_OutOfBounds_MaxY() {
Tondeuse tondeuse = new Tondeuse(new Position(3, pelouse.getMaxY(), Orientation.N));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(3, pos.getX());
assertEquals(pelouse.getMaxY(), pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
@Test
void move_OutOfBounds_MaxX() {
Tondeuse tondeuse = new Tondeuse(new Position(pelouse.getMaxX(), 3, Orientation.E));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(pelouse.getMaxX(), pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void move_OutOfBounds_MinY() {
Tondeuse tondeuse = new Tondeuse(new Position(3, 0, Orientation.S));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(3, pos.getX());
assertEquals(0, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void mov_OutOfBounds_MinX() {
Tondeuse tondeuse = new Tondeuse(new Position(0, 3, Orientation.W));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(0, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
}

View File

@ -0,0 +1,62 @@
package com.qrouland.katatondeuse.controller.movement;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class MovementLeftTest {
private MovementLeft movementLeft;
private Pelouse pelouse;
@BeforeEach
void setUp() {
movementLeft = new MovementLeft();
pelouse = new Pelouse(5, 5);
}
@Test
void testTurnLeftFromNorth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.N));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
@Test
void testTurnLeftFromWest() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.W));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void testTurnLeftFromSouth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.S));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void testTurnLeftFromEast() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.E));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
}

View File

@ -0,0 +1,62 @@
package com.qrouland.katatondeuse.controller.movement;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class MovementRightTest {
private MovementRight movementRight;
private Pelouse pelouse;
@BeforeEach
void setUp() {
movementRight = new MovementRight();
pelouse = new Pelouse(5, 5);
}
@Test
void testTurnRightFromNorth() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.N));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void testTurnRightFromEast() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.E));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void testTurnRightFromSouth() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.S));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
@Test
void testTurnRightFromWest() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.W));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
}

View File

@ -0,0 +1,28 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class DirectionTest {
@Test
void testFromChar() {
assertEquals(Direction.A, Direction.fromChar('A'));
assertEquals(Direction.G, Direction.fromChar('G'));
assertEquals(Direction.D, Direction.fromChar('D'));
}
@Test
void testFromChar_invalidCharacter() {
Exception exception = assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('X'));
assertEquals("Invalid direction character: X", exception.getMessage());
}
@Test
void testFromChar_lowercaseInput() {
assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('a'));
assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('g'));
assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('d'));
}
}

View File

@ -0,0 +1,27 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class OrientationTest {
@Test
void testFromChar() {
assertEquals(Orientation.N, Orientation.fromChar('N'));
assertEquals(Orientation.E, Orientation.fromChar('E'));
assertEquals(Orientation.S, Orientation.fromChar('S'));
assertEquals(Orientation.W, Orientation.fromChar('W'));
}
@Test
void testFromChar_invalidCharacter() {
Exception exception = assertThrows(IllegalArgumentException.class, () -> Orientation.fromChar('X'));
assertEquals("Invalid orientation character: X", exception.getMessage());
}
@Test
void testFromChar_lowercaseInput() {
assertThrows(IllegalArgumentException.class, () -> Orientation.fromChar('n'));
}
}

View File

@ -0,0 +1,60 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class PelouseTest {
private Pelouse pelouse;
@BeforeEach
void setUp() {
pelouse = new Pelouse(5, 5);
}
@Test
void testAddTondeuseAndGetTondeuses() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 2, Orientation.N));
pelouse.addTondeuse(tondeuse);
List<Tondeuse> tondeuses = pelouse.getTondeuses();
assertEquals(1, tondeuses.size());
assertSame(tondeuse, tondeuses.get(0));
}
@Test
void testGetTondeuses() {
assertTrue(pelouse.getTondeuses().isEmpty());
}
@Test
void testGetMaxX_returnsCorrectValue() {
Pelouse pelouse = new Pelouse(10, 7);
assertEquals(10, pelouse.getMaxX());
}
@Test
void testGetMaxY_returnsCorrectValue() {
Pelouse pelouse = new Pelouse(10, 7);
assertEquals(7, pelouse.getMaxY());
}
@Test
void testToString() {
Tondeuse tondeuse = new Tondeuse(new Position(3, 4, Orientation.E));
pelouse.addTondeuse(tondeuse);
String result = pelouse.toString();
assertTrue(result.contains("maxX=5"));
assertTrue(result.contains("maxY=5"));
assertTrue(result.contains("Tondeuse { position ="));
}
}

View File

@ -0,0 +1,14 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class PositionTest {
@Test
void testPositionToString() {
Position position = new Position(0, 0, Orientation.S);
String expected = "0 0 S";
assertEquals(expected, position.toString());
}
}

View File

@ -0,0 +1,41 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TondeuseTest {
@Test
void testPositionGetter() {
Position position = new Position(1, 2, Orientation.N);
Tondeuse tondeuse = new Tondeuse(position);
assertEquals(position, tondeuse.getPosition());
}
@Test
void testSetPosition() {
Position initialPosition = new Position(1, 2, Orientation.N);
Tondeuse tondeuse = new Tondeuse(initialPosition);
Position newPosition = new Position(3, 4, Orientation.E);
tondeuse.setPosition(newPosition);
assertEquals(newPosition, tondeuse.getPosition());
assertEquals(3, tondeuse.getPosition().getX());
assertEquals(4, tondeuse.getPosition().getY());
assertEquals(Orientation.E, tondeuse.getPosition().getOrientation());
}
@Test
void testTondeuseToString() {
Position position = new Position(3, 4, Orientation.E);
Tondeuse tondeuse = new Tondeuse(position);
String expected = "Tondeuse { position = 3 4 E }";
assertEquals(expected, tondeuse.toString());
}
}

View File

@ -0,0 +1,107 @@
package com.qrouland.katatondeuse;
import com.qrouland.katatondeuse.model.Direction;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
import com.qrouland.katatondeuse.parser.FileInputParser;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class FileInputParserTest {
@Test
void testParseTondeuseInstructions_validInput() {
String position = "1 2 N";
String instructions = "AGAD";
ImmutablePair<Tondeuse, List<Direction>> result = FileInputParser.parseTondeuseInstructions(position, instructions);
assertNotNull(result);
assertEquals(1, result.left.getPosition().getX());
assertEquals(2, result.left.getPosition().getY());
assertEquals(Orientation.N, result.left.getPosition().getOrientation());
assertEquals(List.of(Direction.A, Direction.G, Direction.A, Direction.D), result.right);
}
@Test
void testParseTondeuseInstructions_invalidPositionFormat() {
String invalidPosition = "1 N 2";
String instructions = "AGAD";
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
FileInputParser.parseTondeuseInstructions(invalidPosition, instructions));
assertEquals("Invalid tondeuse position format : " + invalidPosition, exception.getMessage());
}
@Test
void testParseTondeuseInstructions_invalidInstructionsFormat() {
String position = "1 2 N";
String invalidInstructions = "AGZRA";
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
FileInputParser.parseTondeuseInstructions(position, invalidInstructions));
assertEquals("Invalid tondeuse instruction format : " + invalidInstructions, exception.getMessage());
}
@Test
void testParsePelouseDefinition_ValidInput() {
Pelouse pelouse = FileInputParser.parsePelouseDefinition("5 5");
assertNotNull(pelouse);
assertEquals(5, pelouse.getMaxX());
assertEquals(5, pelouse.getMaxY());
}
@Test
void testParsePelouseDefinition_InvalidInput() {
String invalidLine = "5x5";
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
FileInputParser.parsePelouseDefinition(invalidLine));
assertTrue(exception.getMessage().contains("Invalid Pelouse size format"));
}
@Test
void testInputParserFile_ValidInputFile() throws IOException {
File tempFile = File.createTempFile("test", ".txt");
tempFile.deleteOnExit();
String[] args = { tempFile.getAbsolutePath() };
File result = FileInputParser.parseInputFile(args);
assertNotNull(result);
assertTrue(result.exists());
assertEquals(tempFile.getAbsolutePath(), result.getAbsolutePath());
}
@Test
void testFileInputParser_MissingArgument() {
String[] args = {};
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
FileInputParser.parseInputFile(args);
});
assertEquals("Missing input file argument.", exception.getMessage());
}
@Test
void testFileInputParser_NonExistentFile() {
String[] args = { "nonexistentfile.txt" };
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
FileInputParser.parseInputFile(args);
});
assertEquals("File does not exist: nonexistentfile.txt", exception.getMessage());
}
}

View File

@ -0,0 +1,81 @@
package com.qrouland.katatondeuse.view;
import com.qrouland.katatondeuse.controller.movement.MovementAhead;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.*;
class ConsolePelouseViewTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;
private final ConsolePelouseView view = new ConsolePelouseView();
@BeforeEach
void setUpStreams() {
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
}
@AfterEach
void restoreStreams() {
System.setOut(originalOut);
System.setErr(originalErr);
}
@Test
void testDisplayPelouseState() {
Pelouse pelouse = new Pelouse(3, 3);
pelouse.addTondeuse(new Tondeuse(new Position(0, 0, Orientation.S)));
pelouse.addTondeuse(new Tondeuse(new Position(2, 2, Orientation.E)));
view.displayPelouseState(pelouse);
String output = outContent.toString().trim();
assertTrue(output.contains("Pelouse 3 x 3"));
assertTrue(output.contains("0 0 S | 2 2 E"));
}
@Test
void testDisplayTondeuseAdd() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.E));
view.displayTondeuseAdd(tondeuse);
String output = outContent.toString().trim();
assertTrue(output.contains("Added"));
assertTrue(output.contains("2 3 E"));
}
@Test
void testDisplayTondeuseMove() {
Position origin = new Position(1, 1, Orientation.N);
Position destination = new Position(1, 2, Orientation.N);
MovementAhead movement = new MovementAhead();
view.displayTondeuseMove(origin, destination, movement);
String output = outContent.toString().trim();
assertTrue(output.contains("Moved"));
assertTrue(output.contains("1 1 N"));
assertTrue(output.contains("1 2 N"));
assertTrue(output.contains("MovementAhead"));
}
@Test
void testDisplayError() {
view.displayError("Test error message");
String output = errContent.toString().trim();
assertEquals("Test error message", output);
}
}