diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..4ffa3c98
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,28 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "java",
+ "name": "Current File",
+ "request": "launch",
+ "mainClass": "${file}"
+ },
+ {
+ "type": "java",
+ "name": "DinosaurApp",
+ "request": "launch",
+ "mainClass": "com.dinosaur.dinosaurexploder/com.dinosaur.dinosaurexploder.DinosaurApp",
+ "projectName": "dinosaur-exploder"
+ },
+ {
+ "type": "java",
+ "name": "DinosaurWebApp",
+ "request": "launch",
+ "mainClass": "com.dinosaur.dinosaurexploder/com.dinosaur.dinosaurexploder.DinosaurWebApp",
+ "projectName": "dinosaur-exploder"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 52f3a3a8..4197cd07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
5.13.4
com.dinosaur.dinosaurexploder.DinosaurApp
com.dinosaur.dinosaurexploder.DinosaurWebApp
+ 0.8.10
@@ -74,6 +75,48 @@
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+ check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.00
+
+
+
+
+
+
+
+
maven-compiler-plugin
${maven.compiler.version}
diff --git a/src/main/java/com/dinosaur/dinosaurexploder/utils/WeaponUnlockChecker.java b/src/main/java/com/dinosaur/dinosaurexploder/utils/WeaponUnlockChecker.java
index 59652328..028891cc 100644
--- a/src/main/java/com/dinosaur/dinosaurexploder/utils/WeaponUnlockChecker.java
+++ b/src/main/java/com/dinosaur/dinosaurexploder/utils/WeaponUnlockChecker.java
@@ -4,9 +4,7 @@
import com.dinosaur.dinosaurexploder.model.HighScore;
import com.dinosaur.dinosaurexploder.model.TotalCoins;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
+
import java.util.Map;
public class WeaponUnlockChecker {
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index c84b3cce..076bf810 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -13,6 +13,7 @@
requires annotations;
requires javafx.base;
requires com.almasb.fxgl.entity;
+ requires com.almasb.fxgl.core;
opens assets.textures;
opens assets.sounds;
diff --git a/src/test/java/com/dinosaur/dinosaurexploder/featureTest/EnemiesTest.java b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/EnemiesTest.java
new file mode 100644
index 00000000..8c7fa172
--- /dev/null
+++ b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/EnemiesTest.java
@@ -0,0 +1,432 @@
+package com.dinosaur.dinosaurexploder.featureTest;
+
+import org.junit.jupiter.api.AfterEach;
+
+//package com.dinosaur.dinosaurexploder.model;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.MockitoAnnotations;
+
+import com.almasb.fxgl.core.Inject;
+import com.almasb.fxgl.dsl.FXGL;
+import com.almasb.fxgl.entity.Entity;
+import com.almasb.fxgl.physics.CollisionHandler;
+import com.almasb.fxgl.time.LocalTimer;
+import com.dinosaur.dinosaurexploder.components.GreenDinoComponent;
+import com.dinosaur.dinosaurexploder.components.OrangeDinoComponent;
+import com.dinosaur.dinosaurexploder.components.PlayerComponent;
+import com.dinosaur.dinosaurexploder.components.RedDinoComponent;
+
+//import org.mockito.Mockito;
+//import static org.mockito.Mockito.*;
+// import org.mockito.Mock;
+// import org.mockito.MockitoAnnotations;
+// import org.mockito.InjectMocks;
+
+import com.dinosaur.dinosaurexploder.components.ScoreComponent;
+import com.dinosaur.dinosaurexploder.utils.AudioManager;
+import com.dinosaur.dinosaurexploder.utils.GameTimer;
+import com.dinosaur.dinosaurexploder.utils.LevelManager;
+import com.dinosaur.dinosaurexploder.view.DinosaurGUI;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalTime;
+//import java.util.logging.LevelManager;
+
+public class EnemiesTest {
+
+public EnemiesTest() {
+ // AudioManager audioManager = new AudioManager();
+
+}
+ // LevelManager levelManager = new LevelManager();
+ //RedDinoComponent redDinoComponent = new RedDinoComponent(null);
+ //GreenDinoComponent greenDinoComponent = new GreenDinoComponent();
+ // OrangeDinoComponent orangeDinoComponent = new OrangeDinoComponent(null, null);
+
+ // @Mock
+ // private GameTimer mockGameTimer;
+
+ // @InjectMocks
+ // private RedDinoComponent redDinoComponent;
+ // private GreenDinoComponent greenDinoComponent;
+ // private OrangeDinoComponent orangeDinoComponent;
+
+ //private CollisionHandler collisionHandler;
+
+
+
+ @Nested
+ class GreenDinoTests{
+
+ private MockedStatic fxglMock;
+ private LocalTimer mockTimer;
+ private GreenDinoComponent greenD;
+
+
+
+ @BeforeEach
+ void setUp(){
+
+ // 1) Skapa mockad timer
+ mockTimer = org.mockito.Mockito.mock(LocalTimer.class);
+
+ // 2) Öppna static-mock för FXGL
+ fxglMock = org.mockito.Mockito.mockStatic(FXGL.class);
+
+ // 3) Stubba newLocalTimer() → mockTimer
+ fxglMock.when(FXGL::newLocalTimer).thenReturn(mockTimer);
+
+ greenD = new GreenDinoComponent();
+ // MockitoAnnotations.openMocks(this);
+ // mockGameTimer = new GameTimer();
+
+ }
+ @AfterEach
+ void tearDown() {
+ fxglMock.close();
+ }
+
+
+ @Test
+ @DisplayName("Should decrease amount of lives : amountOflives=0")
+ void shouldDecreaseAmountOfLivesWith1_amountOfLivesShouldBe0(){
+
+ //arange
+ int startAmountOfLives = greenD.getLives(); //1
+ //act
+ greenD.damage(1);
+
+ //assert
+ assertEquals(startAmountOfLives -1, greenD.getLives());
+ }
+ @Test
+ @DisplayName("Should pause : ")
+ void shouldPause_(){
+
+ //arange
+ boolean isPaused = true;
+ //act
+ greenD.setPaused(isPaused);
+
+ //assert
+ assertTrue(isPaused);
+ }
+ @Test
+ @DisplayName("Get the current enemy speed from the levelManager")
+ void shouldReturnEnemySpeed_shouldBeTrue(){
+
+ LevelManager levelManager = new LevelManager();
+ Double enemySpeed = levelManager.getEnemySpeed();
+
+ assertEquals(1.5, enemySpeed);
+ }
+
+
+
+
+
+
+}
+
+
+
+
+ @Nested
+ class RedDinoTests{
+
+ private MockedStatic fxglMock;
+ private LocalTimer mockTimer;
+ private RedDinoComponent redD;
+ private GameTimer mockGameTimer;
+
+ @BeforeEach
+ void setUp(){
+
+ mockTimer = org.mockito.Mockito.mock(LocalTimer.class);
+ fxglMock = org.mockito.Mockito.mockStatic(FXGL.class);
+
+ fxglMock.when(FXGL::newLocalTimer).thenReturn(mockTimer);
+ mockGameTimer = org.mockito.Mockito.mock(GameTimer.class);
+
+ redD = new RedDinoComponent(mockGameTimer);
+
+ // MockitoAnnotations.openMocks(this);
+ // mockGameTimer = new GameTimer();
+ }
+ @AfterEach
+ void tearDown() {
+ fxglMock.close();
+ }
+
+ @Test
+ @DisplayName("Should decrease amount of lives : amountOflives=0")
+ void shouldDecreaseAmountOfLivesWith1_amountOfLivesShouldBe9(){
+
+ //arange
+ int startAmountOfLives = redD.getLives(); //10
+ //act
+ redD.damage(1);
+
+ //asser
+ assertEquals(startAmountOfLives -1, redD.getLives());
+ }
+ @Test
+ @DisplayName("Should set pause to true")
+ void shouldPause_(){
+
+ //arange
+ boolean isPaused = true;
+
+ //act
+ redD.setPaused(isPaused);
+
+ //assert
+ assertTrue(isPaused);
+ }
+ @Test
+ @DisplayName("Should return horizontalSpeed (1,5)")
+ void shouldSetHorizontalSpeed(){
+
+ //Arrange
+ Double horizontalSpeed = 10.00;
+
+ //Act
+ redD.setHorizontalSpeed(horizontalSpeed);
+
+ //Assert
+ assertEquals(horizontalSpeed, redD.getHorizontalSpeed());
+ }
+ @Test
+ @DisplayName("Should return horizontalSpeed (1,5)")
+ void shouldReturnHorizontalSpeed(){
+
+ //Act and Assert
+ assertEquals(1.5, redD.getHorizontalSpeed());
+ }
+
+
+
+
+}
+
+
+
+
+
+
+ @Nested
+ class OrangeDino{
+
+ private MockedStatic fxglMock;
+ private LocalTimer mockTimer;
+ private GameTimer mockGameTimer;
+ private PlayerComponent mockPlayerComponent;
+ private Entity entity;
+
+ private OrangeDinoComponent orangeD;
+ private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
+
+
+ @BeforeEach
+ void setUp(){
+ mockTimer = org.mockito.Mockito.mock(LocalTimer.class);
+ mockGameTimer = org.mockito.Mockito.mock(GameTimer.class);
+ mockPlayerComponent = org.mockito.Mockito.mock(PlayerComponent.class);
+ orangeD = new OrangeDinoComponent(mockGameTimer,mockPlayerComponent);
+
+ entity = new Entity();
+ entity.addComponent(orangeD);
+
+ // 2) Öppna static-mock för FXGL
+ fxglMock = org.mockito.Mockito.mockStatic(FXGL.class);
+
+ // 3) Stubba newLocalTimer() → mockTimer
+ fxglMock.when(FXGL::newLocalTimer).thenReturn(mockTimer);
+
+ System.setOut(new PrintStream(outputStreamCaptor));
+
+ }
+ @AfterEach
+ void tearDown(){
+ fxglMock.close();
+ }
+
+ @Test
+ @DisplayName("Should decrease amount of lives : amountOflives=0")
+ void shouldDecreaseAmountOfLivesWith1_amountOfLivesShouldBe9(){
+
+ //arange
+ int startAmountOfLives = orangeD.getLives(); //10
+
+ //act
+ orangeD.damage(1);
+
+ //assert
+ assertEquals(startAmountOfLives -1, orangeD.getLives());
+ }
+ @Test
+ @DisplayName("Should set pause to true")
+ void shouldPause_(){
+
+ //arange
+ boolean isPaused = true;
+ //act
+ orangeD.setPaused(isPaused);
+
+ //assert
+ assertTrue(isPaused);
+ }
+ @Test
+ @DisplayName("movementSpeed Should be 1.5")
+ void getmovementSpeed_ShouldBeOneAndHalf(){
+ //arange
+ double movementSpeed = 1.5;
+
+ //act and assert
+ assertEquals(movementSpeed, orangeD.getMovementSpeed() );
+ }
+ @Test
+ @DisplayName("Should set new movementSpeed")
+ void setNewMovementSpeed_shouldBe30(){
+
+ //Arrange
+ orangeD.setMovementSpeed(30);
+
+ //Act and Assert
+ assertEquals(30.0, orangeD.getMovementSpeed());
+ }
+
+ //moveUp
+ @Test
+ @DisplayName("Should move orangedino up")
+ void moveUp_shouldMoveUp(){
+ //Act
+ entity.setY(10);
+ orangeD.setMovementSpeed(3);
+ orangeD.moveUp();
+
+ //assert
+ assertEquals(7, entity.getY());
+ }
+ @Test
+ @DisplayName("Should system.out.printIn")
+ void moveUp_shouldNotChangeX_whenOutOfBounds(){
+ //Act
+ entity.setY(-100);
+ orangeD.setMovementSpeed(3);
+ orangeD.moveUp();
+
+ //Assert
+ assertEquals("Out of bounds", outputStreamCaptor.toString().trim());
+ }
+ @Test
+ @DisplayName("Should allow moving into -Y when starting at 0")
+ void moveUp_shouldDecreaseXByMovementSpeed_whenInsideBounds(){
+ //Act
+ entity.setY(0);
+ orangeD.setMovementSpeed(3);
+ orangeD.moveUp();
+
+ //Assert
+ assertEquals(-3, entity.getY());
+ }
+
+ //moveDown
+ @Test
+ @DisplayName("Should move down by movementSpeed when inside bounds")
+ void moveDown_shouldIncreaseYByMovementSpeed_whenInsideBounds(){
+ //Act
+ entity.setY(100);
+ orangeD.setMovementSpeed(10);
+ orangeD.moveDown();
+
+ //Assert
+ assertEquals(110, entity.getY());
+ }
+ @Test
+ @DisplayName("Should not move when ")
+ void moveDown_shouldNotChangeY_WhenOutOfBounds(){
+ //Act
+ entity.setY(750);
+ orangeD.setMovementSpeed(10);
+ orangeD.moveDown();
+
+ assertEquals("Out of bounds", outputStreamCaptor.toString().trim());
+
+ }
+
+ // public void moveDown() {
+ // if (!(entity.getY() < DinosaurGUI.HEIGHT - entity.getHeight())) {
+ // System.out.println("Out of bounds");
+ // return;
+ // }
+ // entity.translateY(movementSpeed);
+ // }
+
+
+
+
+ //moveRight
+ @Test
+ @DisplayName("Should Allow When Y is Bigger Than 0")
+ void moveRight_shouldDecreaseXByMovementSpeed_whenInsideBounds(){
+ //Act
+ entity.setX(100);
+ orangeD.setMovementSpeed(3);
+ orangeD.moveRight();
+ //Assert
+ assertEquals(103, entity.getX());
+ }
+ @Test
+ @DisplayName("Should system.out.printIn")
+ void moveRight_shouldNotChangeX_whenOutOfBounds(){
+ //Act
+ entity.setX(1000);
+ orangeD.setMovementSpeed(3);
+ orangeD.moveRight();
+
+ //Assert
+ assertEquals("Out of bounds", outputStreamCaptor.toString().trim());
+
+ }
+ //moveLeft
+ @Test
+ @DisplayName("Should move entity left by movement speed")
+ void moveLeft_shouldDecreaseXByMovementSpeed_whenInsideBounds(){
+ //Act
+ entity.setX(10);
+ orangeD.setMovementSpeed(5);
+ orangeD.moveLeft();
+
+ //Asser
+ assertEquals(5, entity.getX());
+ }
+ @Test
+ @DisplayName("Should system.out.printIn")
+ void moveLeft_moveLeft_shouldNotChangeX_whenOutOfBounds(){
+ //Arrange
+ entity.setX(-10);
+ orangeD.setMovementSpeed(10);
+ orangeD.moveLeft();
+ //Assert
+ assertEquals("Out of bounds",outputStreamCaptor.toString().trim());
+
+ }
+
+ }
+
+
+}
diff --git a/src/test/java/com/dinosaur/dinosaurexploder/featureTest/GameInitTest.java b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/GameInitTest.java
new file mode 100644
index 00000000..0fad31c1
--- /dev/null
+++ b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/GameInitTest.java
@@ -0,0 +1,56 @@
+// package com.dinosaur.dinosaurexploder.featureTest;
+
+ // import com.almasb.fxgl.dsl.FXGL;
+ // import com.almasb.fxgl.entity.Entity;
+ // import com.almasb.fxgl.time.LocalTimer;
+ // import com.dinosaur.dinosaurexploder.controller.core.GameInitializer;
+ // import javafx.geometry.Point2D;
+ // import org.junit.jupiter.api.*;
+ // import org.mockito.MockedStatic;
+
+ // import static org.junit.jupiter.api.Assertions.*;
+ // import static org.mockito.ArgumentMatchers.*;
+ // import static org.mockito.Mockito.*;
+
+ // public class GameInitTest {
+
+ // private GameInitializer gameInitializer;
+ // private MockedStatic fxglMock;
+ // private Entity mockPlayer;
+
+ // @BeforeEach
+ // void setUp() {
+ // fxglMock = mockStatic(FXGL.class);
+ // mockPlayer = mock(Entity.class);
+
+ // fxglMock.when(() -> FXGL.newLocalTimer()).thenReturn(mock(LocalTimer.class));
+ // fxglMock.when(() -> FXGL.getAppCenter()).thenReturn(new Point2D(400, 300));
+ // fxglMock.when(() -> FXGL.getAppHeight()).thenReturn(600.0);
+ // fxglMock.when(() -> FXGL.getAppWidth()).thenReturn(300.0);
+
+ // fxglMock.when(() -> FXGL.set(anyString(), any())).thenAnswer(inv -> null);
+
+ // fxglMock.when(() -> FXGL.spawn(eq("player"), anyDouble(), anyDouble()))
+ // .thenReturn(mockPlayer);
+ // fxglMock.when(() -> FXGL.spawn(anyString(), anyDouble(), anyDouble()))
+ // .thenAnswer(inv -> mock(Entity.class));
+ // fxglMock.when(() -> FXGL.spawn(anyString(), (Point2D) any()))
+ // .thenAnswer(inv -> mock(Entity.class));
+
+ // gameInitializer = new GameInitializer();
+ // gameInitializer.initGame();
+ // }
+
+
+ // @AfterEach
+ // void tearDown() {
+ // fxglMock.close();
+ // }
+
+ // @Test
+ // void playerShouldBeCreated() {
+
+ // int h = 1;
+ // assertEquals(h, 1);
+ // }
+ // }
\ No newline at end of file
diff --git a/src/test/java/com/dinosaur/dinosaurexploder/featureTest/ParameterizedTest.java b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/ParameterizedTest.java
new file mode 100644
index 00000000..70075aec
--- /dev/null
+++ b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/ParameterizedTest.java
@@ -0,0 +1,5 @@
+package com.dinosaur.dinosaurexploder.featureTest;
+
+public @interface ParameterizedTest {
+
+}
diff --git a/src/test/java/com/dinosaur/dinosaurexploder/featureTest/ShopTest.java b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/ShopTest.java
new file mode 100644
index 00000000..49b6aeb3
--- /dev/null
+++ b/src/test/java/com/dinosaur/dinosaurexploder/featureTest/ShopTest.java
@@ -0,0 +1,91 @@
+package com.dinosaur.dinosaurexploder.featureTest;
+
+import com.dinosaur.dinosaurexploder.exception.LockedShipException;
+import com.dinosaur.dinosaurexploder.model.HighScore;
+import com.dinosaur.dinosaurexploder.model.TotalCoins;
+import com.dinosaur.dinosaurexploder.utils.DataProvider;
+import com.dinosaur.dinosaurexploder.utils.ShipUnlockChecker;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Tests for ShipUnlockChecker verifying unlock logic using coins and high scores.
+ * Adjusted to work independently of LanguageManager translations.
+ */
+public class ShopTest {
+
+ private ShipUnlockChecker shipChecker;
+ private MockDataProvider dataProvider;
+
+ static class MockDataProvider implements DataProvider {
+ private int score;
+ private int coins;
+
+ public void setHighScore(int score) {
+ this.score = score;
+ }
+
+ public void setTotalCoins(int coins) {
+ this.coins = coins;
+ }
+
+ @Override
+ public HighScore getHighScore() {
+ return new HighScore(score);
+ }
+
+ @Override
+ public TotalCoins getTotalCoins() {
+ return new TotalCoins(coins);
+ }
+ }
+
+ @BeforeEach
+ void setUp() {
+ dataProvider = new MockDataProvider();
+ shipChecker = new ShipUnlockChecker(dataProvider);
+ }
+
+ @Test
+ void shipUnlocks_whenHighScoreAndCoinsAreEnough() {
+ dataProvider.setHighScore(350);
+ dataProvider.setTotalCoins(150);
+ assertDoesNotThrow(() -> shipChecker.check(5));
+ }
+
+ @Test
+ void shipLocked_whenLowScoreButEnoughCoins() {
+ dataProvider.setHighScore(200);
+ dataProvider.setTotalCoins(200);
+ LockedShipException ex = assertThrows(LockedShipException.class, () -> shipChecker.check(5));
+ assertNotNull(ex.getMessage());
+ assertFalse(ex.getMessage().isEmpty());
+ }
+
+ @Test
+ void shipLocked_whenEnoughScoreButLowCoins() {
+ dataProvider.setHighScore(350);
+ dataProvider.setTotalCoins(50);
+ LockedShipException ex = assertThrows(LockedShipException.class, () -> shipChecker.check(5));
+ assertNotNull(ex.getMessage());
+ assertFalse(ex.getMessage().isEmpty());
+ }
+
+ @Test
+ void shipLocked_whenLowScoreAndLowCoins() {
+ dataProvider.setHighScore(100);
+ dataProvider.setTotalCoins(100);
+ LockedShipException ex = assertThrows(LockedShipException.class, () -> shipChecker.check(6));
+ assertNotNull(ex.getMessage());
+ assertFalse(ex.getMessage().isEmpty());
+ }
+
+ @Test
+ void shipUnlocks_whenNoRequirements() {
+ dataProvider.setHighScore(0);
+ dataProvider.setTotalCoins(0);
+ assertDoesNotThrow(() -> shipChecker.check(1));
+ }
+}